k8s-3.3 网络

180 阅读11分钟

Docker原生网络方案

容器网络主要解决两大核心问题:一是容器的IP地址分配,二是容器之间的相互通信。本文重在研究第二个问题并且主要研究容器的跨主机通信问题。

实现容器跨主机通信的最简单方式就是直接使用host网络,这时由于容器IP就是宿主机的IP,复用宿主机的网络协议栈以及underlay网络,原来的主机能通信,容器也就自然能通信,然而带来的最直接问题就是端口冲突问题。

因此通常容器会配置与宿主机不一样的属于自己的IP地址。由于是容器自己配置的IP,underlay平面的底层网络设备如交换机、路由器等完全不感知这些IP的存在,也就导致容器的IP不能直接出去实现跨主机通信。 要解决如上问题实现容器跨主机通信,主要有如下两个思路:

  • 思路一:修改底层网络设备配置,加入容器网络IP地址的管理,修改路由器网关等,该方式主要和SDN结合。

  • 思路二:完全不修改底层网络设备配置,复用原有的underlay平面网络,解决容器跨主机通信,主要有如下两种方式:

    overlay隧道传输。把容器的数据包封装到原主机网络的三层或者四层包头,然后使用原来的网络传输到目标主机,目标主机再拆包转发给容器。overlay隧道如vxlan、ipip等,使用overlay的容器网络如Flannel、Weave等。

    修改主机路由。把容器网络加到主机路由表中,把主机当作容器网关,通过路由规则转发到指定的主机,实现容器的三层互通。通过路由实现容器跨主机通信的网络如Flannel host-gw、Calico等。

libnetwork&CNM

libnetwork 是 docker 容器网络库,最核心的内容是其定义的 Container Network Model (CNM),这个模型对容器网络进行了抽象,由以下三类组件组成:

  • Sandbox 是容器的网络栈,包含容器的 interface、路由表和 DNS 设置。 Linux Network Namespace 是 Sandbox 的标准实现。Sandbox 可以包含来自不同 Network 的 Endpoint。
  • Endpoint 的作用是将 Sandbox 接入 Network。Endpoint 的典型实现是 veth pair,后面我们会举例。一个 Endpoint 只能属于一个网络,也只能属于一个 Sandbox。
  • Network 包含一组 Endpoint,同一 Network 的 Endpoint 可以直接通信。Network 的实现可以是 Linux Bridge、VLAN 等。

单个主机网络方案

none

可以看到Docker容器仅有一lo环回接口,用户使用--net=none启动容器之后,仍然可以手动为容器配置网络。

docker run --net=none -ti ubuntu:latest ip addr show
[root@YZ-25-65-49 ~]# docker run --net=none -ti ubuntu:12.04  bash      
root@1accd8ab4f47:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever

host

host模式下,容器可以操纵主机的网络配置,这是危险的,除非万不得已,应该尽可能避免使用host模式。

docker run -ti --net=host ubuntu:latest bash
root@YZ-25-65-49:/# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
    inet6 ::1/128 scope host 
       valid_lft forever preferred_lft forever
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether f0:00:ac:19:41:31 brd ff:ff:ff:ff:ff:ff
    inet 172.25.65.49/23 brd 172.25.65.255 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::f200:acff:fe19:4131/64 scope link 
       valid_lft forever preferred_lft forever
3: docker0: <NO-CARRIER,BROADCAST,MULTICAST,UP> mtu 1500 qdisc noqueue state DOWN 
    link/ether 02:42:f5:a8:2a:78 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.1/16 brd 172.17.255.255 scope global docker0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:f5ff:fea8:2a78/64 scope link 
       valid_lft forever preferred_lft forever

bridge 

[root@YZ-25-65-50 ~]# docker run -d --name busybox busybox sleep 360000
e6e41c28b89bdf0278648103bd59c036f2779ef6a70e97f5609764d65f26e28d
[root@YZ-25-65-50 ~]# ip link
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP mode DEFAULT qlen 1000
    link/ether f0:00:ac:19:41:32 brd ff:ff:ff:ff:ff:ff
3: docker0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT 
    link/ether 02:42:a7:64:c0:1a brd ff:ff:ff:ff:ff:ff
5: veth18c80ff@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master docker0 state UP mode DEFAULT 
    link/ether da:09:fa:86:08:f7 brd ff:ff:ff:ff:ff:ff link-netnsid 0
[root@YZ-25-65-50 ~]# brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.0242a764c01a       no              veth18c80ff
[root@YZ-25-65-50 ~]# docker inspect e6 |grep SandboxKey
            "SandboxKey": "/var/run/docker/netns/9812fa7c88bf",
[root@YZ-25-65-50 ~]# nsenter --net=/var/run/docker/netns/9812fa7c88bf
[root@YZ-25-65-50 ~]# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
4: eth0@if5: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever

跨主机网络方案

overlay

为支持容器跨主机通信,Docker 提供了 overlay driver,使用户可以创建基于 VxLAN 的 overlay 网络。VxLAN 可将二层数据封装到 UDP 进行传输,VxLAN 提供与 VLAN 相同的以太网二层服务,但是拥有更强的扩展性和灵活性。

Docerk overlay 网络需要一个 key-value 数据库用于保存网络状态信息,包括 Network、Endpoint、IP 等。Consul、Etcd 和 ZooKeeper 都是 Docker 支持的 key-vlaue 软件

docker 会为每个 overlay 网络创建一个独立的 network namespace,其中会有一个 linux bridge br0,endpoint 还是由 veth pair 实现,一端连接到容器中(即 eth0),另一端连接到 namespace 的 br0 上。br0 除了连接所有的 endpoint,还会连接一个 vxlan 设备,用于与其他 host 建立 vxlan tunnel。容器之间的数据就是通过这个 tunnel 通信的。

macvlan

macvlan 本身是 linxu kernel 模块,其功能是允许在同一个物理网卡上配置多个 MAC 地址,即多个 interface,每个 interface 可以配置自己的 IP。macvlan 本质上是一种网卡虚拟化技术,Docker 用 macvlan 实现容器网络就不奇怪了。 macvlan 的最大优点是性能极好,相比其他实现,macvlan 不需要创建 Linux bridge,而是直接通过以太 interface 连接到物理网络。

macvlan 会独占主机的网卡,也就是说一个网卡只能创建一个 macvlan 网络 VLAN 是现代网络常用的网络虚拟化技术,它可以将物理的二层网络划分成多达 4094 个逻辑网络,这些逻辑网络在二层上是隔离的,每个逻辑网络(即 VLAN)由 VLAN ID 区分,VLAN ID 的取值为 1-4094。Linux 的网卡也能支持 VLAN(apt-get install vlan),同一个 interface 可以收发多个 VLAN 的数据包,不过前提是要创建 VLAN 的 sub-interface。比如希望 enp0s9 同时支持 VLAN10 和 VLAN20,则需创建 sub-interface enp0s9.10 和 enp0s9.20。在交换机上,如果某个 port 只能收发单个 VLAN 的数据,该 port 为 Access 模式,如果支持多 VLAN,则为 Trunk 模式

第三方网络方案

flannel

flannel 是 CoreOS 开发的容器网络解决方案。flannel 为每个 host 分配一个 subnet,容器从此 subnet 中分配 IP,这些 IP 可以在 host 间路由,容器间无需 NAT 和 port mapping 就可以跨主机通信。 每个 subnet 都是从一个更大的 IP 池中划分的,flannel 会在每个主机上运行一个叫 flanneld 的 agent,其职责就是从池子中分配 subnet。为了在各个主机间共享信息,flannel 用 etcd(与 consul 类似的 key-value 分布式数据库)存放网络配置、已分配的 subnet、host 的 IP 等信息。 数据包如何在主机间转发是由 backend 实现的。flannel 提供了多种 backend,最常用的有 vxlan 和 host-gw

flannel.1为vxlan设备,linux kernel可以自动识别,并将上面的packet进行vxlan封包处理。在这个封包过程中,kernel需要知道该数据包究竟发到哪个node上去。kernel需要查看node上的fdb(forwarding database)以获得上面对端vtep设备(已经从arp table中查到其mac地址:d6:51:2e:80:5c:69)所在的node地址。如果fdb中没有这个信息,那么kernel会向用户空间的flanned程序发起”L2 MISS”事件。flanneld收到该事件后,会查询etcd,获取该vtep设备对应的node的”Public IP“,并将信息注册到fdb中。这样Kernel就可以顺利查询到该信息并封包了:

https://tonybai.com/2017/01/17/understanding-flannel-network-for-kubernetes/
https://cizixs.com/2017/09/28/linux-vxlan/

[k8s@TX-220-54-4 ~]$ ip link show flannel.1    
9: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN mode DEFAULT 
    link/ether 4a:c7:ba:58:1d:87 brd ff:ff:ff:ff:ff:ff
    
[k8s@TX-220-54-4 ~]$ ip a
7: bond0.120@bond0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 24:6e:96:7d:5d:90 brd ff:ff:ff:ff:ff:ff
    inet 10.220.54.4/23 brd 10.220.55.255 scope global bond0.120
       valid_lft forever preferred_lft forever
