kubernetes service如何通过iptables转发

前言

本文主要是介绍kubernetes的service是如何利用iptables来进行流量的转发达到流量的负载均衡的,并会通过实践操作来更好的理解与验证其原理。

环境搭建

搭建环境如下图所示:

  1. 创建一个deploy,设置其副本数为3
apiVersion: apps/v1
kind: Deployment
metadata:
  name: demoapp-deployment
  labels:
    app: demoapp
spec:
  replicas: 3
  selector:
    matchLabels:
      app: demoapp
  template:
    metadata:
      labels:
        app: demoapp
    spec:
      containers:
        - name: ubuntu
          image: ikubernetes/demoapp:v1.0
  1. 创建service,为上面deployment的3个pod进行负载均衡访问
apiVersion: v1
kind: Service
metadata:
  name: demoapp-service
spec:
  selector:
    app: demoapp
  clusterIP: 192.44.140.73
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
  1. 验证

创建完后,环境如下:

# kubectl get pods -n zwf -o wide
NAME                                  READY   STATUS    RESTARTS   AGE     IP
demoapp-deployment-64bdcb8666-4prbk   1/1     Running   0          7d17h   192.33.229.12   dmoc-fa163eee1e30
demoapp-deployment-64bdcb8666-cxmmz   1/1     Running   0          7d17h   192.33.73.139   dmoc-fa163e7188d6
demoapp-deployment-64bdcb8666-kkzsg   1/1     Running   0          7d17h   192.33.206.93   dmoc-fa163ee6bbe1
demoapp-pod                           1/1     Running   0          4d17h   192.33.73.172   dmoc-fa163e7188d6

# kubectl get svc -n zwf
NAME                       TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demoapp-service            ClusterIP   192.44.140.73    <none>        80/TCP         7s

可以直接在环境中请求service的clusterip地址,会随机访问到三个pod,输出的信息包含了请求IP和目的IP。

# curl 192.44.140.73
iKubernetes demoapp v1.0 !! ClientIP: 10.10.10.16, ServerName: demoapp-deployment-64bdcb8666-kkzsg, ServerIP: 192.33.206.93!

# curl 192.44.140.73
iKubernetes demoapp v1.0 !! ClientIP: 10.65.196.41, ServerName: demoapp-deployment-64bdcb8666-cxmmz, ServerIP: 192.33.73.139!

# curl 192.44.140.73
iKubernetes demoapp v1.0 !! ClientIP: 10.10.10.16, ServerName: demoapp-deployment-64bdcb8666-4prbk, ServerIP: 192.33.229.12!

KUBE-SERVICES

PREROUTING​和OUTPUT​链的nat表中,都包含了KUBE-SERVICES​子链,该子链中就包含了serivce的iptables规则的配置。

# iptables -L PREROUTING -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination       
KUBE-SERVICES  all  --  anywhere             anywhere             /* kubernetes service portals */
cali-PREROUTING  all  --  anywhere             anywhere             /* cali:6gwbT8clXdHdC1b1 */

# iptables -L OUTPUT -t nat
Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination       
KUBE-SERVICES  all  --  anywhere             anywhere             /* kubernetes service portals */
cali-OUTPUT  all  --  anywhere             anywhere             /* cali:tVnHkvAo15HuiPy0 */

为什么在PREROUTING和OUTPUT链中添加service规则?

service的作用是负载均衡,也就是需要将IP包进行DNAT操作,而这个操作需要在最靠近发送的位置。网卡收到的包进入到网络协议栈时,最先进入的就是PREROUTING链,也就是节点外部的流量进入的入口。OUTPUT是本地进程发出的包最先进入的链。所以需要在这两条链中添加KUBE-SERVICES,详情可参考快速了解iptables

为什么nat表?

因为要做DNAT操作,所以在nat表。

KUBE-SERVICES下面有许多KUBE-SVC-XXX的子链,每一条子链都是一个集群中一个service的配置,筛选一下

通过筛选demoapp-service​得到下面的子链,只有访问目的地址为192.44.140.73才能进入到该子链中。

# iptables -L KUBE-SERVICES -t nat | grep demoapp-service
KUBE-SVC-FIHIHDNRZEG5VQEQ  tcp  --  anywhere             192.44.140.73        /* zwf/demoapp-service:http cluster IP */ tcp dpt:http

查看UBE-SVC-FIHIHDNRZEG5VQEQ​链中内容,其中包含了四条子链。

# iptables -L KUBE-SVC-FIHIHDNRZEG5VQEQ -t nat -n
Chain KUBE-SVC-FIHIHDNRZEG5VQEQ (1 references)
target     prot opt source               destination       
KUBE-MARK-MASQ  tcp  -- !192.33.0.0/16        192.44.140.73        /* zwf/demoapp-service:http cluster IP */ tcp dpt:80
KUBE-SEP-FO7YK2K5DAKTCVSE  all  --  0.0.0.0/0            0.0.0.0/0            /* zwf/demoapp-service:http */ statistic mode random probability 0.33333333349
KUBE-SEP-IPKL7NSNTVGKI4T5  all  --  0.0.0.0/0            0.0.0.0/0            /* zwf/demoapp-service:http */ statistic mode random probability 0.50000000000
KUBE-SEP-BPER562ISF3UFYOQ  all  --  0.0.0.0/0            0.0.0.0/0            /* zwf/demoapp-service:http */

