kube-proxy默认使用iptables规则来做k8s集群内部的负载均衡,本文通过例子来分析创建的iptabels规则。
主要的自定义链涉及到:
KUBE-SERVICES: 访问集群内服务的CLusterIP数据包入口,根据匹配到的目标ip+port将数据包分发到相应的KUBE-SVC-xxx链上。一个Service对应一条规则。由OUTPUT链调用。
KUBE-NODEPORTS: 用来匹配nodeport端口号,并将规则转发到KUBE-SVC-xxx。一个NodePort类型的Service一条。在KUBE-SERVICES链的最后被调用
KUBE-SVC-xxx:相当于是负载均衡,将流量利用random模块均分到KUBE-SEP-xxx链上。
KUBE-SEP-xxx:通过dnat规则将连接的目的地址和端口号做dnat,从Service的ClusterIP或者NodePort转换为后端的pod ip
KUBE-MARK-MASQ: 使用mark命令,对数据包设置标记0x4000/0x4000。在KUBE-POSTROUTING链上有MARK标记的数据包进行一次MASQUERADE,即SNAT,会用节点ip替换源ip地址。
环境准备 创建nginx deployment
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx-svc version: nginx name: nginx namespace: default spec: replicas: 2 selector: matchLabels: app: nginx-svc template: metadata: labels: app: nginx-svc version: nginx spec: containers: - image: 'nginx:1.9.0' name: nginx ports: - containerPort: 443 protocol: TCP - containerPort: 80 protocol: TCP
创建service对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 apiVersion: v1 kind: Service metadata: name: nginx-svc namespace: default spec: ports: - name: '80' port: 8000 protocol: TCP targetPort: 80 nodePort: 30080 selector: app: nginx-svc sessionAffinity: None type: NodePort
环境信息如下:
容器网段:172.20.0.0/16
Service ClusterIP cidr: 192.168.0.0/20
k8s版本:
提交后创建出来的信息如下:
Service ClusterIP:192.168.103.148
nginx pod的两个ip地址:172.16.3.3 172.16.4.4
从宿主机上访问ClusterIP 从本机请求ClusterIP的数据包会经过iptables的链:OUTPUT -> POSTROUTING
要想详细知道iptabels的执行情况,可以通过iptables的trace功能。如何开启trace功能可以参考:http://kuring.me/post/iptables/。
执行 iptables -nvL OUTPUT -t nat
可以看到如下的iptables规则命令
1 2 pkts bytes target prot opt in out source destination 17M 1150M KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
执行 iptables -nvL KUBE-SERVICES -t nat
可以查看自定义链的具体内容,里面包含了多条规则,其中跟当前Service相关的规则如下。
1 2 pkts bytes target prot opt in out source destination 1 60 KUBE-SVC-Y5VDFIEGM3DY2PZE tcp -- * * 0.0.0.0/0 192.168.103.148 /* default/nginx-svc:80 cluster IP */ tcp dpt:8000
执行 iptables -nvL KUBE-SVC-Y5VDFIEGM3DY2PZE -t nat
查看自定义链的具体规则
1 2 3 pkts bytes target prot opt in out source destination 0 0 KUBE-SEP-IFV44I3EMZAL3LH3 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ statistic mode random probability 0.50000000000 1 60 KUBE-SEP-6PNQETFAD2JPG53P all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */
上述规则会按照特定的概率将流量均等的执行自定义链的规则,两个自定义的链的规则跟endpoint相关,执行 iptables -nvL KUBE-SEP-IFV44I3EMZAL3LH3 -t nat
可查看endpoint级别的iptabels规则。dnat操作会修改数据包的目的地址和端口,从clusterip+service port修改为访问pod ip+pod端口。
1 2 3 pkts bytes target prot opt in out source destination 0 0 KUBE-MARK-MASQ all -- * * 172.16.3.3 0.0.0.0/0 /* default/nginx-svc:80 */ 0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ tcp to:172.16.3.3:80
会在dnat操作之前为对数据包执行打标签操作。KUBE-MARK-MASQ 自定义链为对数据包打标记的自定义规则,执行 iptables -nvL KUBE-MARK-MASQ -t nat
1 2 pkts bytes target prot opt in out source destination 1 60 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000
接下来看一下POSTROUTING链上的规则,iptables -nvL POSTROUTING -t nat
。
1 2 pkts bytes target prot opt in out source destination 205K 13M KUBE-POSTROUTING all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes postrouting rules */
继续看一下KUBE-POSTROUTING链的内容,iptables -nvL KUBE-POSTROUTING -t nat
,其中最后一条的MASQUERADE指令的操作实际上为SNAT操作。
1 2 3 4 5 Chain KUBE-POSTROUTING (1 references) pkts bytes target prot opt in out source destination 6499 398K RETURN all -- * * 0.0.0.0/0 0.0.0.0/0 mark match ! 0x4000/0x4000 1 60 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK xor 0x4000 1 60 MASQUERADE all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service traffic requiring SNAT */
即从本机访问service clusterip的数据包,在output链上经过了dnat操作,在postrouting链上经过了snat操作后,最终会发往目标pod。pod在处理完请求后,回的数据包最终会经过nat的逆过程返回到本机。
外部访问nodeport 从外部访问本机的nodeport数据包会经过iptables的链:PREROUTING -> FORWARD -> POSTROUTING
nodeport都是被外部访问的情况,入口位于PREROUTING链上。执行 iptables -nvL PREROUTING -t nat
:
1 2 pkts bytes target prot opt in out source destination 349K 21M KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service portals */
在KUBE-SERVICES链的最后一条规则为跳转到KUBE-NODEPORTS链
1 4079 246K KUBE-NODEPORTS all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes service nodeports; NOTE: this must be the last rule in this chain */ ADDRTYPE match dst-type LOCAL
执行iptables -nvL KUBE-NODEPORTS -t nat
, 查看KUBE-NODEPORTS链
1 2 3 pkts bytes target prot opt in out source destination 0 0 KUBE-MARK-MASQ tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ tcp dpt:30080 0 0 KUBE-SVC-Y5VDFIEGM3DY2PZE tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ tcp dpt:30080
其中KUBE-MARK-MASQ链只有一条规则,即打上0x4000的标签。
1 2 pkts bytes target prot opt in out source destination 0 0 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000
自定义链KUBE-SVC-Y5VDFIEGM3DY2PZE的内容如下,跟clusterip的规则是重叠的:
1 2 3 pkts bytes target prot opt in out source destination 0 0 KUBE-SEP-IFV44I3EMZAL3LH3 all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ statistic mode random probability 0.50000000000 0 0 KUBE-SEP-6PNQETFAD2JPG53P all -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */
KUBE-SEP-IFV44I3EMZAL3LH3的内容为,会经过一次DNAT操作:
1 2 3 pkts bytes target prot opt in out source destination 0 0 KUBE-MARK-MASQ all -- * * 172.16.3.3 0.0.0.0/0 /* default/nginx-svc:80 */ 0 0 DNAT tcp -- * * 0.0.0.0/0 0.0.0.0/0 /* default/nginx-svc:80 */ tcp to:172.16.3.3:80
在经过了PREROUTING链后,接下来会判断目的ip地址不是本机的ip地址,接下来会经过FORWARD链。在FORWARD链上,仅做了一件事情,就是将前面大了0x4000的数据包允许转发。
1 2 3 4 pkts bytes target prot opt in out source destination 0 0 KUBE-FORWARD all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */ 0 0 KUBE-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes service portals */ 0 0 KUBE-EXTERNAL-SERVICES all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate NEW /* kubernetes externally-visible service portals */
KUBE-FORWARD的内容如下:
1 2 3 4 5 pkts bytes target prot opt in out source destination 0 0 DROP all -- * * 0.0.0.0/0 0.0.0.0/0 ctstate INVALID 0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding rules */ mark match 0x4000/0x4000 0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding conntrack pod source rule */ ctstate RELATED,ESTABLISHED 0 0 ACCEPT all -- * * 0.0.0.0/0 0.0.0.0/0 /* kubernetes forwarding conntrack pod destination rule */ ctstate RELATED,ESTABLISHED
跟clusterip一样,会在POSTROUTING阶段匹配mark为0x4000/0x4000的数据包,并进行一次MASQUERADE转换,将ip包替换为宿主上的ip地址。
加入这里不做MASQUERADE,流量发到目的的pod后,pod回包时目的地址为发起端的源地址,而发起端的源地址很可能是在k8s集群外部的,此时pod发回的包是不能回到发起端的。NodePort跟ClusterIP的最大不同就是NodePort的发起端很可能是在集群外部的,从而这里必须做一层SNAT转换。
在上述分析中,访问NodePort类型的Service会经过snat,从而服务端的pod不能获取到正确的客户端ip。可以设置Service的spec.externalTrafficPolicy为Local,此时iptables规则只会将ip包转发给运行在这台宿主机上的pod,而不需要经过snat。pod回包时,直接回复源ip地址即可,此时源ip地址是可达的,因为源ip地址跟宿主机是可达的。如果所在的宿主机上没有pod,那么此时流量就不可以转发,此为限制。
使用LoadBalancer类型访问的情况 externalTrafficPolicy为local 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 -A KUBE-SERVICES -d 10.149.30.186/32 -p tcp -m comment --comment "acs-system/nginx-ingress-lb-cloudbiz:http loadbalancer IP" -m tcp --dport 80 -j KUBE-FW-76HLDRT5IPNSMPF5 -A KUBE-FW-76HLDRT5IPNSMPF5 -m comment --comment "acs-system/nginx-ingress-lb-cloudbiz:http loadbalancer IP" -j KUBE-XLB-76HLDRT5IPNSMPF5 -A KUBE-FW-76HLDRT5IPNSMPF5 -m comment --comment "acs-system/nginx-ingress-lb-cloudbiz:http loadbalancer IP" -j KUBE-MARK-DROP # 10.149.112.0/23为pod网段 -A KUBE-XLB-76HLDRT5IPNSMPF5 -s 10.149.112.0/23 -m comment --comment "Redirect pods trying to reach external loadbalancer VIP to clusterIP" -j KUBE-SVC-76HLDRT5IPNSMPF5 -A KUBE-XLB-76HLDRT5IPNSMPF5 -m comment --comment "Balancing rule 0 for acs-system/nginx-ingress-lb-cloudbiz:http" -j KUBE-SEP-XZXLBWOKJBSJBGVU -A KUBE-SVC-76HLDRT5IPNSMPF5 -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-XZXLBWOKJBSJBGVU -A KUBE-SVC-76HLDRT5IPNSMPF5 -j KUBE-SEP-GP4UCOZEF3X7PGLR -A KUBE-SEP-XZXLBWOKJBSJBGVU -s 10.149.112.45/32 -j KUBE-MARK-MASQ -A KUBE-SEP-XZXLBWOKJBSJBGVU -p tcp -m tcp -j DNAT --to-destination 10.149.112.45:80 -A KUBE-SEP-GP4UCOZEF3X7PGLR -s 10.149.112.46/32 -j KUBE-MARK-MASQ -A KUBE-SEP-GP4UCOZEF3X7PGLR -p tcp -m tcp -j DNAT --to-destination 10.149.112.46:80
缺点
iptables规则特别乱,一旦出现问题非常难以排查
由于iptables规则是串行执行,算法复杂度为O(n),一旦iptables规则多了后,性能将非常差。
iptables规则提供的负载均衡功能非常有限,不支持较为复杂的负载均衡算法。