K8S 企业容器云05 - 容器网络 - 容器间通信

4 分钟读完

容器间通信实现方法

同宿主机容器间互通

在同一宿主机上的容器间网络通信原理

网络设备

网络栈: 网卡 + 回环设备 + 路由表 + iptables 规则

  • 容器使用宿主机网络栈

    不开启 Network Namespace (--net=host),占用宿主机端口

  • 容器隔离网络栈

    使用自己 Network Namespace 里独立的网络栈

网桥(Bridge): 在 Linux 系统中起到 ‘虚拟交换机’ 作用的网络设备,在 数据链路层 通过 MAC 地址学习,将数据包转发到适当端口

在容器云环境下,这个网桥即宿主机 ‘docker0’ 设备

# ifconfig
...
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
...

Veth Pair: 包含一对虚拟网卡的虚拟设备,两张网卡间类似直连网线,可以跨 Network Namespace 传递数据包

容器 Veth Pair

图中,容器内 eth0 和 docker0 网桥上的 veth 虚拟网卡形成一组 Veth Pair

示例:

容器 nginx-1 的网络设备 eth0 - 172.17.0.2

# docker run -d --name nginx-1 nginx

# docker exec -it nginx-1 /bin/bash

/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.2  netmask 255.255.0.0  broadcast 172.17.255.255
...

容器路由表内 eth0 是默认路由设备,且 172.17.0.0/16 的请求交给 eth0

/# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

查看宿主机网络设备

# ifconfig
...
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:29ff:fe9a:a8b6  prefixlen 64  scopeid 0x20<link>
...
veth25ce675: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet6 fe80::6cd7:6bff:fe2e:b94a  prefixlen 64  scopeid 0x20<link>
        ether 6e:d7:6b:2e:b9:4a  txqueuelen 0  (Ethernet)
...

# brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242299aa8b6       no              veth25ce675
...

容器内 ‘eth0’ 网卡和宿主机 docker0 网桥上的 ‘veth25ce675’ 网卡即为一组 Veth Pair

再建容器 nginx-2

# docker run -d --name nginx-2 nginx

# docker exec -it nginx-2 /bin/bash

/# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.3  netmask 255.255.0.0  broadcast 172.17.255.255
...

容器内路由

/# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
0.0.0.0         172.17.0.1      0.0.0.0         UG    0      0        0 eth0
172.17.0.0      0.0.0.0         255.255.0.0     U     0      0        0 eth0

宿主机 docker0 网桥多了一个 ‘veth5fc4fdb’ 设备,对应 nginx-2 容器的 eth0

# brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242299aa8b6       no              veth25ce675
                                                        veth5fc4fdb

互通测试

从 nginx-2 测试 ping nginx-1

/# ping 172.17.0.2
PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.
64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=1.31 ms
64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.175 ms

原理

示例: nginx-2 (172.17.0.3) --ping--> nginx-1 (172.17.0.2)

STEP 1

目标地址 172.17.0.2 匹配第二条路由规则: 172.17.0.0 --> nginx-2 eth0 设备

网关 ‘0.0.0.0’,一条直连规则(IP 包经过本机 eth0 网口,通过二层网络直接发往目标地址)

nginx-2 eth0 发送 ARP 广播,通过 IP 查找 MAC 地址

STEP2

ARP 经过 nginx-2 eth0 --> docker0[veth5fc4fdb] (Veth Pair)

网桥 docker0 作为二层交换机,把 ARP 转发到 docker0 上所有端口 (含 veth25ce675)

STEP3

veth25ce675 --> nginx-1 eth0

nginx-1 容器网络栈收到 ARP,将 172.17.0.2 对应 MAC 回复给 nginx-2

STEP4

数据包

nginx-2 eth0 --> veth5fc4fdb --> docker0 (CAM 表) --> veth25ce675 --> nginx-1 eth0

STEP5

<-- Pong --

跨宿主机容器间互通

Flannel 项目为例,支持三种后端实现: UDP/VXLAN/host-gw

UDP 模式原理

简单、直接,性能差,已弃用

环境:

预先配置 docker0 地址范围为 Flannel 分配的宿主机子网

# FLANNEL_SUBNET=10.36.x.1/24

# dockerd --bip=$FLANNEL_SUBNET
...
  • 宿主机 k02,192.168.52.2

    docker0 地址 10.36.2.1 运行容器 flanpod-1 10.36.2.2

  • 宿主机 k03,192.168.52.3

    docker0 地址 10.36.3.1 运行容器 flanpod-2 10.36.3.3

示例: flanpod-1 --ping--> flanpod2

STEP1

flanpod-1 eth0 --(s:10.36.2.2/d:10.36.3.3)--> k02 docker0

STEP2

k02 路由规则

k02
# ip route
default via 192.168.52.2 dev ens33 proto static
10.36.0.0/16 dev flannel0  proto kernel  scope link  src 10.36.2.0
10.36.2.0/24 dev docker0  proto kernel  scope link  src 10.36.2.1
192.168.52.0/24 dev ens33 proto kernel scope link src 192.168.52.152

目的地址 10.36.3.3 不在 docker0 网段,进入宿主机后匹配第二条规则,进入 flannel0 设备

flannel0 是一个 TUN 设备(三层虚拟网络设备,在操作系统内核和用户应用程序之间传递 IP 包)

flannel0 --> flanneld 进程(内核态 --> 用户态)

STEP3

flanneld 给每台宿主机分配一个子网,宿主机上容器同属该子网

由 Etcd 维护子网信息

# etcdctl ls /coreos.com/network/subnets
/coreos.com/network/subnets/10.36.1.0-24
/coreos.com/network/subnets/10.36.2.0-24
/coreos.com/network/subnets/10.36.3.0-24

