Kubernetes网络系列之(十八)Docker网络实现



我们来详细观察&理解Docker容器是如何实现它的网络的,以及解析一个容器是如何与本机、本机中的容器、其他Host、其他Host中的容器 等场景下分别是如何进行通信的详细原理
本机容器网络大概生成过程:首先每个容器对应创建一个network namespace;然后将所有的容器的network namespace连接到Bridge网桥(docker0)上,使得容器间互相处于一个局域网内,方便连通

Docker的网络命名空间

docker使用namespace实现容器网络,但是我们使用ip netns命令却无法在主机上看到任何network namespace,这是因为默认docker把创建的网络命名空间链接文件隐藏起来了。

有2种进入这个“空间”的命令,我们以前的课都讲过。

1.1 通过ip netns exec
启动一个容器

1
docker run -tid ubuntu:18.04

这个时候,查询 network namespace,却发现是空的。

因为 ip netns 是去检查 /var/run/netns 目录的。而Docker这个软件,故意把容器对应的ns信息记录到了 /var/run/docker/netns 目录。所以ip netns查出来就是空的,我们想办法把ns信息翻出来就行。

(1)恢复关联-方式1

所以我们,把这2个目录关联一下:

1
ln -s /var/run/docker/netns  /var/run/netns

接下来再敲:

1
ip netns

就可以看到容器的 network namespace 了。

不过可以发现列出来的ns的ID,和对应容器的ID,不是同一个。 它两有一个映射值,可以通过 docker inspect 的结果查到对应关系:

1
docker inspect 070044b2738f

image

(2)恢复关联-方式2

找到容器主pid:

1
pid=$(docker inspect -f '{{.State.Pid}}' ${container_id})

创建对应的ns记录:

1
2
3
mkdir -p /var/run/netns/

ln -sfT /proc/$pid/ns/net /var/run/netns/$container_id

image

1.2 通过 nsenter
找到容器里app的主pid。

1
docker inspect ecf8689d3297

image

跑到这个pid对应的世界(namespace)里去。

1
nsenter -n -t 25977

这个时候,就是在容器里面的网络空间角度敲命令啦。

例如:

(1)查询网卡:

1
ifconfig

image

(2)抓包:

1
tcpdump -i eth0 -n

image

2 Docker 使用的Linux Bridge
关于Docker为什么要加个Bridge来连通所有的容器?其实不加Bridge,网络也能通。只是说有了Bridge,就有了覆盖更多复杂场景的能力。

这里直接引用Docker自己的描述:通过Bridge,可以使得连到这个Bridge的容器互相通信;同时和没有连到这个Bridge的容器保持网络隔离。(大意就是:容器可以按网络分组)

image

3 我怎么和本机Host主机通信
假设我就是那个Docker容器,那么我是如何与主机Host通信的呢。

3.1 本机Host怎么访问我
主机Host访问自己节点上的容器,答案是:直接访问就行了。

咱们先来看Host主机的路由表:

image

因为根据路由信息:

所有发往Docker容器的地址(即目标为 172.17.* )的报文,—> 统统走给 —> docker0 网卡。而根据上面Bridge章节可以知道,这个docker0就是Bridge网桥,它是连着所有容器的Veth网线的。所以这个报文会发送到所有容器里面,那么目标容器就会应答你。

image

3.2 我怎么访问所在的Host主机
容器访问自己的Host主机,答案也是直接访问。

Docker容器里面,网络很简单,就一个eth0。所以你往外发报文,都是经过eth0网卡。而这个网卡是一个veth网线的一头,所以这个报文就会到达Bridge网桥(即docker0)。而这个网桥就是Host主机的一个网卡,所以就到达了目的地。

image

综上,Docker容器和Host主机,是可以自由通信的。

3.3 本机其他容器怎么访问我
这个问题,直接去Bridge章节看一下就行了。大家都通过docker0这个Bridge焊在一起,所以直接互访就行了。

4 我怎么和别的Host主机通信
别的Host主机,就是“爸爸(所在节点)的兄弟”。

4.1 别的Host怎么访问我
跨节点访问容器时,由于不知道目标容器是住在哪台Host主机上(要访问那个容器,必须经过它所在的Host),所以为了访问一个目标容器专门设置一条路由规则(当我访问xxx容器时,请经过yyy虚拟机,这种规则),并不方便。所以一般直接用端口映射来访问。

即:目标容器所在的Host主机IP + 指定端口。然后当报文到达指定目标的Host主机时,通过指定端口Nat进入容器。

image

举例:

(1) 在192.168.1.9这台机器上启动一个Nginx容器:注意这里-p参数告诉Host,请将主机上面的80端口,作为进入我的NAT入口。

1
docker run -rm  -p 80:80 nginx

(2) 然后咱们再另外找一台机器(与刚才192.168.1.9 这一台能连通)。

跨节点访问刚才那个容器:

1
curl -vvv 192.168.1.9:80

image

这里可以看到,主机跨节点访问容器时,必须通过指定端口NAT进入到目标容器。

image

直接访问IP是不通的(没有路由信息)

4.2 我怎么访问别的Host
这个答案比较简单:只要我所在的Host能通的地方,我就也能与它连通。
image

你看主机上有一条:源地址NAT规则。

1
iptables -t nat –nL

意思是容器里面发出的报文,把源地址改成主机的,然后往外发。意思是跟主机一样往外发报文就完了(不理解的参见以前的NAT课程)。

image

4.3 别的Host上的容器怎么访问我
也就跨节点的2个容器怎么互相通信。一般是2种方式:

4.3.1 NAT端口映射。
即通过指定目标端口,穿到容器中。

根据4.2章节可知:容器-》目标 == 容器所在Host节点 –》目标。

根据4.1章节可知:访问目标容器 == 指定IP+Port

所以容器里面直接用:指定IP+Port访问目标容器就行了。

举例:

(1) 在192.168.1.9这台机器上启动一个Nginx容器:注意这里-p参数告诉Host,请将主机上面的80端口,作为进入我的NAT入口。

1
docker run -rm  -p 80:80 nginx

(2) 然后咱们再另外找一台机器(与刚才192.168.1.9 这一台能连通)。

进入一个容器,然后跨节点访问刚才那个容器:

1
2
3
docker exec -it ea60d3290dd5 /bin/bash

curl -vvv 192.168.1.9:80

image

4.3.2 隧道网络打通所有容器。
这种就稍微复杂一点,就是让所有容器处于同一个局域网中。

隧道模式,实现方式基本是各显神通了。除了新版本Docker有自己的实现,各大厂商也都有不一样的实现,比如现在各种flannel,weave,calico等现实。

原理嘛,请参考早期的 隧道课程。(可以照着物理世界去考虑,好比为很多物理机里面的所有VM创建虚拟局域网类似)

image