【译】在Docker容器上使用CNI

介绍如何为 Docker 创建的容器使用 CNI 接口配置网络

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
<body>
<html>
<h1><span >Web Server #1 - Running on port 80</span></h1>
</body>
</html>
user@ubuntu-1:~/cni$

正如我们所示,连接Docker容器与上篇文章中直接连接命名空间没有太大不同,实际上可以认为过程是相同的,我们只需要知道Docker把容器的网络命名空间的定义存在哪里就行了。在我们的下一篇文章中,我们将讨论如何通过 CNI 为容器进行DNS相关设置。