容器技术 K8s kube-proxy iptables 再谈

3,973 阅读17分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天。

在kubernetes集群内,访问一个pod应用可以通过pod IP,但是pod删除重建后IP会发生变化,而且在高可用多副本场景下直接通过IP访问,则会有客户端需要实现负载均衡等问题。为了解决上述问题,kubernetes在客户端和pod间引入了一个抽象层:service

service通过labelSelector与pod关联,客户端通过service访问pod。访问类型可以分为两类:

  • 通过service VIP访问:通过访问service clusterIP代理到后端pod。

  • 通过service域名访问:在kubernetes集群内创建一个service对象后,kubernetes会生成一条 {serviceName}.{namespace}.svc.cluster.localDNS记录,客户端通过该DNS记录最终访问pod。

通过service域名访问时,根据service的配置又可以分为两类:

  • normal service:客户端向集群内的DNS服务(如coreDNS)解析service域名后得到的是service的VIP,之后流程和上述通过service VIP访问一样。

  • headless service:所谓headless service是指service对象spec.clusterIP=None的service。解析headless service域名时,得到的不再是service VIP,而是某一个后端pod的IP。

kube-proxy

当在kubernetes集群中创建一个service对象后,controller-manager组件会自动创建一个和service名称相同的endpoints对象。endpoints对象中的IP就是该service通过labelSelector关联的且已就绪pod IP,controller-manager里的endpoints controller会监听pod的状态,实时维护endpoints对象中的数据。

新版本kubernetes(v1.21+)中,controller-manager组件除了会创建endpoints对象,一般还会创建两个endpointsSlice对象,这两个endpointsSlice对象一个负责ipv4,一个负责ipv6,名称是service名称加上一个后缀。

endpointsSlice会记录service关联的所有pod IP,即使这个pod非ready状态。

kube-proxy在kubernetes集群中以daemonSet形式启动,也就每个节点上都会启动一个kube-proxy服务的pod。kube-proxy的pod通过监听集群中service和endpoints资源的变化,刷新节点上的iptables/ipvs规则,从而实现访问service VIP代理到后端pod的功能

三种模式

kube-proxy先后出现了三种模式:userspaceiptablesipvs,其中userspace模式是通过用户态程序实现转发,因性能问题基本被弃用,当前主流的模式是iptables和ipvs。kube-proxy默认配置是iptables模式,可以通过修改kube-system命名空间下名称为kube-proxy的configMap资源的mode字段来选择kube-proxy的模式。

本文只介绍kube-proxy iptables模式实现的普通clusterIP类型的service原理,其它类型的service原理大家可以参考本文和其它资料自行分析。如无特殊场景,下文不再对kube-proxy模式和service类型做特别说明。

本文内容基于kube-proxy:v1.15.0+iptables:1.4.21+flannel(vxlan)0.16.1:

iptables

之所以称作iptables模式,是因为该模式是基于iptables规则实现的。在《iptables》一文中,我们对iptables基础知识做了简单说明,其中有几个知识点在本文中需要用到,我们先回顾一下:

  • 表的优先级

iptables 5张表的优先级从高到低分别是:raw、mangle、nat、filter、security。

  • 链的执行顺序

iptables链下的规则是顺序执行的,且JUMP动作支持从一条链跳到另一条链。

  • 链与表

iptables有5条系统预定义的链,也可以自定义规则链,但自定义链不能自动触发,因此一个数据包过来都是从系统预定义的链开始匹配的;iptables也不是每个链上都能挂表,链与表以及数据包流转逻辑如下图所示:

图片

原理分析

环境准备

先从apiserver和controller-manager的启动参数中看看service VIP和pod IP的分配范围,这两个值会在后面用到:

# ps -ef | grep apiserver | grep -v grep | grep service-cluster-ip-range
... --service-cluster-ip-range=10.1.0.0/16 ...

# ps -ef | grep controller-manager | grep -v grep | grep cluster-cidr
... --cluster-cidr=10.244.0.0/16 ...

