actor168
作者actor1682021-09-01 15:05
研发工程师, 中国联通软件研究院

K8S Service实战与原理初探

字数 8459阅读 1875评论 2赞 2

Service

- 本文作者: 陈成,供职于中国联通软件研究院

本文主要讲述了 K8S Service 的基本概念,使用方式及实现原理。

故事的开始,让我们先从一件生产故障说起。 5 月 29 日,内部某系统出现大规模访问 Service 故障,发现 Pod 容器内无法正常访问 ServiceIP:Port ,整个故障持续时间超过 12h ,相关运维支撑人员没有找到根本原因和解决办法。

经过复盘,我们发现,大家对于 K8S Service 的原理不够清晰,导致对问题的定位不能做得到快速准确,如果当时能够按照如下的思路去思考问题,排查过程不至于花费如此久的时间。

下面,我们就来细说一下 Service 在 Kubernetes 中的作用、使用方法及原理。

Service是一种暴露一组Pod网络的抽象方式,K8S Service提供了针对于一组Pod的负载均衡的暴露。通过这样的方式,可以避免不同的pod之间访问时需要知晓对应pod网络信息的痛苦。例如:前端->后端,由于前端POD IP随时变动,后端亦如此,如何处理前端POD和后端POD的通信,就需要Service这一抽象,来保证简单可靠。

Service的使用

  1. 典型服务配置方法

    当配置了 selector 之后, Service Controller 会自动查找匹配这个 selector 的 pod ,并且创建出一个同名的 endpoint 对象,负责具体 service 之后连接。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376

2. 配置没有 selector 的服务

没有 selector 的 service 不会出现 Endpoint 的信息,需要手工创建 Endpoint 绑定, Endpoint 可以是内部的 pod ,也可以是外部的服务。

apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  ports:
    - protocol: TCP
      port: 80
      targetPort: 9376
---
apiVersion: v1
kind: Endpoints
metadata:
  name: my-service
subsets:
  - addresses:
      - ip: 192.0.2.42
    ports:
      - port: 9376

 Service的类型

 CluserIP

 kubectl expose pod nginx --type=CluserIP --port=80 --name=ng-svc
apiVersion: v1
kind: Service
metadata:
  name: ng-svc
  namespace: default
spec:
  selector:
      name: nginx
  clusterIP: 11.254.0.2
  ports:
  - name: http
    port: 80
    protocol: TCP
    targetPort: 1234
  sessionAffinity: None
  type: ClusterIP

 LoadBalance

apiVersion: v1

kind: Service

metadata:

  name: my-service

spec:

  selector:

    app: MyApp

  ports:

  - protocol: TCP

    port: 80

    targetPort: 9376

  clusterIP: 10.0.171.239

  type: LoadBalancer

status:

  loadBalancer:

    ingress:

    - ip: 192.0.2.127
apiVersion: v1
kind: Service
metadata:
  name: my-service
spec:
  selector:
    app: MyApp
  ports:
  - protocol: TCP
    port: 80
    targetPort: 9376
  clusterIP: 10.0.171.239
  type: LoadBalancer
status:
  loadBalancer:
    ingress:
    - ip: 192.0.2.127

 NodePort

apiVersion: v1
kind: Service
metadata:
name: my-service
spec:
type: NodePort
selector:
  app: MyApp
ports:
  - port: 80
    targetPort: 80
    nodePort: 30007

ExternalName

 Headless

apiVersion: v1
kind: Service
metadata:
  labels:
    run: curl
  name: my-headless-service
  namespace: default
spec:
  clusterIP: None
  ports:
  - port: 80
    protocol: TCP
    targetPort: 80
  selector:
    run: curl
  type: ClusterIP

对定义了选择算符的无头服务, Endpoint 控制器在 API 中创建了 Endpoints 记录, 并且修改 DNS 配置返回 A 记录( IP 地址),通过这个地址直接到达 Service 的后端 Pod 上。

ping my-headless-service

 PING my-headless-service (172.200.6.207): 56 data bytes
 64 bytes from 172.200.6.207: seq=0 ttl=64 time=0.040 ms
 64 bytes from 172.200.6.207: seq=1 ttl=64 time=0.063 ms

对没有定义选择算符的无头服务, Endpoint 控制器不会创建 Endpoints 记录。 然而 DNS 系统会查找和配置,无论是:

· 对于 ExternalName 类型的服务,查找其 CNAME 记录

