0x00 前言
此篇文章是上篇理解CNI(容器网络接口)的后续
原作者:Jon Langemak
原文地址: Using CNI with Docker
译者: N0mansky
0x11 正文
在理解CNI(容器网络接口)(如果没读建议先读下)中,我们通过一个示例介绍了CNI如何将网络命名空间连接到 bridge 接口,CNI负责创建 bridge 网卡并使用 VETH pair 连接 bridge 和命名空间。在本文中,我们将探讨如何为 Docker 创建的容器连接到 bridge,具体步骤和上篇文章讨论的差不多,让我们开始吧。
本文假定您已按照第一篇文章理解CNI(容器网络接口)中的步骤动手操作过了,并已经创建了一个包含CNI二进制文件的'cni'目录(~/cni
)。如果没有,请返回上一篇文章,并按照步骤下载CNI二进制文件。同时您需要安装Docker,我使用的是Docker 1.12版。(译者注:Docker 版本大于 1.12 也是可以的,我的是 18.09 )
首先我们需要执行下面的命令来创建一个 Docker 容器
user@ubuntu-2:~/cni$ sudo docker run --name cnitest --net=none -d jonlangemak/web_server_1
835583cdf382520283c709b5a5ee866b9dccf4861672b95eccbc7b7688109b56
user@ubuntu-2:~/cni$
我们注意到当执行上述命令时,设置的网络为 none。当使用none时,Docker 将为该容器创建一个不会连接任何网络的命名空间。如果我们查看容器,我们应该看到它只会有一个环回接口…
user@ubuntu-2:~/cni$ sudo docker exec cnitest ifconfig
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
user@ubuntu-2:~/cni$
接下来我们要使用CNI将该容器连接到网络。在此之前,我们需要一个给CNI使用的定义和给容器网络本身的一些定义。对于CNI定义,我们将创建一个新定义配置文件,并指定一些选项观察其工作方式。我们使用下面命令创建配置(假设您是在~/cni
中创建该文件)...
cat > mybridge2.conf <<"EOF"
{
"cniVersion": "0.2.0",
"name": "mybridge",
"type": "bridge",
"bridge": "cni_bridge1",
"isGateway": true,
"ipMasq": true,
"ipam": {
"type": "host-local",
"subnet": "10.15.30.0/24",
"routes": [
{ "dst": "0.0.0.0/0" },
{ "dst": "1.1.1.1/32", "gw":"10.15.30.1"}
],
"rangeStart": "10.15.30.100",
"rangeEnd": "10.15.30.200",
"gateway": "10.15.30.99"
}
}
EOF
除了上一篇文章中看到的参数外,我们还添加了以下内容……
- rangeStart:定义CNI给容器分配子网 IP 的起始地址
- rangeEnd:定义CNI给容器分配子网 IP 的结束地址
- gateway:定义网关地址。在上篇文章中我们没有定义,因此CNI在 bridge 接口上用的第一个IP作为网关。
您可能会注意到,此配置中缺少关于 DNS 的配置,这个我们先不提,下篇文章会说。
目前我们已经定义好了网络,我们还需要容器网络命名空间的路径和容器ID。要获取该信息,我们可以用docker inspect
命令。
user@ubuntu-1:~/cni$ sudo docker inspect cnitest | grep -E 'SandboxKey|Id'
"Id": "1018026ebc02fa0cbf2be35325f4833ec1086cf6364c7b2cf17d80255d7d4a27",
"SandboxKey": "/var/run/docker/netns/2e4813b1a912",
user@ubuntu-1:~/cni$
在此示例中,我用grep -E
正则模式来匹配查找容器ID 和 SandboxKey。在 Docker 中,网络命名空间文件位置称为“ SandboxKey”,而“ Id”是 Docker 为容器分配的ID。有了这些信息我们就可以构建调用CNI插件的环境变量了,如下所示:
- CNI_COMMAND= ADD
- CNI_CONTAINERID= 1018026ebc02fa0cbf2be35325f4833ec1086cf6364c7b2cf17d80255d7d4a27
- CNI_NETNS= /var/run/docker/netns/2e4813b1a912
- CNI_IFNAME= eth0
- CNI_PATH=`pwd`
我们将所有内容放到一条命令中,如下所示:
sudo CNI_COMMAND=ADD CNI_CONTAINERID=1018026ebc02fa0cbf2be35325f4833ec1086cf6364c7b2cf17d80255d7d4a27 CNI_NETNS=/var/run/docker/netns/2e4813b1a912 CNI_IFNAME=eth0 CNI_PATH=`pwd` ./bridge < mybridge2.conf
然后运行插件...和我们在上篇文章中看到的一样,插件执行后会将操作的结果以JSON返回
user@ubuntu-1:~/cni$ sudo CNI_COMMAND=ADD CNI_CONTAINERID=1018026ebc02fa0cbf2be35325f4833ec1086cf6364c7b2cf17d80255d7d4a27 CNI_NETNS=/var/run/docker/netns/2e4813b1a912 CNI_IFNAME=eth0 CNI_PATH=`pwd` ./bridge < mybridge2.conf
{
"ip4": {
"ip": "10.15.30.100/24",
"gateway": "10.15.30.99",
"routes": [
{
"dst": "0.0.0.0/0"
},
{
"dst": "1.1.1.1/32",
"gw": "10.15.30.1"
}
]
},
"dns": {}
}user@ubuntu-1:~/cni$
让我们再次查看宿主机和容器网络,看看有什么变化...
user@ubuntu-1:~/cni$ ifconfig
cni_bridge0 Link encap:Ethernet HWaddr 0a:58:0a:0f:14:01
inet addr:10.15.20.1 Bcast:0.0.0.0 Mask:255.255.255.0
inet6 addr: fe80::a464:72ff:fe98:2652/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:536 (536.0 B) TX bytes:648 (648.0 B)
cni_bridge1 Link encap:Ethernet HWaddr 0a:58:0a:0f:1e:63
inet addr:10.15.30.99 Bcast:0.0.0.0 Mask:255.255.255.0
inet6 addr: fe80::88f:bbff:fed9:118f/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:536 (536.0 B) TX bytes:648 (648.0 B)
docker0 Link encap:Ethernet HWaddr 02:42:65:43:f5:a7
inet addr:172.17.0.1 Bcast:0.0.0.0 Mask:255.255.0.0
UP BROADCAST MULTICAST MTU:1500 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
ens32 Link encap:Ethernet HWaddr 00:0c:29:3e:49:51
inet addr:10.20.30.71 Bcast:10.20.30.255 Mask:255.255.255.0
inet6 addr: fe80::20c:29ff:fe3e:4951/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:2568909 errors:0 dropped:67 overruns:0 frame:0
TX packets:2057136 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1000
RX bytes:478331698 (478.3 MB) TX bytes:1336636840 (1.3 GB)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:5519471 errors:0 dropped:0 overruns:0 frame:0
TX packets:5519471 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:2796275357 (2.7 GB) TX bytes:2796275357 (2.7 GB)
veth719c8174 Link encap:Ethernet HWaddr aa:bb:6e:c7:cc:d8
inet6 addr: fe80::a8bb:6eff:fec7:ccd8/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:15 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:648 (648.0 B) TX bytes:1206 (1.2 KB)
vethb125661a Link encap:Ethernet HWaddr fa:54:99:46:65:08
inet6 addr: fe80::f854:99ff:fe46:6508/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:8 errors:0 dropped:0 overruns:0 frame:0
TX packets:15 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:648 (648.0 B) TX bytes:1206 (1.2 KB)
user@ubuntu-1:~/cni$
从宿主机角度来看,我们现在有很多网络接口。cni_bridge0接口和它关联的 VETH pair是上篇文章操作留下的,而cni_bridge1及其关联的VETH pair接口则是我们刚刚创建的。您可以看到cni_bridge1接口的 IP 是我们在CNI网络配置中“gateway”部分的IP地址。您还会注意到有一个docker0接口,它是在安装 Docker 时默认创建的。
我们的容器有何变化呢?让我们看看吧...
user@ubuntu-1:~/cni$ sudo docker exec cnitest ifconfig
eth0 Link encap:Ethernet HWaddr 0a:58:0a:0f:1e:64
inet addr:10.15.30.100 Bcast:0.0.0.0 Mask:255.255.255.0
inet6 addr: fe80::f09e:73ff:fe3e:838c/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:15 errors:0 dropped:0 overruns:0 frame:0
TX packets:8 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:1206 (1.2 KB) TX bytes:648 (648.0 B)
lo Link encap:Local Loopback
inet addr:127.0.0.1 Mask:255.0.0.0
inet6 addr: ::1/128 Scope:Host
UP LOOPBACK RUNNING MTU:65536 Metric:1
RX packets:0 errors:0 dropped:0 overruns:0 frame:0
TX packets:0 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:1
RX bytes:0 (0.0 B) TX bytes:0 (0.0 B)
user@ubuntu-1:~/cni$ sudo docker exec cnitest ip route
default via 10.15.30.99 dev eth0
1.1.1.1 via 10.15.30.1 dev eth0
10.15.30.0/24 dev eth0 proto kernel scope link src 10.15.30.100
user@ubuntu-1:~/cni$
如您所见,容器的网络配置和我们的预期一致...
- IP地址在定义的范围内(10.15.30.100)
- 其接口名为“ eth0”
- 默认路由指向网关IP地址10.15.30.99
- 我们额外加的1.1.1.1/32的下一跳路由为10.15.30.1
最后,我们可以尝试从宿主机访问容器中的服务...
user@ubuntu-1:~/cni$ curl http://10.15.30.100
Web Server #1 - Running on port 80
user@ubuntu-1:~/cni$
正如我们所示,连接Docker容器与上篇文章中直接连接命名空间没有太大不同,实际上可以认为过程是相同的,我们只需要知道Docker把容器的网络命名空间的定义存在哪里就行了。在我们的下一篇文章中,我们将讨论如何通过 CNI 为容器进行DNS相关设置。