amazon-ec2 - Kubernetes - 连接跟踪不会将包破坏回原始目标 IP (DNAT)

标签 amazon-ec2 kubernetes netfilter flannel kube-proxy

我们有一个使用 KOPS 创建的 AWS EC2 实例的 Kubernetes 集群设置。我们在通过 kubernetes 服务进行内部 pod 通信时遇到问题(这将负载平衡目标 pod 之间的流量)。当源 pod 和目标 pod 位于同一个 EC2 实例(节点)上时,就会出现问题。 Kubernetes 使用 flannel 设置,用于使用 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 是可能的目标端点的服务):100.67.30.133

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

    当我们检查流量(实例 1 中的 cni0 接口(interface))时,我们观察到:
  • PodA 向 ServiceB IP
  • 发送 SYN 包
  • 包被破坏,目标 IP 从 ServiceB 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 恢复到 ServiceB IP
  • PodA 从 PodB 收到一个 SYN+ACK 包,这是意料之外的,它发回了一个 RESET 包
  • PodA超时后再次向ServiceB发送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 和 flannel.0 接口(interface)。

    这种行为是几周前开始的,在我们没有发现任何问题之前(一年多),我们不记得对集群设置或我们正在运行的 Pod 有任何重大更改。有没有人有任何想法可以解释为什么 SYN+ACK 包没有重新回到预期的 src/dst IPs?

    最佳答案

    我终于找到了答案。 cni0 接口(interface)与所有 pod 虚拟接口(interface)处于桥接模式(在该节点上运行的每个 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
    

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

    在我在问题中公开的示例中,PodA (100.96.54.240) 向不在 cni0 子网 (100.96.54.1/24) 中的 ServiceB (100.67.30.133) 发送 SYN 包,因此该包不会留在桥接的 cni0 interface 和 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)。

    幸好有bridge-netfilter内核模块,可以使 netfilter/iptables 处理桥接接口(interface)中发生的流量:
    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 (credits) 的 Kubernetes 集群设置中解决此问题,请使用 kops edit cluster 编辑集群 list 及以下 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 服务在启动时运行的节点中,它将确保 br_netfilter模块在 kubernetes(即 kubelet)启动之前加载。如果我们不这样做,我们对 AWS EC2 实例(Debian Jessie 镜像)的体验是,有时模块会在启动期间加载,有时则不会(我不知道为什么会有这样的可变性),因此取决于问题可能会出现,也可能不会。

    关于amazon-ec2 - Kubernetes - 连接跟踪不会将包破坏回原始目标 IP (DNAT),我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52976081/

    相关文章:

    reactjs - 部署时无法获取 kubectl 环境变量

    linux - 如何使用ip6tables的功能对ipv6分片数据包进行碎片整理?

    javascript - 如何与 React JS/Node JS 一起部署到 Amazon Web Services (AWS)?

    amazon-web-services - AWS Elastic Beanstalk - 增加实例磁盘容量

    amazon-web-services - AWS CloudWatch 指标 RequestCount 是什么意思?

    kubernetes - 在Kubernetes ConfigMap中指定 `int`值吗?

    python - 获取ec2实例 block 设备的卷Id

    nginx - 如何在不同的命名空间上使用 nginx 入口 TCP 服务

    c - 如果我使用 libnetfilter_queue 进行更改,网络数据包将被拒绝

    linux-kernel - ICMP报文的源端口和目的端口