第一条链是,源IP不在192.33.0.0/16段、目的IP为192.44.140.73并且目的端口是80的数据包会匹配到该条子链进行跳转。其中192.33.0.0/16是pod的网段,如果不是集群内的流量,则会匹配上该条子链,会打上0x4000的标记。

# iptables -L KUBE-MARK-MASQ -t nat -n
Chain KUBE-MARK-MASQ (548 references)
target     prot opt source               destination       
MARK       all  --  0.0.0.0/0            0.0.0.0/0            MARK or 0x4000

后面的三条链是为了将流量随机的分配到任意一个pod中。第二条链代表着有1/3的概率会匹配到该链,而第三条链代表着1/2,最后一条链代表着百分百。

看第二条链中的规则,第一条规则是跳转到子链KUBE-MARK-MASQ​,源IP为192.33.206.93的数据包打上了0x4000标记,再将目的IP DNAT成192.33.206.93,说明如果自己访问自己,也会被打上标记。

这里的192.33.206.93是本身service所匹配上的pod IP地址,如果调用自己的service则会匹配上该链,打上0x4000标记。

而第二条规则是一个DNAT操作,会将目标地址改为192.33.206.93:80,也就是pod的地址。

 # iptables -L KUBE-SEP-FO7YK2K5DAKTCVSE -t nat
Chain KUBE-SEP-FO7YK2K5DAKTCVSE (1 references)
target     prot opt source               destination   
KUBE-MARK-MASQ  all  --  192.33.206.93        anywhere             /* zwf/demoapp-service:http */
DNAT       tcp  --  anywhere             anywhere             /* zwf/demoapp-service:http */ tcp to:192.33.206.93:80

剩下的第3、4条规则也是一样的操作,会将流量DNAT到对应的pod中。

POSTROUTING

POSTROUTING中包含了一条KUBE-POSTROUTING子链,子链也是由kube-porxy来写入的。

# iptables -L POSTROUTING -t nat
Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination       
KUBE-POSTROUTING  all  --  anywhere             anywhere             /* kubernetes postrouting rules */
cali-POSTROUTING  all  --  anywhere             anywhere             /* cali:O3lYWMrLQYEMJtB5 */

查看子链KUBE-POSTROUTING​内容:

  1. 第一条规则是没有标记为0x400的数据包会return,不执行下面的子链。还记得在KUBE-PREROUTING​中有两次打上0x4000标记,代表着访问是集群外部访问该service或者是源和目的一样,都会往下走。
  2. 第二条规则是对数据包的标记值进行异或,而这里是0x4000与0x4000进行异或,也就是0
  3. 第三条规则时对该数据包进行SNAT,random-fully是对源端口进行随机获取。
# iptables -L KUBE-POSTROUTING -t nat
Chain KUBE-POSTROUTING (1 references)
target     prot opt source               destination       
RETURN     all  --  anywhere             anywhere             mark match ! 0x4000/0x4000
MARK       all  --  anywhere             anywhere             MARK xor 0x4000
MASQUERADE  all  --  anywhere             anywhere             /* kubernetes service traffic requiring SNAT */ random-fully

这里有两种情况会被SNAT,说说为什么。

SNAT

为什么集群外部访问该service需要被SNAT?

数据包需要回包,如果没有SNAT,那么回包的目的IP是客户端的IP,而源IP是Pod的IP,而客户端并不认该数据包,则会被丢弃。


如果有发生SNAT,那么回包的时候可以回到Node1的节点,再回到client。

但问题又来了,是怎么从Node1再回包到client的呢?

conntrack

conntrack是一个追踪表,在每一个数据包进入到netfilter的时候,都会记录一条记录,当我们进行SNAT和DNAT的时候也都会更新该表中的记录。

所以在Node1回包给client时候,可以通过查询这个表中的记录,然后再次SNAT和DNAT恢复回去就可以进行正常的回包了。

可以看下conntrack里面的记录长什么样子。

先在某一台节点上curl service的ip,上图的client其实也是在Node1上的,因为Node1上有两张网卡,Node1和Node2的通信使用的网卡eth2 ip为10.10.10.16,因为转发到其他节点,所以会被SNAT。

# curl 192.44.140.73
iKubernetes demoapp v1.0 !! ClientIP: 10.10.10.16, ServerName: demoapp-deployment-64bdcb8666-4prbk, ServerIP: 192.33.229.12!

这里主要是看src和dst分别是client请求service的目的IP,然后后面又有一个src和dst它是由Node2回包给Node1的源和目的IP,也就是源是pod的IP,dst是Node1节点IP。

前面是发送包的方向,后面是回包的方向。通过查询该表,即可以从Node1回包给client

