前段时间看到过一些容器互联技术,主要是flannel这个项目,算是勉强能看懂在讲什么。然后慢慢了解一些容器网络,容器互联的一些原理,这篇文章主要从几个角度来做一个记录,随着认识的不断深入,本文会进行持续修改,直至完结。
- 容器互联网络知识
- 同一宿主机容器互联
- 同一局域网下不同宿主机容器互联
- 跨地区容器互联
容器互联网络知识
dockerd启动时会创建一个网桥docker0,并分配一个IP,会为每个运行的容器创建一个虚拟网卡设备,并桥接到docker0,如果进入docker容器使用ifconfig命令,会看到其中一张网卡会配置了IP地址,我目前对能够连接容器的定义是,能否直接访问容器内部的IP访问容器服务(不是宿主机的IP)
root@iZwz9f03v1u6izgt67j1yaZ:~# ifconfig vethbd02ec8 --------------->
vethbd02ec8 Link encap:Ethernet HWaddr 32:7e:68:80:f7:bc
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:27 errors:0 dropped:0 overruns:0 frame:0
TX packets:28 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:2478 (2.4 KB) TX bytes:2520 (2.5 KB)
root@iZwz9f03v1u6izgt67j1yaZ:~# brctl show ------------------> 显示网桥信息
bridge name bridge id STP enabled interfaces
docker0 8000.02423c8cd9c7 no vethbd02ec8
root@iZwz9f03v1u6izgt67j1yaZ:~#
这里插一个容器端口映射相关技术,这种通过宿主机端口映射的方式也一样能够访问容器,
root@iZwz9f03v1u6izgt67j1yaZ:~# iptables -t nat -nvL OUTPUT
Chain OUTPUT (policy ACCEPT 8 packets, 594 bytes)
pkts bytes target prot opt in out source destination
0 0 DOCKER all -- * * 0.0.0.0/0 !127.0.0.0/8 ADDRTYPE match dst-type LOCAL
root@iZwz9f03v1u6izgt67j1yaZ:~# iptables -t nat -nvL DOCKER
Chain DOCKER (2 references)
pkts bytes target prot opt in out source destination
0 0 RETURN all -- docker0 * 0.0.0.0/0 0.0.0.0/0
0 0 DNAT tcp -- !docker0 * 0.0.0.0/0 0.0.0.0/0 tcp dpt:58422 to:192.168.100.3:58422
目前看到的情况是通过iptables配置目的地址转换来实现宿主机端口映射到容器端口,但是会有点缺陷:
- 程序是运行在容器当中的,由于这种端口映射的存在,其他应用必须通过宿主机的IP进行访问,如果有服务发现,容器内部运行的程序在服务注册的时候肯定是无法知道宿主机的IP,这样服务注册会存在问题。需要通过其他手段拿到宿主机IP,然后在服务注册时使用宿主机IP。
容器互联范围从小到大可以划分为三种:
- 同一宿主机的容器互联
- 同一局域网,不同宿主机的容器互联
- 跨地区容器互联
一图胜千言:
可以将docker0类比路由器的lan口,下面连了两个PC(container),物理网卡eth0类比路由器的wan口。
同一宿主机容器互联
在同一个宿主机内运行的两个容器,两个container的互联通过docker0进行,网桥属于二层设备,所以在ping其他容器ip之后,在容器查看arp表能看到ip对应的mac地址信息.
root@163800d221d6:/# arp -n
Address HWtype HWaddress Flags Mask Iface
192.168.100.1 ether 02:42:3c:8c:d9:c7 C eth0
192.168.100.5 ether 02:42:c0:a8:64:05 C eth0
root@163800d221d6:/#
root@163800d221d6:/# route -n --------> 每个容器的下一跳IP指向docker0的ip
Kernel IP routing table
Destination Gateway Genmask Flags Metric Ref Use Iface
0.0.0.0 192.168.100.1 0.0.0.0 UG 0 0 0 eth0
192.168.100.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0
这个阶段可以类比家庭网络中同一路由器下两台pc之间的通信。docker0类比路由器的内部的lan口,两个container插在路由器的两个网口。
同一局域网不同宿主机之间容器的通信
容器都运行在同一宿主机的情况相对比较少,很多情况需要容器跨宿主机之间通信,比如在宿主机A下运行的app-server需要访问宿主机B下的etcd。
这个问题就转换成一个网络问题,怎么把container之间的网络都调通。在192.168.100.2 ping 192.168.101.2能够ping的通。我目前能想到的完全不用写代码的方法是
1. 在左边宿主机上配置iptables命令
iptables -t nat -I PREROUTING --dst 192.168.101.0/24 -j DNAT --to 192.168.10.12
这样能保证左边容器能够访问右边容器
在192.168.100.2访问192.168.101.2时,数据包先经过iptables的PREROUTING hook点,直接被做DNAT到右边宿主机宿主机上。
右边容器访问左边容器同理。
这种纯网络配置的方法肯定是没问题的,只要宿主机能够在局域网,想把网络调通还不容易,随便找个网工都能干。但是会有两个问题:
- 这种方式其实也属于端口映射,DNAT到目的宿主机之后其实真正访问的是目的宿主机,只是对于访问的容器而言发起的目的IP是对方容器IP而已,在目的宿主机一样需要像dockerd一样,做物理机到容器的端口映射。
- 发挥一下想象力,业务在发展,需要再增加一台宿主机,这时候所有宿主机都需要增加一条配置,来支持当前已存在的宿主机能够访问新增的宿主机上运行的容器。同时,新增的宿主机需要新增n条配置,以支持访问已经存在的宿主机上运行的容器。这个简直是噩梦,十个网络工程师都搞不定。
- IP地址冲突的问题,不同宿主机容器之间IP地址冲突了,可能访问到的是同一宿主机下另外的容器。
要解决以上的问题,肯定是需要引入新的程序来更加自动化的实现新增宿主机配置,ip地址分配管理的。flannel 提供了一张图。
其实看了这张图很多人基本都大概了解flannel的思路了,采用一种类似vpn的技术,现在好像更喜欢称之为overlay技术,不确定两者是否等价。通常这类技术会依赖虚拟网卡设备技术,这类虚拟网卡设备相关技术可以参考以前写的另外一篇文章tun/tap虚拟网卡设备,以及基于tun/tap实现的微pn,代码很短。总的来说是一种ip over tcp/udp的技术。
跨地区容器互联
和之前同事聊的时候,提到把flannel换成gtun试试吧,当时只是开玩笑,但是后面考虑了下,确实是没问题的,但是需要调整代码。
gtun的ip地址分配以及数据包的路由是在服务端进行的,但是有一个缺陷,就是分配的只是一个ip地址,不是ip地址段,路由也只是根据ip地址进行路由,也没有根据地址段进行路由,将这两个功能加上,就可以使用gtun完成跨地区容器互联了。
代码提交到github当中cframe,路由目前采用手动配置的方式,只是验证可行性,目前项目正在开发当中,有兴趣的读者可以一起参与做这个项目。
以前一直不理解sdn/sdwan和k8s,容器这些有什么关系,现在算是慢慢感觉整体解决问题的方法应该是类似的。