flanneld 进程收到 flannel0 设备传入的包,根据目标地址匹配对应子网

由 Etcd 维护子网和宿主机对应关系

找到目标容器所在宿主机 k03 地址 ‘192.168.52.3’

# etcdctl get /coreos.com/network/subnets/10.36.3.0-24
{"PublicIP":"192.168.52.3"}

STEP4

flanneld 进程将 IP 包封装在 UDP 包内,发送给 k03

flanneld --UDP(ss:192.168.52.2[s:10.36.2.2/d:10.36.3.3]dd:192.168.52.3)--> k03:8285

STEP5

k03 宿主机 flanneld 进程将 UDP 包解析后发送给 TUN 设备 flannel0

k03:8285 --> flanneld --(s:10.36.2.2/d:10.36.3.3)--> flannel0

STEP6

通过 flannel0 数据包从用户态进入内核态

k03 路由规则

k03
# ip route
default via 192.168.52.3 dev ens33 proto static
10.36.0.0/16 dev flannel0  proto kernel  scope link  src 10.36.3.0
10.36.3.0/24 dev docker0  proto kernel  scope link  src 10.36.3.1
192.168.52.0/24 dev ens33 proto kernel scope link src 192.168.52.153

目的地址 10.36.3.3 匹配第二条规则,进入 docker0 网桥

flannel0 --> docker0 --> vethXX --> flanpod-2 eth0

STEP7

<-- Pong --

找了网上一张原理图,可供参考

Flannel UPD

Fannel UDP 模式提供三层 Overlay 的网络,对 IP 包进行 UDP 封装、解封装

UDP 模式缺陷

三次内核态与用户态之间数据拷贝,影响性能

  1. 用户态的容器发出 IP 包经过 docker0 网桥进入 内核态

  2. IP 包根据路由表进入 flannel0 (TUN 设备),交给 用户态 flanneld 进程;

  3. flanneld 将 IP 包 UDP 封装后进入 内核态 ,从宿主机 eth0 网口发出

上下文切换和用户态解、封包开销都不小

VXLAN 模式原理

在现有三层网络上,由 Linux 内核的 VXLAN 模块 (内核态实现解、封包) 构建覆盖 (Overlay) 网络,实现 二层互通

VXLAN 在宿主机设置一个特殊网络设备 VTEP (VXLAN Tunnel End Point) 作为隧道两端,对 二层数据帧 (Ethernet frame) 做封装、解封装

宿主机上的 flannel.1 即 VXLAN 所需 VTEP 设备,拥有 IP&MAC 地址

环境:

  • 宿主机 Node1,10.168.0.2

    flannel.1 地址 10.1.15.0,docker0 地址 10.1.15.1,container-1 地址 10.1.15.2

  • 宿主机 Node2,10.168.0.3

    flannel.1 地址 10.1.16.0,docker0 地址 10.1.16.1,container-2 地址 10.1.16.3

Flannel VXLAN

示例: container-1 --ping--> container-2

STEP1

容器内到达宿主机网桥 docker0

container-1 eth0 --(s:10.1.15.2/d:10.1.16.3)--> Node1 docker0

STEP2

Node1 docker0 --> Node1 flannel.1

主机路由将目的地址 10.1.16.3 通过 flannel.1 设备发往网关 10.1.16.0 (Node2 flannel.1)

# route -n
Kernel IP routing table
Destination     Gateway         Genmask         Flags Metric Ref    Use Iface
...
10.1.16.0       10.1.16.0       255.255.255.0   UG    0      0        0 flannel.1

注:路由信息在 Node2 加入 flannel 网络后,由各台宿主机的 flanneld 进程在本机添加

STEP3

已知目的 VTEP (Node2 flannel.1) IP (10.1.16.0)

根据 ARP 表查询到对应二层 MAC 5e:f8:4f:00:e3:37

Node1
# ip neigh show dev flannel.1
10.1.16.0 lladdr 5e:f8:4f:00:e3:37 PERMANENT

注:ARP 信息也是由 flanneld 进程在 Node2 加入 flannel 网络后,在各宿主机添加

STEP4

内核二层封包,加上 ‘Inner Ethernet Header’ 二层头

内部数据帧

这个 ‘内部数据帧’ 在宿主机网络平面无法直接使用,需要再次封装

STEP5

在 ‘内部数据帧’ 前加上 VXLAN 头(含 VNI 标识 1),再封装进 UDP 包

Node1 flannel.1 要将数据包发送给 Node2 flannel.1 (已知 MAC)

但不知道 Node2 宿主机地址

根据目的 MAC (5e:f8:4f:00:e3:37) 在 FDB (Forwarding Database) 转发数据库查询

Node1
# bridge fdb show flannel.1 | grep 5e:f8:4f:00:e3:37
5e:f8:4f:00:e3:37 dev flannel.1 dst 10.168.0.3 self permanent

注:FDB 也由 flanneld 进程维护

确认目的宿主机地址 10.168.0.3 (Node2)

STEP6

在 UDP 包增加 FDB 查询得到的目的 IP

再加上二层数据帧头,包含 Node2 eth0 MAC 信息,形成 ‘外部数据帧’ 如下图

外部数据帧

注:宿主机 eth0 IP/MAC 信息由宿主机层面 ARP 维护

封包过程结束

Node1 flannel.1 --(外部数据帧)--> Node1 eth0 --宿主机网络--> Node2 eth0

STEP7

Node2 内核网络栈发现数据帧有 VXLAN Header,对其拆包

根据 VNI=1,将其交给 Node2 flannel.1 设备,进一步拆包

Node2 eth0 --> Node2 flannel.1 --> Node2 docker0 --> container-2 eth0

进入目标容器网络空间

分类:

更新时间: