Table of Contents

1准备

1.1测试环境

1.2容器网和 nsenter

1.3验证基本连接

2个出口:Pod->主机-> VPC网络

2.1容器内部网络

1.2 Veth对连接到主机

1.3出口BPF代码

1.4主机路由表

2入口

2.1主机路由表

2.2入口BPF代码

2.3集装箱收货

3小结

参考文献

相关阅读


 

发表于2019-10-26 | 最后更新2019-10-26

这篇文章探讨了在AWS上由Cilium驱动的K8S集群中两个跨主机Pod之间的网络拓扑流量路径。我们将使用普通的Linux命令来完成此任务。在这篇文章的结尾,我们将获得如下图片:

1准备

1.1测试环境

我们有两个K8S主机:

  • 节点1: 10.5.2.48
  • 节点2: 10.5.2.58

和两个豆荚:

  • 节点110.5.2.11上的pod1
  • 10.5.2.22node2上的pod2

节点和Pod在同一VPC中,带有VPC网关10.5.2.1

注意:出于安全原因以及易于理解,我用伪造的IP / MAC地址代替了真实的IP / MAC地址。这不应破坏此职位的意义。

1.2容器网和 nsenter

我们将使用nsenter工具工具在主机上容器的网络名称空间中执行命令,格式为:

$ nsenter -t <pid> -n <command>

哪里,

  • -t <pid>:目标过程 <pid>
  • -n:输入网络名称空间
  • <command>:执行命令

这等效于docker exec <container> <command>,但比后者更灵活,因为通常缺少网络工具或容器内部没有特权,因此在主机上执行命令没有此限制。

获取容器进程ID:

root@node1 # docker inspect 04d740a33726 | grep Pid
            "Pid": 75869,

75869就是pid我们想要的。

1.3验证基本连接

从pod1 ping pod2,确保其可访问:

root@node1 # nsenter -t 75869 -n ping 10.5.2.22 -c 2
64 bytes from 10.5.2.22: icmp_seq=1 ttl=61 time=0.248 ms
64 bytes from 10.5.2.22: icmp_seq=2 ttl=61 time=0.208 ms

好!接下来,我们将探索这些数据包的确切路径,即网络设备,路由表,arp表,BPF挂钩。

2个出口:Pod->主机-> VPC网络

2.1容器内部网络

从pod1开始我们的旅程。检查pod1内的网络设备:

root@node1 # nsenter -t 75869 -n ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    ...
42: eth0@if43: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc noqueue state UP
    link/ether ee:14:d3:9a:62:42 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.5.2.11/32 brd 10.5.2.11 scope global eth0

可以看出,它具有一个loopbak接口lo和一个eth0 IP地址为IP的网络接口10.5.2.11

请注意42: eth0@if43,此特殊符号表示eth0具有 ifindex编号42,并且设备带有ifindex 4243 组成veth对。这ifindex在主机(在本例中为node1)中是唯一的,稍后我们将再次看到。

接下来,检查容器内的路由表:

root@node1 # nsenter -t 75869 -n route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         10.5.2.191   0.0.0.0         UG    0      0        0 eth0
10.5.1.191   0.0.0.0         255.255.255.255 UH    0      0        0 eth0

可以看出,该Pod的网关为10.5.1.191,所有出口流量都将转发到该网关。在主机上查找网关:

root@node1 # ip a | grep 10.5.2.191 -B 2
31: cilium_host@cilium_net: <...> mtu 9001 ... qlen 1000
    link/ether 0a:ee:d6:5f:6c:32 brd ff:ff:ff:ff:ff:ff
    inet 10.5.2.191/32 scope link cilium_host

我们可以看到容器网关是由device持有的cilium_host。其实 cilium_hostcilium_net撰写另一VETH对,他们都对主机的网络空间,并将于纤毛代理开始创建。

接下来,检查容器内的ARP表:

root@node1 # nsenter -t 75869 -n arp -n
Address                  HWtype  HWaddress           Flags Mask            Iface
10.5.2.191            ether   86:05:d4:99:a9:f5   C                     eth0
10.5.2.48             ether   86:05:d4:99:a9:f5   C                     eth0

可以看出,容器的网关和主机IP都指向相同的MAC地址86:05:d4:99:a9:f5。让我们进一步确定哪个设备拥有该地址。

可以看出,容器的网关和主机IP都指向相同的MAC地址86:05:d4:99:a9:f5。让我们进一步确定哪个设备拥有该地址。

