微信公众号:运维开发故事,作者:double冬
1.Service介绍
1.1什么是Service
service是k8s中的一个重要概念,主要是提供负载均衡和服务自动发现。
Service 是由 kube-proxy 组件,加上 iptables 来共同实现的。
1.2.Service的创建
创建Service的方法有两种:
1.通过kubectl expose创建
#kubectl expose deployment nginx --port=88 --type=NodePort --target-port=80 --name=nginx-service 这一步说是将服务暴露出去,实际上是在服务前面加一个负载均衡,因为pod可能分布在不同的结点上。–port:暴露出去的端口–type=NodePort:使用结点+端口方式访问服务–target-port:容器的端口–name:创建service指定的名称
2.通过yaml文件创建
创建一个名为nginx-service的服务,将在端口88接收请求并将链接路由到具有标签选择器是app=nginx的pod的80端口上
使用kubectl create来创建serivice
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
app: nginx
spec:
type: NodePort
ports:
- port: 88 //该服务的可用端口
targetPort: 80
selector:
app: nginx //具有app=hostnames标签的pod都属于该服务
使用如下命令来检查服务:
[root@k8s-master ~]# kubectl get svc
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
nginx-service NodePort 10.0.0.28 88:36877/TCP 13s
服务并不是和pod直接相连的,介于他们之间的就是Endpoint资源。
Endpoint资源就是暴露一个服务的IP地址和端口列表。
通过service查看endpoint方法如下:
[root@k8s-master ~]# kubectl -n kube-system get svc kube-dns
NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE
kube-dns ClusterIP 10.0.0.2 53/UDP,53/TCP 28d
[root@k8s-master ~]# kubectl -n kube-system describe svc kube-dns
Name: kube-dns
Namespace: kube-system
Labels: addonmanager.kubernetes.io/mode=Reconcile
k8s-app=kube-dns
kubernetes.io/cluster-service=true
kubernetes.io/name=CoreDNS
Annotations: kubectl.kubernetes.io/last-applied-configuration:
{"apiVersion":"v1","kind":"Service","metadata":{"annotations":{"prometheus.io/port":"9153","prometheus.io/scrape":"true"},"labels":{"addon...
prometheus.io/port: 9153
prometheus.io/scrape: true
Selector: k8s-app=kube-dns
Type: ClusterIP
IP: 10.0.0.2
Port: dns 53/UDP
TargetPort: 53/UDP
Endpoints: 172.17.80.4:53
Port: dns-tcp 53/TCP
TargetPort: 53/TCP
Endpoints: 172.17.80.4:53 //代表服务endpoint的pod的ip和端口列表
Session Affinity: None
Events:
直接查看endpoint信息方法如下:
[root@k8s-master ~]# kubectl -n kube-system get endpoints kube-dns
NAME ENDPOINTS AGE
kube-dns 172.17.80.4:53,172.17.80.4:53 28d
[root@k8s-master ~]# kubectl -n kube-system describe endpoints kube-dns
Name: kube-dns
Namespace: kube-system
Labels: addonmanager.kubernetes.io/mode=Reconcile
k8s-app=kube-dns
kubernetes.io/cluster-service=true
kubernetes.io/name=CoreDNS
Annotations: endpoints.kubernetes.io/last-change-trigger-time: 2020-01-01T23:38:21+08:00
Subsets:
Addresses: 172.17.80.4
NotReadyAddresses:
Ports:
Name Port Protocol
---- ---- --------
dns 53 UDP
dns-tcp 53 TCP
Events:
如果创建pod时不包含选择器,则k8s将不会创建endpoint资源。这样就需要创建endpoint来指的服务的对应的endpoint列表。
service中创建endpoint资源,其中一个作用就是用于service知道包含哪些pod。
有3种方式在外部访问服务:
1.将服务的类型设置成NodePort;
2.将服务的类型设置成LoadBalance;
3.创建一个Ingress资源。
NodePort 服务是引导外部流量到你的服务的最原始方式。NodePort,正如这个名字所示,在所有节点(虚拟机)上开放一个特定端口,任何发送到该端口的流量都被转发到对应服务。
YAML 文件类似如下:
apiVersion: v1
kind: Service
metadata:
name: nginx-service
labels:
app: nginx
spec:
type: NodePort //为NodePort设置服务类型
ports:
- port: 88
targetPort: 80
nodePort: 30123 //通过集群节点的30123端口可以访问服务
selector:
app: nginx
这种方法有许多缺点:
1.每个端口只能是一种服务
2.端口范围只能是 30000-32767
如果节点/VM 的 IP 地址发生变化,你需要能处理这种情况
基于以上原因,我不建议在生产环境上用这种方式暴露服务。如果你运行的服务不要求一直可用,或者对成本比较敏感,你可以使用这种方法。这样的应用的最佳例子是 demo 应用,或者某些临时应用。
3.2.通过Loadbalance将服务暴露出来
LoadBalancer 服务是暴露服务到 internet 的标准方式。在 GKE 上,这种方式会启动一个 Network Load Balancer[2],它将给你一个单独的 IP 地址,转发所有流量到你的服务
通过如下方法来定义服务使用负载均衡
apiVersion: v1
kind: Service
metadata:
name: loadBalancer-nginx
spec:
type: LoadBalancer //该服务从k8s集群的基础架构获取负载均衡器
ports:
- port: 80
targetPort: 8080
selector:
app: nginx
何时使用这种方式?
如果你想要直接暴露服务,这就是默认方式。所有通往你指定的端口的流量都会被转发到对应的服务。它没有过滤条件,没有路由等。这意味着你几乎可以发送任何种类的流量到该服务,像 HTTP,TCP,UDP,Websocket,gRPC 或其它任意种类。
这个方式的最大缺点是每一个用 LoadBalancer 暴露的服务都会有它自己的 IP 地址,每个用到的 LoadBalancer 都需要付费,这将是非常昂贵的。
为什么使用Ingress,一个重要的原因是LoadBalancer服务都需要创建自己的负载均衡器,以及独有的公有Ip地址,而Ingress只需要一个公网Ip就能为许多服务提供访问。
Ingress 事实上不是一种服务类型。相反,它处于多个服务的前端,扮演着“智能路由”或者集群入口的角色。
你可以用 Ingress 来做许多不同的事情,各种不同类型的 Ingress 控制器也有不同的能力。
Ingress 组成
ingress controller
将新加入的Ingress转化成Nginx的配置文件并使之生效
ingress服务
将Nginx的配置抽象成一个Ingress对象,每添加一个新的服务只需写一个新的Ingress的yaml文件即可
Nginx:实现负载均衡到pod的集合
Ingress Controller:从集群api获取services对应pod的ip到nginx配置文件中
Ingress:为nginx创建虚拟主机
了解Ingress 工作原理
ingress controller通过和kubernetes api交互,动态的去感知集群中ingress规则变化
然后读取它,按照自定义的规则,规则就是写明了哪个域名对应哪个service或者单个域名多个location形式,生成一段nginx配置
再动态注入到nginx-ingress-control的pod里,这个Ingress controller的pod里运行着一个Nginx服务,控制器会把生成的nginx配置写入/etc/nginx/nginx.conf文件中
然后reload一下使配置生效。
何时使用这种方式?
Ingress 可能是暴露服务的最强大方式,但同时也是最复杂的。Ingress 控制器有各种类型,包括 Google Cloud Load Balancer, Nginx,Contour,Istio,等等。它还有各种插件,比如 cert-manager[5],它可以为你的服务自动提供 SSL 证书。
如果你想要使用同一个 IP 暴露多个服务,这些服务都是使用相同的七层协议(典型如 HTTP),那么Ingress 就是最有用的。如果你使用本地的 GCP 集成,你只需要为一个负载均衡器付费,且由于 Ingress是“智能”的,你还可以获取各种开箱即用的特性(比如 SSL、认证、路由等等)。
ingress的部署,需要考虑两个方面:
ingress-controller是作为pod来运行的,以什么方式部署比较好
ingress解决了把如何请求路由到集群内部,那它自己怎么暴露给外部比较好
下面列举一些目前常见的部署和暴露方式,具体使用哪种方式还是得根据实际需求来考虑决定
Deployment+LoadBalancer模式的Service
如果要把ingress部署在公有云,那用这种方式比较合适。用Deployment部署ingress-controller,创建一个type为LoadBalancer的service关联这组pod。大部分公有云,都会为LoadBalancer的service自动创建一个负载均衡器,通常还绑定了公网地址。只要把域名解析指向该地址,就实现了集群服务的对外暴露。
Deployment+NodePort模式的Service
同样用deployment模式部署ingress-controller,并创建对应的服务,但是type为NodePort。这样,ingress就会暴露在集群节点ip的特定端口上。由于nodeport暴露的端口是随机端口,所以测试的时候可以手动增加type: Nodeport,手动指定端口或者一般会在前面再搭建一套负载均衡器来转发请求。该方式一般用于宿主机是相对固定的环境ip地址不变的场景。
nodePort的部署思路就是通过在每个节点上开辟nodePort的端口,将流量引入进来,而后通过iptables首先转发到ingress-controller容器中(图中的nginx容器),而后由nginx根据ingress的规则进行判断,将其转发到对应的应用web容器中。NodePort方式暴露ingress虽然简单方便,但是NodePort多了一层NAT,在请求量级很大时可能对性能会有一定影响。
DaemonSet+HostNetwork+nodeSelector
hostNetwork模式不再需要创建一个nodePort的svc,而是直接在每个节点都创建一个ingress-controller的容器,而且将该容器的网络模式设为hostNetwork。也就是说每个节点物理机的80和443端口将会被ingress-controller中的nginx容器占用。当流量通过80/443端口进入时,将直接进入到nginx中。而后nginx根据ingress规则再将流量转发到对应的web应用容器中。使用hostNetwork的方式,ingress-controller将会使用的是物理机的DNS域名解析(即物理机的/etc/resolv.conf)。而无法使用内部的比如coredns的域名解析
用DaemonSet结合nodeselector来部署ingress-controller到特定的node上,然后使用HostNetwork直接把该pod与宿主机node的网络打通,直接使用宿主机的80/433端口就能访问服务。这时,ingress-controller所在的node机器就很类似传统架构的边缘节点,比如机房入口的nginx服务器。该方式整个请求链路最简单,性能相对NodePort模式更好。缺点是由于直接利用宿主机节点的网络和端口,一个node只能部署一个ingress-controller pod。比较适合大并发的生产环境使用。
github提供了两种方式下载ingress部署文件 :
默认下载最新的yaml
指定版本号下载对应的yaml
官方部署文件地址长期更新:https://kubernetes.github.io/ingress-nginx/deploy/
Deployment+NodePort模式的service
一、下载nginx-ingress的部署配置文件
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/static/mandatory.yaml
将镜像拉下来并更改mandatory.yaml中的镜像地址或者去阿里云公开镜像找相关镜像替换
docker pull registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1
替换镜像地址
sed -i 's#quay.io/kubernetes-ingress-controller/nginx-ingress-controller:0.26.1#registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1#g' mandatory.yaml
二、下载部署service-nodeport文件用于对外提供服务
wget https://raw.githubusercontent.com/kubernetes/ingress-nginx/master/deploy/provider/baremetal/service-nodeport.yaml
修改service文件,指定一下nodePort,使用30080端口和30443端口作为nodePort。因为nodePort端口必须在30000以上,更改完如下所示:
apiVersion: v1
kind: Service
metadata:
name: ingress-nginx
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
type: NodePort # 指定端口类型
ports:
- name: http
port: 80
targetPort: 80
nodePort: 30080 # 新增30080端口,http访问时需要加上该端口
protocol: TCP
- name: https
port: 443
targetPort: 443
nodePort: 30443 # 新增30443端口来对外映射,https访问时需要加上该端口
protocol: TCP
selector:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
三、部署一个tomcat用于测试ingress转发功能
# cat tomcat-svc-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: tomcat
namespace: ingress-nginx
spec:
selector:
app: tomcat
release: canary
ports:
- name: http
targetPort: 8080 # 容器port
port: 8080 # service port
- name: ajp
targetPort: 8009
port: 8009
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-deploy
namespace: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app: tomcat
release: canary
template:
metadata:
labels:
app: tomcat
release: canary
spec:
containers:
- name: tomcat
image: tomcat
ports:
- name: http
containerPort: 8080
四、定义ingress策略
cat ingress-tomcat.yaml
#cat ingress-tomcat.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-tomcat
namespace: ingress-nginx
annotations:
kubernetes.io/ingress.class: "nginx"
# nginx.ingress.kubernetes.io/rewrite-target: / # 重写规则
# nginx.ingress.kubernetes.io/use-regex: "true" # 开启use-regex启动正则path匹配
spec:
rules:
- host: tomcat.ingress.com # 对应的域名
http:
paths:
- path: # url上下文
backend: # 后向转发,到对应的 serviceName:servicePort
serviceName: tomcat
servicePort: 8080
五、本地做hosts解析
将tomcat.ingress.com域名在本地做hosts解析,解析的ip为ingress-controller这个pod所在的node机器外网地址,然后浏览器访问:http://tomcat.ingress.com:30080
成功访问 到tomcat界面表示deployment+nodeport方式的ingress已经成功
DaemonSet+HostNetwork+nodeSelector
更改 mandatory.yaml 文件中deployment部分:
修改参数如下:
kind: Deployment #修改为DaemonSet
replicas: 1 #注销此行,DaemonSet不需要此参数
hostNetwork: true #添加该字段让docker使用物理机网络,在物理机暴露服务端口(80),注意物理机80端口提前不能被占用
dnsPolicy: ClusterFirstWithHostNet #使用hostNetwork后容器会使用物理机网络包括DNS,会无法解析内部service,使用此参数让容器使用K8S的DNS。当前测试没添加。
nodeSelector:isingress: “true” #添加节点标签
tolerations: 添加对指定节点容忍度。当前测试没添加
apiVersion: extensions/v1beta1
kind: DaemonSet
metadata:
name: nginx-ingress-controller
namespace: ingress-nginx
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
spec:
# 注释Replicas
# replicas: 1
selector:
matchLabels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
template:
metadata:
labels:
app.kubernetes.io/name: ingress-nginx
app.kubernetes.io/part-of: ingress-nginx
annotations:
prometheus.io/port: "10254"
prometheus.io/scrape: "true"
spec:
serviceAccountName: nginx-ingress-serviceaccount
# 选择对应标签的node
nodeSelector:
isIngress: "true"
# 使用hostNetwork暴露服务
hostNetwork: true
containers:
- name: nginx-ingress-controller
image: registry.cn-hangzhou.aliyuncs.com/google_containers/nginx-ingress-controller:0.26.1
给节点打标签,让ingress-controller以ds的方式跑在这台机器上
[root@k8s-master ingrress-nginx]# kubectl label node 192.168.2.220 isIngress=true
[root@k8s-master ingrress-nginx]# kubectl get node -l isIngress=true
NAME STATUS ROLES AGE VERSION
192.168.2.220 Ready node 76d v1.15.0
用修改完成后的mandatory.yaml文件部署ingress-controller控制器
[root@k8s-master ingrress-nginx]# kubectl apply -f mandatory.yaml
namespace/ingress-nginx created
configmap/nginx-configuration created
configmap/tcp-services created
configmap/udp-services created
serviceaccount/nginx-ingress-serviceaccount created
clusterrole.rbac.authorization.k8s.io/nginx-ingress-clusterrole created
role.rbac.authorization.k8s.io/nginx-ingress-role created
rolebinding.rbac.authorization.k8s.io/nginx-ingress-role-nisa-binding created
clusterrolebinding.rbac.authorization.k8s.io/nginx-ingress-clusterrole-nisa-binding created
daemonset.extensions/nginx-ingress-controller created
[root@k8s-master ingress-daemonset]# kubectl get ds -n ingress-nginx
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
nginx-ingress-controller 1 1 1 1 1 isIngress=true 6m8s
[root@k8s-master ingress-daemonset]# kubectl get po -n ingress-nginx -o wide
NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES
nginx-ingress-controller-d52lg 1/1 Running 0 2m1s 192.168.2.220 192.168.2.220
可以看到,nginx-controller的pod已经部署在在192.168.2.220这个节点上了暴露nginx-controller到192.168.2.220上看下本地端口:
[root@k8s-node01 ~]# netstat -lntup | grep nginx
tcp 0 0 127.0.0.1:10246 0.0.0.0:* LISTEN 95817/nginx: master
tcp 0 0 127.0.0.1:10247 0.0.0.0:* LISTEN 95817/nginx: master
tcp 0 0 0.0.0.0:80 0.0.0.0:* LISTEN 95817/nginx: master
tcp 0 0 0.0.0.0:8181 0.0.0.0:* LISTEN 95817/nginx: master
tcp 0 0 0.0.0.0:443 0.0.0.0:* LISTEN 95817/nginx: master
tcp 0 0 127.0.0.1:10245 0.0.0.0:* LISTEN 95782/nginx-ingress
tcp6 0 0 :::10254 :::* LISTEN 95782/nginx-ingress
tcp6 0 0 :::80 :::* LISTEN 95817/nginx: master
tcp6 0 0 :::8181 :::* LISTEN 95817/nginx: master
tcp6 0 0 :::443 :::* LISTEN 95817/nginx: master
由于配置了hostnetwork,nginx已经在node主机本地监听80/443/8181端口。其中8181是nginx-controller默认配置的一个default backend。这样,只要访问node主机有公网IP,就可以直接映射域名来对外网暴露服务了。如果要nginx高可用的话,可以在多个node上部署,并在前面再搭建一套LVS+keepalive做负载均衡。用hostnetwork的另一个好处是,如果lvs用DR模式的话,是不支持端口映射的,这时候如果用nodeport,暴露非标准的端口,管理起来会很麻烦。
配置ingress资源
创建一个tomcat的deployment应用用来测试ingress转发功能。
因为我们创建的ingress-controller采用的是hostnetwork模式,所以无需在创建nodePort形式的ingress-svc服务来把端口映射到节点主机上。即svc文件中不需要指定nodePort
# cat tomcat-svc-deployment.yaml
apiVersion: v1
kind: Service
metadata:
name: tomcat
namespace: ingress-nginx
spec:
selector:
app: tomcat
release: canary
ports:
- name: http
targetPort: 8080
port: 8080
- name: ajp
targetPort: 8009
port: 8009
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: tomcat-deploy
namespace: ingress-nginx
spec:
replicas: 1
selector:
matchLabels:
app: tomcat
release: canary
template:
metadata:
labels:
app: tomcat
release: canary
spec:
containers:
- name: tomcat
image: tomcat
ports:
- name: httpd
containerPort: 8080
定义ingress策略,通过Ingress把tomcat发布出去
# cat ingress-tomcat.yaml
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-tomcat
namespace: ingress-nginx
annotations:
kubernetes.io/ingress.class: "nginx"
# nginx.ingress.kubernetes.io/rewrite-target: / # 重写规则
# nginx.ingress.kubernetes.io/ssl-redirect: "true" # 设置是否强制跳转
# nginx.ingress.kubernetes.io/use-regex: "true" # 开启use-regex启动正则path匹配
spec:
rules:
- host: tomcat.ingress.com
http:
paths:
- path:
backend:
serviceName: tomcat # 指定需要绑定暴露的svc名称
servicePort: 8080
部署tomcat应用的svc和deployment,并且部署ingress规则
[root@k8s-master ingress-daemonset]# kubectl apply -f tomcat-svc-deployment.yaml
[root@k8s-master ingress-daemonset]# kubectl apply -f ingress-tomcat.yam
[root@k8s-master ingress-daemonset]# kubectl get ds,po -n ingress-nginx
NAME DESIRED CURRENT READY UP-TO-DATE AVAILABLE NODE SELECTOR AGE
daemonset.extensions/nginx-ingress-controller 1 1 1 1 1 isIngress=true 10m
NAME READY STATUS RESTARTS AGE
pod/nginx-ingress-controller-d52lg 1/1 Running 0 5m53s
pod/tomcat-deploy-69d84cbf7c-qxnnd 1/1 Running 0 83s
[root@k8s-master ingress-daemonset]# kubectl get ing -n ingress-nginx
NAME HOSTS ADDRESS PORTS AGE
ingress-tomcat tomcat.ingress.com 80 37s
最后浏览器直接通过域名访问,不加任何端口。成功访问到tomcat界面表示ingress+DaemonSet+nodeSelector方式部署成功
生产环境中,建议把ingress通过DaemonSet的方式部署集群中,而且该节点打上污点不允许业务pod进行调度,以避免业务应用与Ingress服务发生资源争抢。然后通过SLB把ingress节点主机添为后端服务器,进行流量转发
配置ingress-nginx使用https
ingress-nginx创建https方法:https://blog.csdn.net/bbwangj/article/details/82940419
生成ingress-nginx的tls类型secret创建命令:
kubectl create secret tls ingress-https --key xxxx.key --cert xxxx.pem <-n namespace>
一、制作自签证书
如果是阿里云上购买的域名,则可以申请一个免费的证书与之绑定,然后把证书下载下来再用来创建
[root@k8s-master ingress-daemonset]# openssl genrsa -out tls.key 2048
[root@k8s-master ingress-daemonset]# openssl req -new -x509 -key tls.key -out tls.crt -subj /C=CN/ST=HangZhou/L=ZheJiang/O=devops/CN=tomcat.ingress.com
这时候会生成两个文件:tls.crt和tls.key
第二步:利用这两个文件创建secret
[root@k8s-master ingress-daemonset]# kubectl create secret tls tomcat-ingress-https --cert=tls.crt --key=tls.key -n ingress-nginx
secret/tomcat-ingress-https created
[root@k8s-master ingress-daemonset]# kubectl get secret -n ingress-nginx
NAME TYPE DATA AGE
default-token-8465p kubernetes.io/service-account-token 3 17h
nginx-ingress-serviceaccount-token-k2j27 kubernetes.io/service-account-token 3 17h
tomcat-ingress-https kubernetes.io/tls 2 6s
第三步:修改ingress策略,发布暴露tomcat应用添加tls部分
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: ingress-tomcat
namespace: ingress-nginx
annotations:
kubernetes.io/ingress.class: "nginx"
spec:
tls: # 添加tls
- hosts:
- tomcat.ingress.com # 指定证书绑定的域名
secretName: tomcat-ingress-https # 指定刚才创建的secret名称
rules:
- host: tomcat.ingress.com
http:
paths:
- path:
backend:
serviceName: tomcat
servicePort: 8080
更新原来ingress发布策略
[root@k8s-master ingress-daemonset]# kubectl apply -f ingress-tomcat.yaml
ingress.extensions/ingress-tomcat configured
web浏览器使用https访问测试:https://tomcat.ingress.com。
由于因为自制证书,所以这个是正常的。如果自制证书不想看到不安全,可以设置浏览器信任该证书。正常访问,则表示部署https方式的ingress成功
公众号:运维开发故事
github:https://github.com/orgs/sunsharing-note/dashboard
爱生活,爱运维
如果你觉得文章还不错,就请点击右上角选择发送给朋友或者转发到朋友圈。您的支持和鼓励是我最大的动力。喜欢就请关注我吧~
扫码二维码
关注我,不定期维护优质内容
温馨提示
如果你喜欢本文,请分享到朋友圈,想要获得更多信息,请关注我。
........................