# conntrack -L | grep 192.33.229.12
tcp      6 107 TIME_WAIT src=10.65.196.41 dst=192.44.140.73 sport=33468 dport=80 src=192.33.229.12 dst=10.10.10.16 sport=80 dport=18623 [ASSURED] mark=0 use=1

源和目的IP相同

当pod访问service时,正好DNAT成自己的IP,那么就存在源和目的IP一样的情况。

这种数据包是很特殊的存在,网络设备可能会将这种数据包视为无效或者异常的包被丢弃,最好不要一样,所以会将源IP SNAT为主机的IP,解决该问题。

实验

当前节点的IP为:10.65.196.41,然后进入在当前节点的Pod内,当前pod的ip为:192.33.73.139

kubectl exec -it -n zwf demoapp-deployment-64bdcb8666-cxmmz -- sh

进行多次curl service ip,当某一次请求到本POD的IP时,查看其ClientIP,发现其源IP是:10.65.196.41,这个当前节点的IP

# curl 192.44.140.73
iKubernetes demoapp v1.0 !! ClientIP: 10.65.196.41, ServerName: demoapp-deployment-64bdcb8666-cxmmz, ServerIP: 192.33.73.139!

NodePort

上面介绍的是serivi type=ClusterIP方式的,那么NodePort有什么区别呢?

搭建环境

先将clusterIP的service删了,防止干扰。

kubectl delete svc -n zwf demoapp-service

创建NodePort的service文件demo_service_nodeport.yaml

apiVersion: v1
kind: Service
metadata:
  name: demoapp-nodeport-service
spec:
  selector:
    app: demoapp
  ports:
    - name: http
      protocol: TCP
      port: 80
      targetPort: 80
  type: NodePort

创建Service

# kubectl apply -n zwf -f demo_service_nodeport.yaml 

# kubectl get svc -n zwf
NAME                       TYPE       CLUSTER-IP       EXTERNAL-IP   PORT(S)        AGE
demoapp-nodeport-service   NodePort   192.44.152.223   <none>        80:30337/TCP   64s

请求clusterip

通过clusterip+端口的方式请求service,原理和上面将是一样的。

在POSTROUTING和OUTPUT中添加了KUBE-SERVICES链,其中包含了新增的service对应的KUBE-SVC-XXX子链,当访问的是其clusterip时,则匹配上该链。在其中包含了从多个pod中随机选择一个进行DNAT操作,打上标记,在POSTROUTING中再SNAT。

# iptables -L PREROUTING -t nat
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination       
KUBE-SERVICES  all  --  anywhere             anywhere             /* kubernetes service portals */
cali-PREROUTING  all  --  anywhere             anywhere             /* cali:6gwbT8clXdHdC1b1 */


# iptables -L KUBE-SERVICES -t nat | grep zwf
KUBE-SVC-YSWRJGOK5VEB6HOG  tcp  --  anywhere             192.44.152.223       /* zwf/demoapp-nodeport-service:http cluster IP */ tcp dpt:http

# iptables -L KUBE-SVC-YSWRJGOK5VEB6HOG -t nat |grep zwf
KUBE-MARK-MASQ  tcp  -- !192.33.0.0/16        192.44.152.223       /* zwf/demoapp-nodeport-service:http cluster IP */ tcp dpt:http
KUBE-MARK-MASQ  tcp  --  anywhere             anywhere             /* zwf/demoapp-nodeport-service:http */ tcp dpt:30337
KUBE-SEP-NMMBRSZXI4OIWDPB  all  --  anywhere             anywhere             /* zwf/demoapp-nodeport-service:http */ statistic mode random probability 0.33333333349
KUBE-SEP-PXDORKZI3GD2RUMC  all  --  anywhere             anywhere             /* zwf/demoapp-nodeport-service:http */ statistic mode random probability 0.50000000000
KUBE-SEP-YLPOR67MTHPHIZAB  all  --  anywhere             anywhere             /* zwf/demoapp-nodeport-service:http */

请求节点IP

但是如果不是通过ClusterIP而是通过节点IP请求service呢,它是怎么做的?

因为是节点IP,所以无法匹配上该service的KUBE-SVC-XXX子链,但是有一条KUBE-NODEPORTS

# iptables -L KUBE-SERVICES  -t nat | grep KUBE-NODEPORTS
KUBE-NODEPORTS  all  --  anywhere             anywhere             /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL

在其中包含了一条子链KUBE-SVC-YSWRJGOK5VEB6HOG​,只要目的端口为30337就能匹配上,而这个端口正好就是demoapp-nodeport-service​的NodePort端口。

这条子链和上面匹配clusterIP时是同一个链,都是进行一个DNAT操作。

# # iptables -L KUBE-NODEPORTS -t nat | grep zwf
KUBE-SVC-YSWRJGOK5VEB6HOG  tcp  --  anywhere             anywhere             /* zwf/demoapp-nodeport-service:http */ tcp dpt:30337

参考链接

欢迎关注,互相学习,共同进步~

我的个人博客
公众号:编程黑洞

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值