1.2 Veth对连接到主机

root@node1 # ip link | grep 86:05:d4:99:a9:f5 -B 1
43: lxc050ba70e11a8@if42: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 9001 qdisc ... qlen 1000
    link/ether 86:05:d4:99:a9:f5 brd ff:ff:ff:ff:ff:ff link-netnsid 1

而已!设备lxc050ba70e11a8持有它。请注意43: lxc050ba70e11a8@if42符号,并回想一下容器的eth0实际持有ifindex=42,因此我们现在确保:

  1. pod1通过veth对(if42 <--> if43或名称表示eth0 <--> lxc050ba70e11a8)连接到主机
  2. Pod中的默认网关指向cilium_host主机上的设备
  3. Pod生成流量的下一个L3跃点是 cilium_host
  4. Pod产生流量的下一个L2跳是第ve对(lxc050ba70e11a8)的主机端

这正是Pod出口流量从容器流向主机的方式。

1.3出口BPF代码

Cilium的强大功能之一是动态流量操纵。它通过利用BPF来实现。关于此主题的详细说明不在本文的范围之内,如果您有兴趣的话,请参阅官方文档《BPF和XDP参考指南》( 如果您可以阅读中文,请参阅我的 翻译)。

Cilium使用tcBPF过滤容器的进出流量。让我们看一下出口部分:

root@node1:~  # tc filter show dev lxc050ba70e11a8 egress
filter protocol all pref 1 bpf
filter protocol all pref 1 bpf handle 0x1 bpf_lxc.o:[from-container] direct-action not_in_hw tag db59e2ea8177ded3

注意:如果以上命令的输出未显示该 bpf_lxc.o:[from-container] direct-action not_in_hw tag db59e2ea8177ded3 信息,则可能是您的iproute2软件包太旧了,请尝试升级到新版本。

列出此主机(节点1)上所有已加载的BPF程序:

root@node1:~  # bpftool prog
288: sched_cls  tag a390cb0eda39ede9
        loaded_at Oct 22/10:48  uid 0
        xlated 808B  jited 637B  memlock 4096B  map_ids 182,169
...
294: sched_cls  tag 596c1921e0319e72
        loaded_at Oct 22/10:48  uid 0
        xlated 176B  jited 157B  memlock 4096B
297: sched_cls  tag db59e2ea8177ded3
        loaded_at Oct 22/10:48  uid 0
        xlated 19144B  jited 11706B  memlock 20480B  map_ids 285,169,171,286,287,172,277,183,283,173,179,167,180

让我们进一步了解BPF代码/规则是什么样的。转储解释的BPF代码:

root@node1:~  # bpftool prog dump xlated id 297
   0: (bf) r6 = r1
   1: (b7) r7 = 0
   2: (63) *(u32 *)(r6 +60) = r7
   3: (63) *(u32 *)(r6 +56) = r7
   4: (63) *(u32 *)(r6 +52) = r7
   5: (63) *(u32 *)(r6 +48) = r7
   6: (63) *(u32 *)(r6 +64) = r7
   7: (18) r2 = 0xffffff5a
   9: (79) r1 = *(u64 *)(r6 +80)
  10: (79) r8 = *(u64 *)(r6 +216)
   ...

转储JITed BPF代码:

root@node1:~  # bpftool prog dump jited id 297
   0:   push   %rbp
   1:   mov    %rsp,%rbp
   4:   sub    $0x228,%rsp
   b:   sub    $0x28,%rbp
   f:   mov    %rbx,0x0(%rbp)
  13:   mov    %r13,0x8(%rbp)
  17:   mov    %r14,0x10(%rbp)
  1b:   mov    %r15,0x18(%rbp)
  1f:   xor    %eax,%eax
  21:   mov    %rax,0x20(%rbp)
  ...

好的,无需进一步挖掘。如果出口流量没有被BPF代码/规则丢弃,它将到达主机,主机路由设施将对其进行处理。

1.4主机路由表

查看主机路由表:

root@node1:~  # ip rule list
9:      from all fwmark 0x200/0xf00 lookup 2004
100:    from all lookup local
32766:  from all lookup main
32767:  from all lookup default

我们看到,有4个路由表:2004localmaindefault。检查每个内容:

root@node1:~  # ip route show table 2004
local default dev lo scope host

root@node1 $ ip route show table main
default via 10.5.2.1 dev eth0
10.5.2.0/24 dev eth0 proto kernel scope link src 10.5.2.48