9: flannel.1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UNKNOWN 
    link/ether 4a:c7:ba:58:1d:87 brd ff:ff:ff:ff:ff:ff
    inet 10.244.0.0/32 scope global flannel.1
       valid_lft forever preferred_lft forever
10: cni0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP qlen 1000
    link/ether d6:e8:51:68:d2:fd brd ff:ff:ff:ff:ff:ff
    inet 10.244.0.1/24 scope global cni0
       valid_lft forever preferred_lft forever
14213: vethd176601d@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue master cni0 state UP 
    link/ether da:03:29:43:c8:31 brd ff:ff:ff:ff:ff:ff link-netnsid 2
    
 [k8s@TX-220-54-4 ~]$ kubectl get po -o wide
NAME                                        READY   STATUS      RESTARTS   AGE     IP             NODE                               NOMINATED NODE   READINESS GATES
simple-tensorflow-serving-cbbcdcc74-wsgjc   1/1     Running     214        83d     10.244.0.210   tx-220-54-4.h.chinabank.com.cn     <none>           <none>
[k8s@TX-220-54-4 ~]$ kubectl exec -it simple-tensorflow-serving-cbbcdcc74-wsgjc bash
root@simple-tensorflow-serving-cbbcdcc74-wsgjc:/simple_tensorflow_serving# ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
3: eth0@if14213: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1450 qdisc noqueue state UP group default 
    link/ether 96:b9:cb:2e:a2:9c brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 10.244.0.210/24 scope global eth0
       valid_lft forever preferred_lft forever
       
       
 [k8s@TX-220-54-7 ~]$ ip r
default via 10.220.55.254 dev bond0.120 
10.220.54.0/23 dev bond0.120 proto kernel scope link src 10.220.54.7 
10.244.0.0/24 via 10.244.0.0 dev flannel.1 onlink 
[k8s@TX-220-54-7 ~]$  bridge fdb show dev flannel.1|grep 54.4
4a:c7:ba:58:1d:87 dst 10.220.54.4 self permanent

calico

Calico 是一个纯三层的虚拟网络方案,Calico 为每个容器分配一个 IP,每个 host 都是 router,把不同 host 的容器连接起来。与 VxLAN 不同的是,Calico 不对数据包做额外封装,不需要 NAT 和端口映射,扩展性和性能都很好。 与其他容器网络方案相比,Calico 还有一大优势:network policy。用户可以动态定义 ACL 规则,控制进出容器的数据包,实现业务需求。

fuckcloudnative.io/posts/poke-…

[root@YZ-25-58-1 istio-1.1.6]# kubectl exec -it ceph-pod1 sh
/ # ip a
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
    inet 127.0.0.1/8 scope host lo
       valid_lft forever preferred_lft forever
2: tunl0@NONE: <NOARP> mtu 1480 qdisc noop qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0
4: eth0@if303: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1440 qdisc noqueue 
    link/ether b2:ef:a6:90:45:a1 brd ff:ff:ff:ff:ff:ff
    inet 10.222.18.238/32 scope global eth0
       valid_lft forever preferred_lft forever
/ # ip route
default via 169.254.1.1 dev eth0 
169.254.1.1 dev eth0 scope link 
/ # ip neigh
172.25.58.2 dev eth0 lladdr ee:ee:ee:ee:ee:ee used 0/0/0 probes 0 STALE
169.254.1.1 dev eth0 lladdr ee:ee:ee:ee:ee:ee used 0/0/0 probes 4 STALE


[root@YZ-25-58-2 supdev]# ip a
303: calie6c32025bf7@if4: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1440 qdisc noqueue state UP 
    link/ether ee:ee:ee:ee:ee:ee brd ff:ff:ff:ff:ff:ff link-netnsid 55
[root@YZ-25-58-2 supdev]# ip route
10.222.18.238 dev calie6c32025bf7 scope link 
[root@YZ-25-58-2 supdev]# cat /proc/sys/net/ipv4/conf/calie6c32025bf7/proxy_arp
1
[root@YZ-25-58-2 supdev]# tcpdump -i calie6c32025bf7 -e -nn
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on calie6c32025bf7, link-type EN10MB (Ethernet), capture size 262144 bytes


16:12:29.866426 b2:ef:a6:90:45:a1 > ff:ff:ff:ff:ff:ff, ethertype ARP (0x0806), length 42: Request who-has 169.254.1.1 tell 10.222.18.238, length 28
16:12:29.866454 ee:ee:ee:ee:ee:ee > b2:ef:a6:90:45:a1, ethertype ARP (0x0806), length 42: Reply 169.254.1.1 is-at ee:ee:ee:ee:ee:ee, length 28

参考

www.lijiaocn.com/%E9%A1%B9%E…

mritd.me/2019/06/18/…