我们接着创建一个service以及一个2副本的deployment(注意使deployment的2个pod调度到不同的节点),deployment配置的pod镜像是nginx,nginx服务起来后会监听80端口。

Note:

在只有master和worker两个节点的环境下,为了使普通pod能调度到master节点,可以删除master上的污点(taint):

# kubectl taint node {masterNodeName} node-role.kubernetes.io/master-

service yaml:

apiVersion: v1
kind: Service
metadata:
  name: nginx-svc
spec:
  selector:
    app: nginx
  ports:
  - name: http
    protocol: TCP
    port: 8080
    targetPort: 80

deployment yaml:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  replicas: 2
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
      - name: nginx
        image: nginx
        imagePullPolicy: IfNotPresent

查看service、endpoint、node和pod信息:

# kubectl get svc
NAME         TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)    AGE
nginx-svc    ClusterIP   10.1.190.219   <none>        8080/TCP   9m34s

# kubectl get endpoints
NAME         ENDPOINTS                     AGE
nginx-svc    10.244.0.3:80,10.244.1.7:80   18m

 kubectl get node -o wide
NAME              STATUS   ROLES    AGE     VERSION   INTERNAL-IP   EXTERNAL-IP   OS-IMAGE                KERNEL-VERSION                CONTAINER-RUNTIME
vm-12-11-centos   Ready    <none>   4d15h   v1.15.0   10.0.12.11    <none>        CentOS Linux 7 (Core)   3.10.0-1160.45.1.el7.x86_64   docker://18.6.1
vm-12-7-centos    Ready    master   4d15h   v1.15.0   10.0.12.7     <none>        CentOS Linux 7 (Core)   3.10.0-1160.45.1.el7.x86_64   docker://18.6.1

# kubectl get pod -o wide
NAME                    READY   STATUS    RESTARTS   AGE   IP           NODE              NOMINATED NODE   READINESS GATES
nginx-55fc968d9-4pddn   1/1     Running   0          13m   10.244.1.7   vm-12-11-centos   <none>           <none>
nginx-55fc968d9-kh24k   1/1     Running   0          13m   10.244.0.3   vm-12-7-centos    <none>           <none>
iptables规则

kube-proxy生成的链都会包含KUBE字段,执行命令的时候可以加上该字段过滤。我们来看看此时kube-proxy在iptables 5张表中生成的链与规则:

iptables详细参数可以参考文档:ipset.netfilter.org/iptables-ex…

  • raw表
# iptables -t raw -S | grep KUBE#
  • mangle表
# iptables -t mangle -S | grep KUBE#
  • nat表(输出结果已过滤不相关规则)
# iptables -t nat -S | grep KUBE
-N KUBE-MARK-DROP
-N KUBE-MARK-MASQ
-N KUBE-NODEPORTS
-N KUBE-POSTROUTING
-N KUBE-SEP-FNO4E6JYD7EGUHTP
-N KUBE-SEP-SMDVMBZNJPO5AA7R
-N KUBE-SERVICES
-N KUBE-SVC-ELCM5PCEQWBTUJ2I
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
-A KUBE-MARK-DROP -j MARK --set-xmark 0x8000/0x8000
-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
-A KUBE-SEP-FNO4E6JYD7EGUHTP -s 10.244.1.7/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-FNO4E6JYD7EGUHTP -p tcp -m tcp -j DNAT --to-destination 10.244.1.7:80
-A KUBE-SEP-SMDVMBZNJPO5AA7R -s 10.244.0.3/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-SMDVMBZNJPO5AA7R -p tcp -m tcp -j DNAT --to-destination 10.244.0.3:80
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-SVC-ELCM5PCEQWBTUJ2I
-A KUBE-SVC-ELCM5PCEQWBTUJ2I -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-SMDVMBZNJPO5AA7R
-A KUBE-SVC-ELCM5PCEQWBTUJ2I -j KUBE-SEP-FNO4E6JYD7EGUHTP
  • filter表