· 对所有其他类型的服务,查找与 Service 名称相同的任何 Endpoints 的记录

Service的实现方式

1.用户态代理访问

即:当对于每个 Service , Kube-Proxy 会在本地 Node 上打开一个随机选择的端口 ,连接到代理端口的请求,都会被代理转发给 Pod 。那么通过 Iptables 规则,捕获到达 Service:Port 的请求都会被转发到代理端口,代理端口重新转为对 Pod 的访问

这种方式的缺点是存在内核态转为用户态,再有用户态转发的两次转换,性能较差,一般不再使用

2.Iptables 模式

3.Ipvs 模式

Service Iptables实现原理

 Iptables表和链及处理过程_

Service 的 Traffic 流量将会通过 prerouting 和 output 重定向到 kube-service 链

 -A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
 -A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
 -A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

· KUBE-SERVICES->KUBE-SVC-XXXXXXXXXXXXXXXX->KUBE-SEP-XXXXXXXXXXXXXXXX represents a ClusterIP service
· KUBE-NODEPORTS->KUBE-SVC-XXXXXXXXXXXXXXXX->KUBE-SEP-XXXXXXXXXXXXXXXX represents a NodePort service

 几种不同类型的Service在Kube-Proxy启用Iptables模式下上的表现

  • ClusterIP

    -A KUBE-SERVICES ! -s  172.200 . 0.0 / 16  -d  10.100 . 160.92 / 32 -p tcp -m comment --comment "default/ccs-gateway-clusterip:http cluster IP" -m tcp --dport 30080 -j KUBE-MARK-MASQ
    
    -A KUBE-SERVICES -d  10.100 . 160.92 / 32 -p tcp -m comment --comment "default/ccs-gateway-clusterip:http cluster IP" -m tcp --dport 30080 -j KUBE-SVC- 76 GERFBRR2RGHNBJ
    
    -A KUBE-SVC- 76 GERFBRR2RGHNBJ -m comment --comment "default/ccs-gateway-clusterip:http" -m statistic --mode random --probability 0.33333333349 -j KUBE-SEP-GBVECAZBIC3ZKMXB
    
    -A KUBE-SVC- 76 GERFBRR2RGHNBJ -m comment --comment "default/ccs-gateway-clusterip:http" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-PVCYYXEU44D3IMGK
    
    -A KUBE-SVC- 76 GERFBRR2RGHNBJ -m comment --comment "default/ccs-gateway-clusterip:http" -j KUBE-SEP-JECGZLHE32MEARRX
    
    -A KUBE-SVC-CEZPIJSAUFW5MYPQ -m comment --comment "kubernetes-dashboard/kubernetes-dashboard" -j KUBE-SEP-QO6MV4HR5U56RP7M
    
    -A KUBE-SEP-GBVECAZBIC3ZKMXB -s  172.200 . 6.224 / 32 -m comment --comment "default/ccs-gateway-clusterip:http" -j KUBE-MARK-MASQ
    
    -A KUBE-SEP-GBVECAZBIC3ZKMXB -p tcp -m comment --comment "default/ccs-gateway-clusterip:http" -m tcp -j DNAT --to-destination 172.200 . 6.224 : 80

* NodePort

apiVersion: v1
kind: Service
metadata:
labels:
  app: ccs-gateway
spec:
clusterIP: 10.101.156.39
externalTrafficPolicy: Cluster
ports:
- name: http
  nodePort: 30081
  port: 30080
  protocol: TCP
  targetPort: 80
selector:
  app: ccs-gateway
sessionAffinity: None
type: NodePort
 -A KUBE-NODEPORTS -p tcp -m comment --comment "default/ccs-gateway-service:http" -m tcp --dport 30081 -j KUBE-MARK-MASQ

 -A KUBE-NODEPORTS -p tcp -m comment --comment "default/ccs-gateway-service:http" -m tcp --dport 30081 -j KUBE-SVC-QYHRFFHL5VINYT2K

 ############################

 -A KUBE-SVC-QYHRFFHL5VINYT2K -m comment --comment "default/ccs-gateway-service:http" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-2NPKETIWKKVUXGCL

 -A KUBE-SVC-QYHRFFHL5VINYT2K -m comment --comment "default/ccs-gateway-service:http" -j KUBE-SEP-6O5FHQRN5IVNPW4Q

 ##########################

 -A KUBE-SEP-2NPKETIWKKVUXGCL -s 172.200.6.224/32 -m comment --comment "default/ccs-gateway-service:http" -j KUBE-MARK-MASQ

 -A KUBE-SEP-2NPKETIWKKVUXGCL -p tcp -m comment --comment "default/ccs-gateway-service:http" -m tcp -j DNAT --to-destination 172.200.6.224:80

 #########################

 -A KUBE-SEP-6O5FHQRN5IVNPW4Q -s 172.200.6.225/32 -m comment --comment "default/ccs-gateway-service:http" -j KUBE-MARK-MASQ

 -A KUBE-SEP-6O5FHQRN5IVNPW4Q -p tcp -m comment --comment "default/ccs-gateway-service:http" -m tcp -j DNAT --to-destination 172.200.6.225:80

