提问者:小点点

Kubernetes - 连接跟踪不会将包分解回原始目标 IP (DNAT)


我们有一个使用AWS EC2实例的Kubernetes集群设置,这些实例是我们使用KOPS创建的。我们在通过kubernetes服务进行内部pod通信时遇到问题(kubernetes服务将在目的地pod之间进行流量负载平衡)。当源pod和目标pod在同一个EC2实例(节点)上时,问题就出现了。Kubernetes通过法兰绒进行设置,使用vxlan进行节点间通信,kubernetes服务由kube-proxy使用iptables进行管理。

在以下情况下:

  • 在EC2实例1(ip-172-20-121-84, us-East-1c)上运行的PodA:100.96.54.240
  • 在EC2实例1(ip-172-20-121-84, us-East-1c)上运行的PodB:100.96.54.247
  • ServiceB(其中PodB是可能的目标endpoint的服务):100.67.30.133

如果我们进入 PodA 并执行“curl -v http://ServiceB/”,则不会收到任何答案,最后会产生超时。

当我们检查流量(实例1中的cni0接口)时,我们观察到:

> < li>PodA向ServiceB IP发送一个SYN包 < li >包被损坏,目标IP从ServiceB IP更改为PodB IP < li>

更改的Conntrack寄存器:

root@ip-172-20-121-84:/home/admin# conntrack -L|grep 100.67.30.133
tcp      6 118 SYN_SENT src=100.96.54.240 dst=100.67.30.133 sport=53084 dport=80 [UNREPLIED] src=100.96.54.247 dst=100.96.54.240 sport=80 dport=43534 mark=0 use=1

PodB向PodA发送SYN确认字符包

这里是tcpdump注释的详细信息:

root@ip-172-20-121-84:/home/admin# tcpdump -vv -i cni0 -n "src host 100.96.54.240 or dst host 100.96.54.240"
TCP SYN:
15:26:01.221833 IP (tos 0x0, ttl 64, id 2160, offset 0, flags [DF], proto TCP (6), length 60)
    100.96.54.240.43534 > 100.67.30.133.80: Flags [S], cksum 0x1e47 (incorrect -> 0x3e31), seq 506285654, win 26733, options [mss 8911,sackOK,TS val 153372198 ecr 0,nop,wscale 9], length 0
15:26:01.221866 IP (tos 0x0, ttl 63, id 2160, offset 0, flags [DF], proto TCP (6), length 60)
    100.96.54.240.43534 > 100.96.54.247.80: Flags [S], cksum 0x36d6 (incorrect -> 0x25a2), seq 506285654, win 26733, options [mss 8911,sackOK,TS val 153372198 ecr 0,nop,wscale 9], length 0

Level 2:
15:26:01.221898 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 100.96.54.240 tell 100.96.54.247, length 28
15:26:01.222050 ARP, Ethernet (len 6), IPv4 (len 4), Reply 100.96.54.240 is-at 0a:58:64:60:36:f0, length 28

TCP SYN+ACK:
15:26:01.222151 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    100.96.54.247.80 > 100.96.54.240.43534: Flags [S.], cksum 0x36d6 (incorrect -> 0xc318), seq 2871879716, ack 506285655, win 26697, options [mss 8911,sackOK,TS val 153372198 ecr 153372198,nop,wscale 9], length 0

TCP RESET:
15:26:01.222166 IP (tos 0x0, ttl 64, id 32433, offset 0, flags [DF], proto TCP (6), length 40)
    100.96.54.240.43534 > 100.96.54.247.80: Flags [R], cksum 0x6256 (correct), seq 506285655, win 0, length 0

TCP SYN (2nd time):
15:26:02.220815 IP (tos 0x0, ttl 64, id 2161, offset 0, flags [DF], proto TCP (6), length 60)
    100.96.54.240.43534 > 100.67.30.133.80: Flags [S], cksum 0x1e47 (incorrect -> 0x3d37), seq 506285654, win 26733, options [mss 8911,sackOK,TS val 153372448 ecr 0,nop,wscale 9], length 0
