我们有一个使用 AWS EC2 实例的 Kubernetes 集群设置,这是我们使用 KOPS 创建的。我们在通过 kubernetes 服务进行内部 Pod 通信时遇到了问题(这将在目标 Pod 之间对流量进行负载平衡(。当源和目标 Pod 位于同一 EC2 实例(节点(上时,会出现此问题。Kubernetes 使用 flannel 进行设置,用于使用 vxlan 进行节点间通信,而 kubernetes 服务由使用 iptables 的 kube-proxy 管理。
在以下情况下:
- 在 EC2 实例 1 上运行的 PodA (ip-172-20-121-84,us-east-1c(:100.96.54.240
- 在 EC2 实例 1 上运行的 PodB (ip-172-20-121-84,us-east-1c(:100.96.54.247
- 服务 B(其中 PodB 可能是目标端点的服务(:100.67.30.133
如果我们进入 PodA 并执行"curl -v http://ServiceB/",则不会收到任何答案,最后会产生超时。
当我们检查流量(实例 1 中的 cni0 接口(时,我们观察到:
- PodA 将 SYN 包发送到服务B IP
- 软件包被破坏,目标 IP 从业务 B IP 更改为 PodB IP
-
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+ACK 包
- SYN+ACK 包的源 IP 不会从 PodB IP 恢复到服务B IP
- PodA 从 PodB 接收到 SYN+ACK 包,这是意料之外的,它会发回一个 RESET 包
- PodA 在超时后再次向服务B 发送 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-proxy 自动管理(:
-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+ACK 包没有被修改回预期的 src/dst IP?
我终于找到了答案。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 到另一个,两者都属于同一个网桥(不由 netfilter/iptables 处理。
在我在问题中暴露的示例中,PodA (100.96.54.240( 将一个 SYN 包发送到不在 cni0 子网 (100.96.54.1/24( 中的 ServiceB (100.67.30.133(,因此该包不会保留在桥接的 cni0 接口中,iptable 处理它。这就是为什么我们看到 DNAT 发生了并在 conntrack 中注册的原因。但是,如果选定的目标 Pod 位于同一节点中,例如 PodB (100.96.54.247(,则 PodB 会看到 SYN 包并使用 SYN+ACK 进行响应,其中源为 100.96.54.247,目标为 100.96.54.240。这些是 cni0 子网内的 IP,不需要离开它,因此 netfilter/iptables 不会处理它,也不会根据 conntrack 信息对包进行修改(即,真正的源 100.96.54.247 不会被预期的源 100.67.30.133 替换(。
幸运的是,有桥接-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(积分(的 Kubernetes 群集设置中解决此问题,请使用kops edit cluster
和以下内容编辑群集清单,spec:
包括:
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 图像(时遇到的是,有时模块在启动期间加载,有时不加载(我不知道为什么会有这样的可变性(,因此取决于问题可能会表现出来或不