# iptables -t filter -S | grep KUBE
-N KUBE-EXTERNAL-SERVICES
-N KUBE-FIREWALL
-N KUBE-FORWARD
-N KUBE-SERVICES
-A INPUT -m conntrack --ctstate NEW -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A INPUT -m conntrack --ctstate NEW -m comment --comment "kubernetes externally-visible service portals" -j KUBE-EXTERNAL-SERVICES
-A INPUT -j KUBE-FIREWALL
-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD
-A FORWARD -m conntrack --ctstate NEW -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -m conntrack --ctstate NEW -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A OUTPUT -j KUBE-FIREWALL
-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP
-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
-A KUBE-FORWARD -s 10.244.0.0/16 -m comment --comment "kubernetes forwarding conntrack pod source rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A KUBE-FORWARD -d 10.244.0.0/16 -m comment --comment "kubernetes forwarding conntrack pod destination rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
  • security表
# iptables -t security -S | grep KUBE#

Note:

如果在nat表中看到KUBE-SEP-XXX链下的规则DNAT后是--to-destination :0 --persistent --to-destination :0 --persistent --to-destination之类的数据而不是--to-destination IP:端口,可能是iptables版本太低了。

规则分析

从上文可以看出,kube-proxy iptables模式只在nat表和filter表增加了iptables规则,接下来我们分别对nat表和filter表的规则做详细梳理:

  • nat表

前文提到过用户自定义链是不能直接执行的,只能从系统预定义的链跳转过来,在nat表规则中我们首先看到在系统预定义的PREROUTING、OUTPUT和POSTROUTING链中各增加了一条规则,其中PREROUTING链是外部包进来的第一条链,OUTPUT链是本地进程包出去的第一条链,POSTROUTING链是数据包出宿主机网路协议栈的最后一条链

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

可以看出PREROUTING链和OUTPUT链都有一条无条件匹配规则跳转到KUBE-SERVICES链,POSTROUTING链则是有一条规则跳转到KUBE-POSTROUTING链。

我们继续分析KUBE-SERVICES链和KUBE-POSTROUTING链。

1. KUBE-SERVICES链

-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-SVC-ELCM5PCEQWBTUJ2I

上面是KUBE-SERVICES链的两条规则,第一条规则的意思是:源IP非10.244.0.0/16网段 && 目的IP是10.1.190.219/32网段 && TCP协议 && 目的端口是8080 的数据包会跳转到KUBE-MARK-MASQ,KUBE-MARK-MASQ链规则如下,也就是会给数据包打上一个0x4000/0x4000的标记(标记只在当前网络协议栈有效)。

-A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000

KUBE-MARK-MASQ链里没有DROP、REJECT等动作,于是执行完KUBE-MARK-MASQ链后又会回到KUBE-SERVICES链继续执行第二条规则。第二条规则是目的IP是10.1.190.219/32网段 && TCP协议 && 目的端口是8080 的数据包会跳转到KUBE-SVC-ELCM5PCEQWBTUJ2I链。

到这里我们把这两条规则综合起来看看:10.244.0.0/16是pod IP网段,10.1.190.219/32是service nginx-svc的clusterIP,也就是service VIP,目标端口8080是service中配置的port字段数值。于是我们可以“翻译”这两条规则为:非pod IP访问service会打上0x4000/0x4000标记所有访问service的数据包都会跳转到KUBE-SVC-XXX链

1.1 KUBE-SVC-ELCM5PCEQWBTUJ2I链

-A KUBE-SVC-ELCM5PCEQWBTUJ2I -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-SMDVMBZNJPO5AA7R
-A KUBE-SVC-ELCM5PCEQWBTUJ2I -j KUBE-SEP-FNO4E6JYD7EGUHTP

KUBE-SVC-ELCM5PCEQWBTUJ2I链中也有两条规则,第一条规则有--mode random --probability 0.50000000000配置。iptables的random随机功能中,--probability是随机概率,--probability 0.5表示有0.5的概率执行本规则。注意如果当前规则命中了概率,执行完当前规则后是结束当前链而不是继续匹配后续的规则。于是可以知道,这两条规则命中的概率都是0.5,这也是iptables实现service负载均衡的原理