15:26:02.220855 IP (tos 0x0, ttl 63, id 2161, offset 0, flags [DF], proto TCP (6), length 60)
    100.96.54.240.43534 > 100.96.54.247.80: Flags [S], cksum 0x36d6 (incorrect -> 0x24a8), seq 506285654, win 26733, options [mss 8911,sackOK,TS val 153372448 ecr 0,nop,wscale 9], length 0
15:26:02.220897 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    100.96.54.247.80 > 100.96.54.240.43534: Flags [S.], cksum 0x36d6 (incorrect -> 0x91f0), seq 2887489130, ack 506285655, win 26697, options [mss 8911,sackOK,TS val 153372448 ecr 153372448,nop,wscale 9], length 0
15:26:02.220915 IP (tos 0x0, ttl 64, id 32492, offset 0, flags [DF], proto TCP (6), length 40)
    100.96.54.240.43534 > 100.96.54.247.80: Flags [R], cksum 0x6256 (correct), seq 506285655, win 0, length 0

实例1(ip-172-20-121-84,us-east-1c)上的相关iptable规则(由kube代理自动管理):

-A INPUT -m comment --comment "kubernetes service portals" -j KUBE-SERVICES
-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 100.96.0.0/11 -d 100.67.30.133/32 -p tcp -m comment --comment "prod/export: cluster IP" -m tcp --dport 80 -j KUBE-MARK-MASQ
-A KUBE-SERVICES -d 100.67.30.133/32 -p tcp -m comment --comment "prod/export: cluster IP" -m tcp --dport 80 -j KUBE-SVC-3IL52ANAN3BQ2L74

-A KUBE-SVC-3IL52ANAN3BQ2L74 -m comment --comment "prod/export:" -m statistic --mode random --probability 0.10000000009 -j KUBE-SEP-4XYJJELQ3E7C4ILJ
-A KUBE-SVC-3IL52ANAN3BQ2L74 -m comment --comment "prod/export:" -m statistic --mode random --probability 0.11110999994 -j KUBE-SEP-2ARYYMMMNDJELHE4
-A KUBE-SVC-3IL52ANAN3BQ2L74 -m comment --comment "prod/export:" -m statistic --mode random --probability 0.12500000000 -j KUBE-SEP-OAQPXBQCZ2RBB4R7
-A KUBE-SVC-3IL52ANAN3BQ2L74 -m comment --comment "prod/export:" -m statistic --mode random --probability 0.14286000002 -j KUBE-SEP-SCYIBWIJAXIRXS6R
-A KUBE-SVC-3IL52ANAN3BQ2L74 -m comment --comment "prod/export:" -m statistic --mode random --probability 0.16667000018 -j KUBE-SEP-G4DTLZEMDSEVF3G4
-A KUBE-SVC-3IL52ANAN3BQ2L74 -m comment --comment "prod/export:" -m statistic --mode random --probability 0.20000000019 -j KUBE-SEP-NXPFCT6ZBXHAOXQN
-A KUBE-SVC-3IL52ANAN3BQ2L74 -m comment --comment "prod/export:" -m statistic --mode random --probability 0.25000000000 -j KUBE-SEP-7DUMGWOXA5S7CFHJ
-A KUBE-SVC-3IL52ANAN3BQ2L74 -m comment --comment "prod/export:" -m statistic --mode random --probability 0.33332999982 -j KUBE-SEP-LNIY4F5PIJA3CQPM
-A KUBE-SVC-3IL52ANAN3BQ2L74 -m comment --comment "prod/export:" -m statistic --mode random --probability 0.50000000000 -j KUBE-SEP-SLBETXT7UIBTZCPK
-A KUBE-SVC-3IL52ANAN3BQ2L74 -m comment --comment "prod/export:" -j KUBE-SEP-FMCOTKNLEICO2V37

-A KUBE-SEP-OAQPXBQCZ2RBB4R7 -s 100.96.54.247/32 -m comment --comment "prod/export:" -j KUBE-MARK-MASQ
-A KUBE-SEP-OAQPXBQCZ2RBB4R7 -p tcp -m comment --comment "prod/export:" -m tcp -j DNAT --to-destination 100.96.54.247:80