同时,可以看到 Service 所申请的端口 38081 被 Kube-proxy 所代理和监听

 # netstat -ntlp | grep 30081

 tcp 0  0  0 . 0 . 0 . 0 : 30081  0 . 0 . 0 . 0 :*  LISTEN  3665705 /kube-proxy

* LoadBalancer

不带有 Endpoint 的 Service

 kubectl **create** svc clusterip fake-endpoint --tcp=80
####
 -A KUBE-SERVICES -d 10.101 .117.0/ 32 -p tcp -m comment --comment "default/fake-endpoint:80 has no endpoints" -m tcp --dport 80 -j REJECT --reject- **with** icmp-port-unreachable

* 带有外部 endpoint 的 Service

直接通过 iptable 规则转发到对应的外部 ep 地址

apiVersion: v1
kind: Service
metadata:
  labels:
    app: external
  name: external
  namespace: default
spec:
  ports:
  - name: http
    protocol: TCP
    port: 80
  sessionAffinity: None
  type: ClusterIP
---
apiVersion: v1
kind: Endpoints
metadata:
  labels:
    app: external
  name: external
  namespace: default
subsets:
- addresses:
  - ip: 10.124.142.43
  ports:
  - name: http
    port: 80
protocol: TCP
 -A KUBE-SERVICES ! -s  172.200 . 0.0 / 16  -d  10.111 . 246.87 / 32 -p tcp -m comment --comment "default/external:http cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ

 -A KUBE-SERVICES -d  10.111 . 246.87 / 32 -p tcp -m comment --comment "default/external:http cluster IP" -m tcp --dport 80 -j KUBE-SVC-LI2K5327B6J24KJ3

 -A KUBE-SEP-QTGIPNOYXN2CZGD5 -s  10.124 . 142.43 / 32 -m comment --comment "default/external:http" -j KUBE-MARK-MASQ

 -A KUBE-SEP-QTGIPNOYXN2CZGD5 -p tcp -m comment --comment "default/external:http" -m tcp -j DNAT --to-destination 10.124 . 142.43 : 80

总结

ClusterIP 类型, KubeProxy 监听 Service 和 Endpoint 创建规则,采用 DNAT 将目标地址转换为 Pod 的 ip 和端口,当有多个 ep 时,按照策略进行转发,默认 RR 模式时, iptables 采用:比如有 4 个实例,四条规则的概率分别为 0.25, 0.33, 0.5 和 1 ,按照顺序,一次匹配完成整个流量的分配。

NodePort 类型,将会在上述 ClusterIP 模式之后,再加上 Kube-Proxy 的监听(为了确保其他服务不会占用该端口)和 KUBE-NODEPORT 的 iptable 规则

参考文献

iptables https://en.wikipedia.org/wiki/Iptables

ipvs https://en.wikipedia.org/wiki/IP_Virtual_Server

K8S Service https://kubernetes.io/zh/docs/concepts/services-networking/service/

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

2

添加新评论2 条评论

LearLear系统工程师, Karltec
2021-10-26 22:03
好好学习
hufeng719hufeng719系统工程师, 某钢铁企业
2021-09-06 11:58
没看懂。应该结合着案例分析。开头有了案例中间分析过程和结果都没有提到案例。不完美。

aixchina@hufeng719 写个系列,一篇肯定讲不完。

2021-09-06 12:00
Ctrl+Enter 发表

本文隶属于专栏

最佳实践
不同的领域,都有先行者,实践者,用他们的最佳实践来加速更多企业的建设项目落地。

容器云管理平台选型优先顺序调查

发表您的选型观点,参与即得50金币。