service简介
在Kubernetes中对 Pod 的期望值并不是持续健壮的,Pod 中的容器很可能因为各种原因发生故障而死掉。Deployment 等 controller 会通过动态创建和销毁 Pod 来保证应用整体的健壮性。换句话说,Pod 是脆弱的,但应用是健壮的。
每个 Pod 都有自己的 IP 地址。当 controller 用新 Pod 替代发生故障的 Pod 时,新 Pod 会分配到新的 IP 地址。这样就产生了一个问题:
如果一组 Pod 对外提供服务(比如 HTTP),它们的 IP 很有可能发生变化,那么客户端如何找到并访问这个服务呢?
Kubernetes 给出的解决方案是 Service。
Service定义了一组pods
的逻辑集合和一个用于访问它们的策略,通过Service可以为一组具有相同功能的容器应用提供一个统一的入口地址,并且将请求负载分发到后端的各个容器应用上。
一个Service的目标Pod集合通常是由Label Selector 来决定的,当Pod宕机后重新生成时,其IP等状态信息会变动,Service会根据Pod的Label对这些状态信息进行监控和变更,保证上游服务不受Pod的变动而影响。
service代理三种模式
k8s群集中的每个节点都运行一个kube-proxy的组件,kube-proxy其实是一个代理层,负责k8s service的实现,即实现了k8s内部从pod到service和外部从node port到service的访问。
kube-proxy当前支持三种不同的操作模式:
1.userspace(用户空间)
这种模式之所以得名,是因为服务路由发生在kube-proxy用户进程空间而不是内核网络堆栈(如iptables)中。
userspace模式中,所有的客户端请求svc,先经过iptables,然后再经过kube-proxy到pod,频繁的切换内核和用户空间,所以性能很差。它不常用,因为它运行缓慢且过时。
2.iptables
相比较于userspace方式,kube-proxy不再负责代理工作,只需监听apiserver写入etcd中关于pod的变化,将需要变更的规则添加到iptables中。
3.ipvs(IP虚拟服务器)
客户端访问ServiceIP(clusterIP)请求会由内核中的ipvs直接重定向到后端某一个pod。(流程图与iptables方式一样)
从k8s的1.8版本开始,kube-proxy引入了IPVS模式,IPVS模式与iptables同样基于Netfilter,但是ipvs采用的hash表,iptables采用一条条的规则列表。iptables又是为了防火墙设计的,集群数量越多iptables规则就越多,而且iptables规则是从上到下匹配,所以效率就越是低下。因此当service数量达到一定规模时,hash查表的速度优势就会显现出来,从而提高service的服务性能。
ipvs 支持比 iptables 更复杂的负载均衡算法(最小负载、最少连接、加权等等),ipvs 支持服务器健康检查和连接重试等功能。
总之,每个节点的kube-proxy负责监听API server中service和endpoint的变化情况。将变化信息写入本地userspace、iptables、ipvs来实现service负载均衡,使用NAT将vip流量转至endpoint中。
目前,service已默认使用ipvs规则,如果没有加载并启用ipvs模块,或者没有配置ipvs相关配置,则会被降级成iptables模式。
service的四种类型
ClusterIP
ClusterIP是默认模式,如图所示只能在集群内部访问。
来看个例子,创建下面的这个 Deployment:
apiVersion: apps/v1 kind: Deployment metadata: name: nginx spec: replicas: 3 selector: matchLabels: run: nginx strategy: {} template: metadata: labels: run: nginx spec: containers: - image: nginx:1.8 name: nginx ports: - containerPort: 80 resources: {} status: {}
run: nginx
,Service 将会用这个 label 来挑选 Pod。[root@master service]# kubectl apply -f nginx.yaml deployment.apps/nginx created [root@master service]# kubectl get pods -o wide NAME READY STATUS RESTARTS AGE IP NODE NOMINATED NODE READINESS GATES nginx-5f9d59d9fc-299b9 1/1 Running 0 88s 10.244.2.26 node2 <none> <none> nginx-5f9d59d9fc-8whz6 1/1 Running 0 88s 10.244.1.22 node1 <none> <none> nginx-5f9d59d9fc-hzt6s 1/1 Running 0 88s 10.244.1.23 node1 <none> <none>
三个pod分配到2台node上,每个pod都分配了各自的 IP,这些 IP 只能被 Kubernetes Cluster 中的容器和节点访问:
[root@master service]# curl -I 10.244.2.26 HTTP/1.1 200 OK Server: nginx/1.8.1 [root@master service]# curl -I 10.244.1.22 HTTP/1.1 200 OK Server: nginx/1.8.1 [root@master service]# curl -I 10.244.1.23 HTTP/1.1 200 OK Server: nginx/1.8.1
接下来创建service,其配置文件如下:
apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: //selector
指明挑选那些 label 为run: nginx
的 Pod 作为 Service 的后端。 run: nginx ports: - protocol: TCP port: 8080 //将 Service 的 8080 端口映射到 Pod 的 80 端口,使用 TCP 协议 targetPort: 80 type: ClusterIP
执行 kubectl apply 创建 Service nginx-service:
[root@master service]# kubectl apply -f nginx-service.yaml service/nginx-service created [root@master service]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 34d nginx-service ClusterIP 10.110.59.203 <none> 8080/TCP 7m18s
nginx-service 分配到一个 ClusterIP 为10.110.59.203,这个就是service的虚拟ip,可以通过该 IP 访问后端的 Pod:
[root@master ~]# curl -I 10.110.59.203:8080 HTTP/1.1 200 OK Server: nginx/1.8.1
根据前面的端口映射,这里要使用 8080 端口。另外,除了我们创建的 nginx-service,还有一个 Service 是kubernetes,这是k8s默认自带的service,Cluster 内部通过这个 Service 访问 kubernetes API Server。
通过 kubectl describe 可以查看 nginx-service 与 Pod 的对应关系,Endpoints中的地址正好和前面的创建的3个pod的地址相对应:
可以总结一下,nginx-service作为一个类型为ClusterIP的service,定义了三个nginx的pod的逻辑集合,为这组具有相同功能的容器应用提供了一个统一的入口地址,即10.110.59.203:8080,即使后端的pod由于各种原因被销毁再被创建(控制器要求保持三个副本),整个service都能提供稳定服务。
深入理解Service-IP的工作原理
Service Cluster IP 是一个虚拟 IP,是由 Kubernetes 节点上的 iptables 规则管理的。
可以通过 iptables-save 命令打印出当前节点的 iptables 规则,因为输出较多,这里只截取与nginx-service Cluster IP 10.110.59.203 相关的信息:
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.110.59.203/32 -p tcp -m comment --comment "default/nginx-service: cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ -A KUBE-SERVICES -d 10.110.59.203/32 -p tcp -m comment --comment "default/nginx-service: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-GKN7Y2BSGW4NJTYL
这两条规则的含义是:
如果 Cluster 内的 Pod(源地址来自 10.244.0.0/16)要访问 nginx-service,则允许。
其他源地址访问 nginx-service,跳转到规则KUBE-SVC-GKN7Y2BSGW4NJTYL。
KUBE-SVC-GKN7Y2BSGW4NJTYL 规则如下:
[root@master ~]# iptables-save |grep KUBE-SVC-GKN7Y2BSGW4NJTYL :KUBE-SVC-GKN7Y2BSGW4NJTYL - [0:0] -A KUBE-SERVICES -d 10.110.59.203/32 -p tcp -m comment --comment "default/nginx-service: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-GKN7Y2BSGW4NJTYL -A KUBE-SVC-GKN7Y2BSGW4NJTYL -m comment --comment "default/nginx-service:" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-VMLJ2G2AI2XTLVYF -A KUBE-SVC-GKN7Y2BSGW4NJTYL -m comment --comment "default/nginx-service:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-UZZUH7MRVLV5UEPK -A KUBE-SVC-GKN7Y2BSGW4NJTYL -m comment --comment "default/nginx-service:" -j KUBE-SEP-T7B57UROKNW6T76L
1/3 的概率跳转到规则 KUBE-SEP-VMLJ2G2AI2XTLVYF
1/3 的概率(剩下 2/3 的一半)跳转到规则 KUBE-SEP-UZZUH7MRVLV5UEPK
还剩下的1/3 的概率跳转到规则 KUBE-SEP-T7B57UROKNW6T76L
上面的转发规则又通过DNAT目标地址转换来实现请求转发:
[root@master ~]# iptables-save |grep :80 -A KUBE-SEP-T7B57UROKNW6T76L -p tcp -m comment --comment "default/nginx-service:" -m tcp -j DNAT --to-destination 10.244.2.26:80 -A KUBE-SEP-UZZUH7MRVLV5UEPK -p tcp -m comment --comment "default/nginx-service:" -m tcp -j DNAT --to-destination 10.244.1.23:80 -A KUBE-SEP-VMLJ2G2AI2XTLVYF -p tcp -m comment --comment "default/nginx-service:" -m tcp -j DNAT --to-destination 10.244.1.22:80
可见,iptables 将访问 Service 的流量平均转发到后端 Pod,而且使用类似轮询的负载均衡策略。
另外需要补充一点:k8s集群中的每一个节点都配置了相同的 iptables 规则,这样就确保了整个集群都能够通过 Service 的 Cluster IP 访问 Service:
[root@master ~]# curl -I 10.110.59.203:8080 HTTP/1.1 200 OK Server: nginx/1.8.1 [root@node1 ~]# curl -I 10.110.59.203:8080 HTTP/1.1 200 OK Server: nginx/1.8.1 [root@node2 ~]# curl -I 10.110.59.203:8080 HTTP/1.1 200 OK Server: nginx/1.8.1
所以整个集群中的每个节点都可以通过统一的clusterIP 来访问service,而且自动实现了应用之间的负载均衡。
需要注意的是,每个节点上的kube-proxy都时刻监听着apiserver,一旦pod或service的信息有改变动,都会马上在本地iptables中同步相关的规则信息,从而保证可用性。
比如销毁并重新创建了一个pod,kube-proxy会修改iptables相关规则中pod的ip信息;比如kubectl delete svc删除了一个service,kube-proxy会删除iptables关于这个svc的多条规则。
NodePort
我们的场景不全是集群内访问,也需要集群外业务访问。那么ClusterIP就满足不了了。NodePort就是其中的一种实现方案。
来实践一下 NodePort,Service的配置文件修改如下,其实就是改了一下type:
apiVersion: v1 kind: Service metadata: name: nginx-service spec: selector: run: nginx ports: - protocol: TCP port: 8080 targetPort: 80 type: NodePort
重新创建nginx-service:
[root@master service]# kubectl apply -f nginx-service.yaml service/nginx-service created [root@master service]# kubectl get svc NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE kubernetes ClusterIP 10.96.0.1 <none> 443/TCP 35d nginx-service NodePort 10.99.156.118 <none> 8080:31923/TCP 8s
Kubernetes 依然会为 nginx-svc 分配一个 ClusterIP,不同的是:
EXTERNAL-IP 为 nodes,表示可通过 Cluster 每个节点自身的 IP 访问 Service。
PORT(S) 为 8080:31923 ,8080 是 ClusterIP 监听的端口,31923 则是node节点上监听的端口。Kubernetes 会从 30000-32767 中分配一个可用的端口,每个节点都会监听此端口并将请求转发给 Service。
[root@master service]# netstat -anp|grep 31923 tcp 0 0 0.0.0.0:31923 0.0.0.0:* LISTEN 2457/kube-proxy [root@node1 ~]# netstat -anp|grep 31923 tcp 0 0 0.0.0.0:31923 0.0.0.0:* LISTEN 1635/kube-proxy [root@node2 ~]# netstat -anp|grep 31923 tcp 0 0 0.0.0.0:31923 0.0.0.0:* LISTEN 1765/kube-proxy
测试 NodePort 是否正常工作:
[root@master ~]# curl -I 172.31.93.200:31923 HTTP/1.1 200 OK Server: nginx/1.8.1 [root@master ~]# curl -I 172.31.93.201:31923 HTTP/1.1 200 OK Server: nginx/1.8.1 [root@master ~]# curl -I 172.31.93.202:31923 HTTP/1.1 200 OK Server: nginx/1.8.1
通过三个节点 IP + 31923 端口都能够访问 nginx-service。
接下来探讨一个问题:Kubernetes 是如何将 <NodeIP>:<NodePort> 映射到 Pod 的呢?
与 ClusterIP 一样,也是借助了 iptables。与 ClusterIP 相比,每个节点的 iptables 中都增加了下面两条规则:
[root@master ~]# iptables-save |grep 31923 -A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx-service:" -m tcp --dport 31923 -j KUBE-MARK-MASQ -A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx-service:" -m tcp --dport 31923 -j KUBE-SVC-GKN7Y2BSGW4NJTYL
规则的含义是,访问当前节点31923端口的请求会应用规则 KUBE-SVC-GKN7Y2BSGW4NJTYL ,内容为:
[root@master ~]# iptables-save |grep KUBE-SVC-GKN7Y2BSGW4NJTYL :KUBE-SVC-GKN7Y2BSGW4NJTYL - [0:0] -A KUBE-NODEPORTS -p tcp -m comment --comment "default/nginx-service:" -m tcp --dport 31923 -j KUBE-SVC-GKN7Y2BSGW4NJTYL -A KUBE-SERVICES -d 10.99.156.118/32 -p tcp -m comment --comment "default/nginx-service: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-GKN7Y2BSGW4NJTYL -A KUBE-SVC-GKN7Y2BSGW4NJTYL -m comment --comment "default/nginx-service:" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-VMLJ2G2AI2XTLVYF -A KUBE-SVC-GKN7Y2BSGW4NJTYL -m comment --comment "default/nginx-service:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-UZZUH7MRVLV5UEPK -A KUBE-SVC-GKN7Y2BSGW4NJTYL -m comment --comment "default/nginx-service:" -j KUBE-SEP-T7B57UROKNW6T76L
可见,NodeIP:31923与10.99.156.118:8080的请求,采取了相同的处理方式,平均丢给后端三个pod的规则,策略还是轮休。
NodePort 默认是的随机选择,不过我们可以用 nodePort 指定某个特定端口:
现在配置文件中就有三个 Port 了:
- nodePort 是节点上监听的端口
- port 是 ClusterIP 上监听的端口
- targetPort 是 Pod 监听的端口
最终,Node 和 ClusterIP 在各自端口上接收到的请求都会通过 iptables 转发到 Pod 的 targetPort。
LoadBalancer
LoadBalancer和NodePort其实是同一种方式。区别在于LoadBalancer比NodePort多了一步,就是利用云平台的LB来向节点导流,把<NodeIP>:<NodePort>自动添加到公有云的负载均衡当中。
ExternalName
这种类型的 Service通过返回 CNAME和它的值,可以将服务映射到 externalName字段的内容。类型为 ExternalName 的service将服务映射到 DNS 名称,而不是典型的选择器。
一般使用在两个场景:
1.用于将集群外部的服务引入到集群内部,在集群内部可直接访问来获取服务。
例如,在k8s集群中需要保留使用aws的rds:
kind: Service apiVersion: v1 metadata: name: test-service namespace: default spec: type: ExternalName externalName: test.database.example.com
Web Pod尝试访问test-service上的数据库之后,Kubernetes DNS服务器将返回值为test.database.example.com的CNAME记录。
2.ExternalName service 也可以用于从其他namespace访问服务
例如:
kind: Service apiVersion: v1 metadata: name: test-service-1 namespace: namespace-a spec: type: ExternalName externalName: test-service-2.namespace-b.svc.cluster.local ports: - port: 80
在这里,就可以使用名称空间a中定义的test-service-1访问命名空间b中的服务test-service-2。
https://www.cnblogs.com/benjamin77/p/9908547.html