-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

这是服务定义:

root@adsvm010:/yamls# kubectl describe service export
Name:              export
Namespace:         prod
Labels:            <none>
Annotations:       <none>
Selector:          run=export
Type:              ClusterIP
IP:                100.67.30.133
Port:              <unset>  80/TCP
TargetPort:        80/TCP
Endpoints:         100.96.5.44:80,100.96.54.235:80,100.96.54.247:80 + 7 more...
Session Affinity:  None
Events:            <none>

如果我们直接使用PodB IP而不是服务(因此无需破坏包),则连接有效。

如果我们使用该服务,但随机选择的目标 Pod 在不同的实例中运行,则连接跟踪机制可以正常工作,它会破坏包,以便 PodA 看到预期的 SYN ACK 包(来自 ServiceB IP)。在这种情况量通过 cni0 和法兰绒 .0 接口。

这种行为开始于几周前,我们没有观察到任何问题(一年多),我们不记得集群设置或我们正在运行的pod有任何重大变化。有人知道为什么SYN确认字符包没有被篡改回预期的src/dst IP吗?


共1个答案

匿名用户

我终于找到了答案。cni0接口与所有pod虚拟接口处于桥接模式(每个pod有一个veth0在该节点上运行):

root@ip-172-20-121-84:/home/admin# brctl show
bridge name bridge id       STP enabled interfaces
cni0        8000.0a5864603601   no      veth05420679
                                        veth078b53a1
                                        veth0a60985d
...


root@ip-172-20-121-84:/home/admin# ip addr
5: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 8951 qdisc noqueue state UP group default qlen 1000
    link/ether 0a:58:64:60:36:01 brd ff:ff:ff:ff:ff:ff
    inet 100.96.54.1/24 scope global cni0
       valid_lft forever preferred_lft forever
    inet6 fe80::1c66:76ff:feb6:2122/64 scope link
       valid_lft forever preferred_lft forever

netfilter/iptables处理从桥接接口到其他接口的流量,但是不离开桥接接口的流量(例如,从一个veth0到另一个veth 0,两者都属于同一网桥)不被netfilter/iptables处理。

在我在问题中公开的示例中,PodA(100.96.54.240)将SYN包发送到不在cni0子网(100.96.54.1/24)中的ServiceB(100.67.30.133),因此该包不会停留在桥接的cni0接口中,并且iptable会处理它。这就是为什么我们看到DNAT发生并在连接轨道中注册。但是如果选择的目标pod在同一个节点中,例如PodB(100.96.54.247),那么PodB会看到SYN包并响应SYN确认字符,其中源100.96.54.247,目标100.96.54.240。这些是cni0子网内的IP,不需要离开它,因此netfilter/iptables不会处理它,也不会根据contrack信息修改包(即,真正的源100.96.54.247不会被预期的源100.67.30.133替换)。

幸运的是,有bridge-netfilter内核模块可以使netfilter/iptables处理桥接接口中发生的流量:

root@ip-172-20-121-84:/home/admin# modprobe br_netfilter
root@ip-172-20-121-84:/home/admin# cat /proc/sys/net/bridge/bridge-nf-call-iptables
1

要在使用KOPS(信用)的库伯内特斯集群设置中解决此问题,请使用kops编辑集群编辑集群清单,并在规范:下包括:

hooks:
- name: fix-bridge.service
  roles:
  - Node
  - Master
  before:
  - network-pre.target
  - kubelet.service
  manifest: |
    Type=oneshot
    ExecStart=/sbin/modprobe br_netfilter
    [Unit]
    Wants=network-pre.target
    [Install]
    WantedBy=multi-user.target

这将在你的节点中的 /lib/systemd/system/fix-bridge.service 中创建一个 systemd 服务,该服务将在启动时运行,它将确保在 kubernetes(即 kubelet)启动之前加载br_netfilter模块。如果我们不这样做,我们在使用 AWS EC2 实例(Debian Jessie 图像)时遇到的是,有时模块在启动期间加载,有时不加载(我不知道为什么会有这样的可变性),因此取决于问题可能会表现出来或不表现出来。