来源:https://www.cnblogs.com/charlieroro

910d36848d3c4bb7cb6ddebe442ec230.png

node 节点的 iptables 是由 kube-proxy 生成的,具体实现可以参见 kube-proxy 的代码(http://dwz.date/cqfm) kube-proxy 只修改了 filter 和 nat 表,它对 iptables 的链进行了扩充,自定义了KUBE-SERVICES,KUBE-NODEPORTS,KUBE-POSTROUTING,KUBE-MARK-MASQ 和 KUBE-MARK-DROP 五个链,并主要通过为 KUBE-SERVICES 链(附着在PREROUTING 和 OUTPUT)增加 rule 来配制 traffic routing 规则,官方定义如下:
// the services chain
kubeServicesChain utiliptables.Chain = "KUBE-SERVICES"
 
// the external services chain
kubeExternalServicesChain utiliptables.Chain = "KUBE-EXTERNAL-SERVICES"
 
// the nodeports chain
kubeNodePortsChain utiliptables.Chain = "KUBE-NODEPORTS"
 
// the kubernetes postrouting chain
kubePostroutingChain utiliptables.Chain = "KUBE-POSTROUTING"
 
// the mark-for-masquerade chain
KubeMarkMasqChain utiliptables.Chain = "KUBE-MARK-MASQ"    /*对于未能匹配到跳转规则的traffic set mark 0x8000,有此标记的数据包会在filter表drop掉*/
 
// the mark-for-drop chain
KubeMarkDropChain utiliptables.Chain = "KUBE-MARK-DROP"    /*对于符合条件的包 set mark 0x4000, 有此标记的数据包会在KUBE-POSTROUTING chain中统一做MASQUERADE*/
 
// the kubernetes forward chain
kubeForwardChain utiliptables.Chain = "KUBE-FORWARD"
KUBE-MARK-MASQ 和 KUBE-MARK-DROP
 这两个规则主要用来对经过的报文打标签,打上标签的报文可能会做相应处理,打标签处理如下:
(注:iptables set mark 的用法可以参见 https://unix.stackexchange.com/questions/282993/how-to-add-marks-together-in-iptables-targets-mark-and-connmark
http://ipset.netfilter.org/iptables-extensions.man.html )
-A KUBE-MARK-DROP -j MARK --set-xmark 0x8000/0x8000
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
KUBE-MARK-DROP 和 KUBE-MARK-MASQ 本质上就是使用了 iptables 的 MARK命令
Chain KUBE-MARK-DROP (6 references)
 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 0x8000
Chain KUBE-MARK-MASQ (89 references)
 pkts bytes target prot opt in     out     source               destination
   88  5280 MARK all -- * * 0.0.0.0/0 0.0.0.0/0 MARK or 0x4000
对于 KUBE-MARK-MASQ 链中所有规则设置了 kubernetes 独有 MARK 标记,在KUBE-POSTROUTING 链中对 NODE 节点上匹配 kubernetes 独有 MARK 标记的数据包,当报文离开 node 节点时进行 SNAT,MASQUERADE 源 IP
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
而对于 KUBE-MARK-DROP 设置标记的报文则会在 KUBE_FIREWALL 中全部丢弃  
-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP
KUBE_SVC 和 KUBE-SEP Kube-proxy 接着对每个服务创建 “KUBE-SVC-” 链,并在 nat 表中将 KUBE-SERVICES 链中每个目标地址是 service 的数据包导入这个 “KUBE-SVC-” 链,如果endpoint 尚未创建,KUBE-SVC- 链中没有规则,任何 incomming packets 在规则匹配失败后会被 KUBE-MARK-DROP。在 iptables 的 filter 中有如下处理,如果KUBE-SVC 处理失败会通过 KUBE_FIREWALL 丢弃
Chain INPUT (policy ACCEPT 209 packets, 378K bytes)
 pkts bytes target prot opt in out source destination
 540K 1370M KUBE-SERVICES all -- * * 0.0.0.0/0            0.0.0.0/0            /* kubernetes service portals */
 540K 1370M KUBE-FIREWALL all -- * * 0.0.0.0/0            0.0.0.0/0
KUBE_FIREWALL 内容如下,就是直接丢弃所有报文:
Chain KUBE-FIREWALL (2 references)
 pkts bytes target prot opt in out source               destination
    0     0 DROP all  -- * * 0.0.0.0/0            0.0.0.0/0            /* kubernetes firewall for dropping marked packets */ mark match 0x8000/0x8000
下面是对 nexus 的 service 的处理,可以看到该规对目的 IP 为172.21.12.49(Cluster IP) 且目的端口为 8080 的报文作了特殊处理:KUBE-SVC-HVYO5BWEF5HC7MD7
-A KUBE-SERVICES -d 172.21.12.49/32 -p tcp -m comment --comment "default/sonatype-nexus: cluster IP" -m tcp --dport 8080 -j KUBE-SVC-HVYO5BWEF5HC7MD7
KUBE-SEP 表示的是 KUBE-SVC 对应的 endpoint,当接收到的 serviceInfo 中包含endpoint 信息时,为 endpoint 创建跳转规则,如上述的KUBE-SVC-HVYO5BWEF5HC7MD7 有 endpoint,其 iptables 规则如下:
-A KUBE-SVC-HVYO5BWEF5HC7MD7 -m comment --comment "oqton-backoffice/sonatype-nexus:" -j KUBE-SEP-ESZGVIJJ5GN2KKU
KUBE-SEP-ESZGVIJJ5GN2KKU中的处理为将经过该链的所有 tcp 报文,DNAT 为container 内部暴露的访问方式172.20.5.141:8080。结合对KUBE-SVC 的处理可可知,这种访问方式就是 cluster IP 的访问方式,即将目的 IP 是 cluster IP 且目的端口是 service 暴露的端口的报文 DNAT 为目的 IP 是 container 且目的端口是container 暴露的端口的报文,
-A KUBE-SEP-ESZGVIJJ5GN2KKUR -p tcp -m comment --comment "oqton-backoffice/sonatype-nexus:" -m tcp -j DNAT --to-destination 172.20.5.141:8080
如果 service 类型为 nodePort,(从 LB 转发至 node 的数据包均属此类)那么将KUBE-NODEPORTS 链中每个目的地址是 NODE 节点端口的数据包导入这个 “KUBE-SVC-”链;KUBE-NODEPORTS 必须位于 KUBE-SERVICE 链的最后一个,可以看到iptables 在处理报文时会优先处理目的 IP 为 cluster IP 的报文,匹配失败之后再去使用 NodePort 方式。如下规则表明,NodePort 方式下会将目的 ip 为 node 节点且端口为 node 节点暴露的端口的报文进行 KUBE-SVC-HVYO5BWEF5HC7MD7 处理,KUBE-SVC-HVYO5BWEF5HC7MD7 中会对报文进行 DNAT 转换。因此 Custer IP 和 NodePort 方式的唯一不同点就是 KUBE-SERVICE 中是根据 cluster IP 还是根据 node port 进行匹配 "-m addrtype --dst-type LOCAL"表示对目的地址是本机地址的报文执行 KUBE-NODEPORTS 链的操作
-A KUBE-SERVICES -m comment --comment "kubernetes service nodeports; NOTE: this must be the last rule in this chain" -m addrtype --dst-type LOCAL -j KUBE-NODEPORTS
-A KUBE-NODEPORTS -p tcp -m comment --comment "oqton-backoffice/sonatype-nexus:" -m tcp --dport 32257 -j KUBE-MARK-MASQ
-A KUBE-NODEPORTS -p tcp -m comment --comment "oqton-backoffice/sonatype-nexus:" -m tcp --dport 32257 -j KUBE-SVC-HVYO5BWEF5HC7MD7
如果服务用到了 loadblance,此时报文是从 LB inbound 的,报文的 outbound 处理则是通过 KUBE-FW 实现 outbound 报文的负载均衡。如下对目的 IP 是50.1.1.1(LB公网IP)且目的端口是443(一般是https)的报文作了 KUBE-FW-J4ENLV444DNEMLR3 处理。 (参考 kubernetes ingress 到 pod 的数据流: http://dwz.date/cqfK )
-A KUBE-SERVICES -d 50.1.1.1/32 -p tcp -m comment --comment "kube-system/nginx-ingress-lb:https loadbalancer IP" -m tcp --dport 443 -j KUBE-FW-J4ENLV444DNEMLR3
如下在 KUBE-FW-J4ENLV444DNEMLR3 中显示的是 LB 的3个 endpoint(该  endpoint 可能是 service),使用比率对报文进行了负载均衡控制
Chain KUBE-SVC-J4ENLV444DNEMLR3 (3 references)
    10   600 KUBE-SEP-ZVUNFBS77WHMPNFT all  -- * * 0.0.0.0/0            0.0.0.0/0            /* kube-system/nginx-ingress-lb:https */ statistic mode random probability 0.33332999982
    18  1080 KUBE-SEP-Y47C2UBHCAA5SP4C all  -- * * 0.0.0.0/0            0.0.0.0/0            /* kube-system/nginx-ingress-lb:https */ statistic mode random probability 0.50000000000
    16   960 KUBE-SEP-QGNNICTBV4CXTTZM all  -- * * 0.0.0.0/0            0.0.0.0/0            /* kube-system/nginx-ingress-lb:https */
而上述3条链对应的处理如下,可以看到上述的每条链都作了 DNAT,将目的 IP 由 LB公网 IP 转换为 LB 的 container IP
-A KUBE-SEP-ZVUNFBS77WHMPNFT -s 172.20.1.231/32 -m comment --comment "kube-system/nginx-ingress-lb:https" -j KUBE-MARK-MASQ
-A KUBE-SEP-ZVUNFBS77WHMPNFT -p tcp -m comment --comment "kube-system/nginx-ingress-lb:https" -m tcp -j DNAT --to-destination 172.20.1.231:443
-A KUBE-SEP-Y47C2UBHCAA5SP4C -s 172.20.2.191/32 -m comment --comment "kube-system/nginx-ingress-lb:https" -j KUBE-MARK-MASQ
-A KUBE-SEP-Y47C2UBHCAA5SP4C -p tcp -m comment --comment "kube-system/nginx-ingress-lb:https" -m tcp -j DNAT --to-destination 172.20.2.191:443
-A KUBE-SEP-QGNNICTBV4CXTTZM -s 172.20.2.3/32 -m comment --comment "kube-system/nginx-ingress-lb:https" -j KUBE-MARK-MASQ
-A KUBE-SEP-QGNNICTBV4CXTTZM -p tcp -m comment --comment "kube-system/nginx-ingress-lb:https" -m tcp -j DNAT --to-destination 172.20.2.3:443
从上面可以看出,node 节点上的 iptables 中有到达所有 service 的规则,service  的 cluster IP 并不是一个实际的 IP,它的存在只是为了找出实际的 endpoint 地址,对达到 cluster IP 的报文都要进行 DNAT 为 Pod IP(+port),不同 node 上的报文实际上是通过 POD IP 传输的,cluster IP 只是本 node 节点的一个概念,用于查找并 DNAT,即目的地址为 clutter IP 的报文只是本 node 发送的,其他节点不会发送(也没有路由支持),即默认下 cluster ip 仅支持本 node 节点的 service 访问,如果需要跨 node 节点访问,可以使用插件实现,如 flannel,它将 pod  ip 进行了封装

ebdc9a39db0de4026e4d8cf5fb753808.png

  • 至此已经讲完了 kubernetes 的容器中 iptables 的基本访问方式,在分析一个应用的iptables 规则时,可以从 KUBE-SERVICE 入手,并结合该应用关联的服务(如 ingress LB 等)进行分析。

  • 查看 iptables 表项最好结合 iptables-save 以及如 iptables -t nat -nvL 的方式,前者给出了 iptables 的具体内容,但比较杂乱;后者给出了 iptables 的结构,可以方便地看出表中的内容,但是没有详细信息,二者结合起来才能比较好地分析。链接状态可以查看 /proc/net/nf_conntrack

b7bd5c360a9a996688c93f78706f2d17.png

TIPS:

openshift 下使用如下配置创建 nodeport 类型的 service。"nodeport" 表示通过nodeport 方式访问的端口;"port" 表示通过 service 方式访问的口;"targetPort" 表示后端服务"app" 暴露的 pod 端口。通过 nodeport 访问集群的流程为: {dstNodeIP:dstNodePort}-->(iptables)DNAT-->{dstPodIP:dstPodPort}。 创建 nodeport 类型的 service 时会在每个 node 上开启一个端口号为 {nodePort}的监听 socket(单独使用 docker run -p 启动 nodeport 进程为 docker-proxy),其中一个作用方便给应用通过 loopback 地址访问容器服务,删除该进程后将无法通过 host 的 loopback 接口访问容器服务,更多参见 docker-proxy(http://dwz.date/cqfZ)。 同时也可以通过:{service:servicePort}-->(iptables)DNAT-->{dstPodIP:dstPodPort} 的方式在集群内部访问后端服务。
apiVersion: v1
kind: Service
metadata:
  annotations:
  name: app-test
  namespace: openshift-monitoring
spec:
  externalTrafficPolicy: Cluster
  ports:
  - name: cluster
    nodePort: 33333  
    port: 44444
    protocol: TCP
    targetPort: 55555
  selector:
    app: app
  sessionAffinity: None
  type: NodePort
主要参考:https://blog.csdn.net/ebay/article/details/52798074 kube-proxy 的转发规则查看:http://www.lijiaocn.com/%E9%A1%B9%E7%9B%AE/2017/03/27/Kubernetes-kube-proxy.html

- END -

 推荐阅读 从零认识 iptables

Python 调用 Kubernetes API 自动化管理资源

大型网站技术架构的演进之路

Ceph分布式存储日常运维管理

30个Python极简代码,10分钟get常用技巧!

部署一套完整的Kubernetes高可用集群(二进制)

10 分钟部署一个 Kubernetes 集群

从网管到架构师再到微创业,我这9年的成长感悟

1d8b6d390716cbf05127a4071bcf04bb.png

点亮,服务器三年不宕机a793e73ec6067fba216e6fc200c069ed.gif

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