强大的 iptables 在 K8s 中的应用剖析

DevOps技术栈

共 8769字,需浏览 18分钟

 · 2020-09-03

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



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 进行了封装


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

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



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
    nodePort33333  
    port44444
    protocol: TCP
    targetPort55555
  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年的成长感悟



点亮,服务器三年不宕机

浏览 42
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报