来自Pod1的出口流量将到达main桌面。

注意:更具体地说,Pod IP是从ENI分配的,每个ENI都有自己的路由表以用于出口流量。在我的情况下,Pod1的IP来自节点1的默认ENI(eth0),因此流量将到达main表中。如果您有多个ENI,则此处的路由应该有所不同。

main路由表也是节点1的默认路由表(不要”由误导default表上方,它只是一个名称表default,而不是主机的默认表)。

node1的默认网关为10.5.2.1(VPC网关)。因此,pod1的出口流量最终将被发送到VPC网关。

这样就完成了我们的交通旅程的出口部分。

 

2入口

如果VPC网络将流量正确路由到Node2(供应商的责任),则这些数据包将到达Node2的相应ENI。让我们来看看如何处理这些数据包。

2.1主机路由表

root@node2:~  # ip rule list
9:      from all fwmark 0x200/0xf00 lookup 2004
100:    from all lookup local
32766:  from all lookup main
32767:  from all lookup default
root@node2:~  # ip route show table 2004
local default dev lo scope host

node2 $ ip route show table main
default via 10.5.1.1 dev eth0
10.5.2.22 dev lxcd86fc95bf974 scope link
...

可以看出,Pod2有一条专用的路由:

10.5.2.22 dev lxcd86fc95bf974 scope link

这意味着所有发10.5.2.22往的流量都将转发到 lxcd86fc95bf974

2.2入口BPF代码

Cilium将lxcxx为其创建的每个设备注入入口BPF规则。让我们检查一下这个:

root@node2:~  # tc filter show dev lxcd86fc95bf974 ingress
filter protocol all pref 1 bpf
filter protocol all pref 1 bpf handle 0x1 bpf_lxc.o:[from-container] direct-action not_in_hw tag c17fab4b3f874a54

如果您对确切的BPF代码感兴趣,那么以下步骤与我们之前的出口部分大致相同:

root@node2:~  # bpftool prog
156: sched_cls  tag a390cb0eda39ede9
        loaded_at Oct 22/10:59  uid 0
        xlated 808B  jited 637B  memlock 4096B  map_ids 46,33
...
165: sched_cls  tag c17fab4b3f874a54
        loaded_at Oct 22/10:59  uid 0
        xlated 19144B  jited 11706B  memlock 20480B  map_ids 155,33,35,156,157,36,147,47,153,37,43,31,44

root@node2:~  # bpftool prog dump xlated id 165 | head -n 10
   0: (bf) r6 = r1
   1: (b7) r7 = 0
   2: (63) *(u32 *)(r6 +60) = r7
   3: (63) *(u32 *)(r6 +56) = r7
   4: (63) *(u32 *)(r6 +52) = r7
   5: (63) *(u32 *)(r6 +48) = r7
   6: (63) *(u32 *)(r6 +64) = r7
   7: (18) r2 = 0xffffff5a
   9: (79) r1 = *(u64 *)(r6 +80)
  10: (79) r8 = *(u64 *)(r6 +216)

2.3集装箱收货

如果流量没有被Cilium网络策略规则(入口BPF)丢弃,则数据包将通过主机端的veth对,并最终到达 eth0内部容器-我们旅程的最终目的地。

现在在这里重新描述全局数据流图:

3小结

这篇文章探讨了AWS上由Cilium驱动的K8S集群中两个Pod之间的主机间流量网络拓扑数据流。我们使用了常见的Linux命令行工具来完成此任务。希望对您有帮助!

参考文献

  1. Cilium:BPF和XDP参考指南
  2. Cilium:AWS ENI
  3. Cilium:AWS ENI数据路径

 

相关阅读


深入理解 Cilium 的 eBPF(XDP)收发包路径:数据包在Linux网络协议栈中的路径

iptables详解(1):iptables概念

iptables详解(2):路由表

eBPF.io eBPF文档:扩展的数据包过滤器(BPF)

介绍Calico eBPF数据平面:Linux内核网络、安全性和跟踪(Kubernetes、kube-proxy)

Linux eBPF和XDP高速处理数据包;使用EBPF编写XDP网络过滤器;高性能ACL

深入理解 Cilium 的 eBPF 收发包路径

《Understanding (and Troubleshooting) the eBPF Datapath in Cilium》

kubernetes(K8s):管理云平台中多个主机上的容器化的应用

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