Note:

如果有三条规则且期望这三条规则的概率是一样(即都为1/3),那么这三条规则配置的--probability参数分别为1/31/21,而不是1/3、1/3、1/3。

1.1.1 KUBE-SEP-SMDVMBZNJPO5AA7R链

-A KUBE-SEP-SMDVMBZNJPO5AA7R -s 10.244.0.3/32 -j KUBE-MARK-MASQ-A KUBE-SEP-SMDVMBZNJPO5AA7R -p tcp -m tcp -j DNAT --to-destination 10.244.0.3:80

我们接着分析KUBE-SEP-SMDVMBZNJPO5AA7R链里的两条规则,第一条规则是源IP是10.244.0.3/32的数据包会跳转到KUBE-MARK-MASQ打标记,最终所有的包都会DNAT到10.244.0.3:80,10.244.0.3:80正是pod nginx-55fc968d9-kh24k的IP和service nginx-svc中配置的targetPort。于是这两条规则结合前面的规则翻译过来就是:如果pod通过service IP+端口访问自己会被打上标记,所有通过service IP+端口访问的包都会DNAT到{pod IP}:{service targetPort}

1.1.2 KUBE-SEP-FNO4E6JYD7EGUHTP链

-A KUBE-SEP-FNO4E6JYD7EGUHTP -s 10.244.1.7/32 -j KUBE-MARK-MASQ
-A KUBE-SEP-FNO4E6JYD7EGUHTP -p tcp -m tcp -j DNAT --to-destination 10.244.1.7:80

KUBE-SEP-FNO4E6JYD7EGUHTP链内容其实和1.1.1的KUBE-SEP-SMDVMBZNJPO5AA7R链内容差不多,就是另外一个pod的配置而已,这里不再赘述。

2. KUBE-POSTROUTING链

POSTROUTING有一条无条件跳转到KUBE-POSTROUTING链的规则,且KUBE-POSTROUTING链只有一条规则,该规则是匹配有0x4000/0x4000标记的数据包做MASQUERADE,MASQUERADE会做SNAT,且SNAT IP就是出口网络设备的IP

-A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
  • filter

分析完了nat表我们再来看看filter表。kube-proxy在filter表的INPUT链、FORWARD链和OUTPUT链都有增加规则,但当前环境下KUBE-SERVICES链(注意不要与nat表KUBE-SERVICE链混淆)和KUBE-EXTERNAL-SERVICES链都是空链(即只有链没规则),于是:

1. INPUT链

INPUT链下当前只会跳到KUBE-FIREWALL链执行,KUBE-FIREWALL链下唯一一条规则也比较简单:打了0x8000/0x8000标记的包将被丢弃(DROP)。

-A KUBE-FIREWALL -m comment --comment "kubernetes firewall for dropping marked packets" -m mark --mark 0x8000/0x8000 -j DROP

2. OUTPUT链

当前OUTPUT链和INPUT链一样,也是跳转到KUBE-FIREWALL,不再赘述。

3. FORWARD链

-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD
-A FORWARD -m conntrack --ctstate NEW -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-A FORWARD -s 10.244.0.0/16 -j ACCEPT
-A FORWARD -d 10.244.0.0/16 -j ACCEPT

FORWARD链上有四条规则,我们先看后三条规则。第二条规则是基于conntrack模块匹配数据包状态是NEW的,跳转到KUBE-SERVICES链,但当前KUBE-SERVICES链是空链;第三条规则匹配源IP是10.244.0.0/16网段ACCEPT,第四条规则匹配目的IP是10.244.0.0/16网段ACCEPT。

再来看第一条规则,第一条规则是无条件跳转到KUBE-FORWARD链,KUBE-FORWARD链内容如下:

-A KUBE-FORWARD -m conntrack --ctstate INVALID -j DROP
-A KUBE-FORWARD -m comment --comment "kubernetes forwarding rules" -m mark --mark 0x4000/0x4000 -j ACCEPT
-A KUBE-FORWARD -s 10.244.0.0/16 -m comment --comment "kubernetes forwarding conntrack pod source rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A KUBE-FORWARD -d 10.244.0.0/16 -m comment --comment "kubernetes forwarding conntrack pod destination rule" -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT

KUBE-FORWARD链也有4条规则,第一条规则基于conntrack模块匹配状态是INVALID的数据包DROP;第二条规则匹配0x4000/0x4000标记ACCEPT;第三条规则匹配源IP是pod IP网段且conntrack状态是RELATED或者ESTABLISHED的数据包ACCEPT;最后一条规则匹配目的IP是pod IP网段且conntrack状态是RELATED或者ESTABLISHED的数据包ACCEPT。

小结

上述iptables规则分析完之后,我们对kube-proxy iptables模式下实现的service功能做个总结:

kube-proxy iptables模式最主要的链是:KUBE-SERVICES、KUBE-SVC-XXX、KUBE-SEP-XXX。其中:

  • KUBE-SERVICES链 :访问集群内service的数据包入口,它会根据匹配到的service IP:port跳转到KUBE-SVC-XXX链。

  • KUBE-SVC-XXX链 :对应service对象,基于random功能实现了流量的负载均衡。

  • KUBE-SEP-XXX链 :通过DNAT将service IP:port替换成后端pod IP:port,从而将流量转发到相应的pod。

通过前文iptables规则的分析也可以知道,service clusterIP仅仅是iptables规则的匹配条件,物理上并不存在一个IP是service clusterIP的网络设备,因此service clusterIP无法ping通:

# ping -c 1 10.1.190.219PING 10.1.190.219 (10.1.190.21956(84) bytes of data.--- 10.1.190.219 ping statistics ---1 packets transmitted, 0 received, 100% packet loss, time 0ms

实战分析

通过前文内容,我们对kube-proxy iptables模式实现的service有了一些理解,接下来我们再从宿主机上通过svc ip访问其它机器上pod的实际场景来看看数据包在宿主机iptables中的规则匹配流程。我们重新整理下当前的环境信息:

图片

Note:

  • pod中的iptables没有特殊规则,可以看做全放通,这里不再关注。

  • 下述iptables规则只保留了主要规则,缩进格式是为了更清晰的体现链的跳转。

  • 请求命令为宿主机上执行curl 10.1.190.219:8080,pod内抓取的报文仅截取了TCP三次握手。

去程逻辑图

图片

回程逻辑图

图片

去程iptables规则
/*************节点vm-12-7-centos*************/
// {src: localProcess, dst: 10.1.190.219:8080}
// nat表OUTPUT链
-A OUTPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
  -A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
    -A KUBE-MARK-MASQ -j MARK --set-xmark 0x4000/0x4000
  -A KUBE-SERVICES -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-SVC-ELCM5PCEQWBTUJ2I
    -A KUBE-SVC-ELCM5PCEQWBTUJ2I -j KUBE-SEP-FNO4E6JYD7EGUHTP
      -A KUBE-SEP-FNO4E6JYD7EGUHTP -p tcp -m tcp -j DNAT --to-destination 10.244.1.7:80
      
// filter表OUTPUT链
-A OUTPUT -j KUBE-FIREWALL

// {src: localProcess, dst: 10.244.1.7:80}
// 路由选择
10.244.1.0/24 via 10.244.1.0 dev flannel.1 onlink

// nat表POSTROUTING链
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
  -A KUBE-POSTROUTING -m comment --comment "kubernetes service traffic requiring SNAT" -m mark --mark 0x4000/0x4000 -j MASQUERADE
// MASQUERADE修改源IP+端口:{src: localProcess, dst: 10.1.190.219:8080} => {src: 10.244.0.0.50396, dst: 10.244.1.7:80}


/*************节点vm-12-11-centos*************/
// {src: 10.244.0.0.50396, dst: 10.244.1.7:80}
// nat表PREROUTING链
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

// 路由决策
10.244.1.0/24 dev cni0 proto kernel scope link src 10.244.1.1

// filter表FORWARD链
-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD

// nat表POSTROUTING链
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING
回程iptables规则
/*************节点vm-12-11-centos*************/
// {src: 10.244.1.7:80, dst: 10.244.0.0.50396}
// nat表PREROUTING链
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

// 路由决策
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink

// filter表FORWARD链
-A FORWARD -m comment --comment "kubernetes forwarding rules" -j KUBE-FORWARD

// nat表POSTROUTING链
-A POSTROUTING -m comment --comment "kubernetes postrouting rules" -j KUBE-POSTROUTING


/*************节点vm-12-7-centos*************/
// 出去的报文经MASQUERADE后回来的报文会转换回来:{src: 10.244.1.7:80, dst: 10.244.0.0.50396} => {src: 10.244.1.7:80, dst: localProcess}
// nat表PREROUTING链
-A PREROUTING -m comment --comment "kubernetes service portals" -j KUBE-SERVICES

// 路由决策

// filterINPUT链
-A INPUT -j KUBE-FIREWALL
目标pod内tcpdump抓包

因客户端宿主机POSTROUTING处做了MASQUERADE,pod看到的请求IP是客户端宿主机上的flannel.1设备的IP。

[root@VM-12-11-centos ~]# nsenter -t 10319 -n
[root@VM-12-11-centos ~]# tcpdump -i eth0 -n
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), capture size 262144 bytes
21:31:19.107449 IP 10.244.0.0.50396 > 10.244.1.7.http: Flags [S], seq 2721445788, win 29200, options [mss 1460,sackOK,TS val 3626323491 ecr 0,nop,wscale 7], length 0
21:31:19.107480 IP 10.244.1.7.http > 10.244.0.0.50396: Flags [S.], seq 2272988590, ack 2721445789, win 27960, options [mss 1410,sackOK,TS val 1413096198 ecr 3626323491,nop,wscale 7], length 0
21:31:19.107676 IP 10.244.0.0.50396 > 10.244.1.7.http: Flags [.], ack 1, win 229, options [nop,nop,TS val 3626323491 ecr 1413096198], length 0
小结

通过该场景验证以及逻辑分析,我们可以得出结论:当在集群内(node或者pod)访问service clusterIP:port时,都会经过PREROUTING/OUTPUT做DNAT到一个pod IP:port,并且在这个过程中有两种情况下会被打上0x4000/0x4000标记:

  1. 访问端IP非pod IP网段IP(如宿主机上直接访问);

  2. pod通过service clusterIP访问到了自己。

带有0x4000/0x4000标记的数据包如果到了POSTROUTING后,会做MASQUERADE

结语

前文分析nat表时,我们过滤掉了不相关service对应的iptables规则,我们来看看没过滤时的KUBE-SERVICE链:

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

-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.0.10/32 -p udp -m comment --comment "kube-system/kube-dns:dns cluster IP" -m udp --dport 53 -j KUBE-SVC-TCOU7JCQXEZGVUNU
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:dns-tcp cluster IP" -m tcp --dport 53 -j KUBE-SVC-ERIFXISQEP7F7OF4
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.0.10/32 -p tcp -m comment --comment "kube-system/kube-dns:metrics cluster IP" -m tcp --dport 9153 -j KUBE-SVC-JD5MR3NA4I4DYORP
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.190.219/32 -p tcp -m comment --comment "default/nginx-svc:http cluster IP" -m tcp --dport 8080 -j KUBE-SVC-ELCM5PCEQWBTUJ2I
-A KUBE-SERVICES ! -s 10.244.0.0/16 -d 10.1.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 10.1.0.1/32 -p tcp -m comment --comment "default/kubernetes:https cluster IP" -m tcp --dport 443 -j KUBE-SVC-NPX46M4PTMTKRN6Y
-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

iptables规则是链式顺序执行的,可以看出,随着service数量的增加,匹配规则也会越来越多,这在性能上会有严重的问题。为了解决大规模场景下的性能问题,kube-proxy推出了ipvs模式,下篇文章会详细分析kube-proxy的ipvs模式原理,敬请期待。