Docker容器网络实践

Docker容器网络实践

文章的目的是了解容器网络是如何工作的。当我们用docker run启动一个容器的时候,通常可以通过–network参考来指定docker的网络模型,比如host, bridge, 这样docker runtime会自动为容器创建相应的网络。为了更加深刻地了解容器的网络工作原理,本文不使用docker的自动创建的网络,而是手工地创建一个容器网络,实现host与容器的通信。

最后为了对比docker network,我们也会分析一下docker为我们自动做了什么配置。

本文需要使用到下列的工具与概念:

  • Linux network namespace - 网络栈隔离
  • ip netns:管理network namespace
  • ip link: 管理network device,包含bridge
  • brctl: 管理bridge

期望的结果

本实践将会实现容器与宿主机的通信,模型如下:
网络通信模型
我们会首先创建一个没有网络的docker容器,然后创建网络将容器与宿主机连接在一起。

操作步骤

Step1: 运行一个容器,创建network namespace

> docker run -it --rm --network=none -d --entrypoint /bin/bash centos6.8:1.6

容器启动后会自动创建network namespace,但这个namespace并不会自动地加到linux宿主机的runtime上,也就是你在/var/run/netns看不到容器的network namespace。
现在需要将容器的namespace暴露在宿主机上(类似创建namespace),跟我们使用ip netns add的效果是一样的

先拿到容器的进程Id (root process Id)

> docker inspect d7e -f "{{.State.Pid}}"
17221

然后在host上创建一个softlink 将容器的network namespace暴露出来,下面我们创建一个namespace ContainerA

> ln -sfT /proc/17221/ns/net /var/run/netns/containerA
> ip netns
containerA

Step2: 在宿主机上创建一个bridge br-demo

> brctl addbr br-demo
> brctl show
bridge name     bridge id               STP enabled     interfaces
br-demo         8000.000000000000       no

interfaces为空,现在还没有任何的network device 挂到当前的bridge

Step3: 创建network device
现在我们要创建一对虚拟的network device interface pairs,你可以想象为一根虚拟的网线,网线的一端插到容器的namespace,另外一端插到上面新建的宿主机的bridge br-demo,这样才可以通信

> ip link add veth-a type veth peer name veth-b
> ip link
248: veth-b@veth-a: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 86:62:d0:1e:c2:76 brd ff:ff:ff:ff:ff:ff
249: veth-a@veth-b: <BROADCAST,MULTICAST,M-DOWN> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 02:0f:41:a2:7b:ff brd ff:ff:ff:ff:ff:ff

创建了veth-a, veth-b后,它们还在宿主机的root namespace中,所以上面执行ip link时可以看到它们,下面会这对网络设备插入到插入到两个namespace

Step4:将veth-a挂到容器
虚拟设备veth-a需要绑定到容器的namespace,为了实现这个目的,将veth-a的namespace设置为containerA

> ip link set veth-a netns containerA
> ip netns exec containerA ip link  # 在容器的namespace下查看
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
249: veth-a@if248: <BROADCAST,MULTICAST> mtu 1500 qdisc noop state DOWN mode DEFAULT qlen 1000
    link/ether 02:0f:41:a2:7b:ff brd ff:ff:ff:ff:ff:ff link-netnsid 0

现在可以在容器的namespace下看到veth-a

Step5: 将veth-b挂到Bridge
虚拟设备对的另外一端veth-b需要挂到第二步创建的bridge br-demo

> brctl addif br-demo veth-b
> brctl show
bridge name     bridge id               STP enabled     interfaces
br-demo         8000.8662d01ec276       no              veth-b

可以看到网桥的interfaces已经有一个veth-b

Step6: 分配网络地址
上面的步骤相当于创建了虚假的物理设备,并且把这些设备连接好了,但就如同操作真实的网络设备一样,网络通信需要通过ip address

为容器端的veth-a分配ip address 172.10.0.1/24

> ip netns exec containerA ip addr add 172.10.0.1/24 dev veth-a
> ip netns exec containerA ip link set veth-a up
> ip netns exec containerA ip addr
249: veth-a@if248: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 02:0f:41:a2:7b:ff brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.10.0.1/24 scope global veth-a
       valid_lft forever preferred_lft forever
    inet6 fe80::f:41ff:fea2:7bff/64 scope link
       valid_lft forever preferred_lft forever

为宿主机网桥br-demo分配ip address 172.10.0.2/24

> ip addr add 172.10.0.2/24 dev br-demo
> ip link set br-demo up
> ip addr
247: br-demo: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP qlen 1000
    link/ether 86:62:d0:1e:c2:76 brd ff:ff:ff:ff:ff:ff
    inet 172.10.0.2/24 scope global br-demo
       valid_lft forever preferred_lft forever
    inet6 fe80::8462:d0ff:fe1e:c276/64 scope link
       valid_lft forever preferred_lft forever

验证网络连接

从容器ping宿主机

> docker exec -it d7e539f02361 ping -c 2 172.10.0.2
PING 172.10.0.2 (172.10.0.2) 56(84) bytes of data.
64 bytes from 172.10.0.2: icmp_seq=1 ttl=64 time=0.051 ms
64 bytes from 172.10.0.2: icmp_seq=2 ttl=64 time=0.041 ms

--- 172.10.0.2 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1000ms
rtt min/avg/max/mdev = 0.041/0.046/0.051/0.005 ms

从宿主机ping容器

> ping 172.10.0.1
PING 172.10.0.1 (172.10.0.1) 56(84) bytes of data.
64 bytes from 172.10.0.1: icmp_seq=1 ttl=64 time=0.041 ms
64 bytes from 172.10.0.1: icmp_seq=2 ttl=64 time=0.046 ms
^C
--- 172.10.0.1 ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 999ms
rtt min/avg/max/mdev = 0.041/0.043/0.046/0.007 ms

Docker network

假如不用上面Linux自身提供的网络工具,使用docker network命令也可以实现上面相同的目的。

我们先创建一个bridge network:

> docker network create -d bridge net-test

然后运行一个容器,指定将容器绑定到这个网络

> docker run -it --rm --network=net-test -d --entrypoint /bin/bash vipdocker-f9nub.vclound.com/centos6.8:1.6

Under the hook, 来看看docker在后面为我们做了什么

> brctl show
bridge name     bridge id               STP enabled     interfaces
br-c94eccabb070         8000.02428a56a650       no              veth47c954e
br-demo         8000.8662d01ec276       no              veth-b

这个新建了一个新的bridge br-c94eccabb070,并且有一个新的network interface veth47c954e 连接到这个bridge上

282: br-c94eccabb070: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:8a:56:a6:50 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.1/16 scope global br-c94eccabb070
       valid_lft forever preferred_lft forever
    inet6 fe80::42:8aff:fe56:a650/64 scope link
       valid_lft forever preferred_lft forever
284: veth47c954e@if283: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master br-c94eccabb070 state UP
    link/ether 72:02:c9:b6:80:dd brd ff:ff:ff:ff:ff:ff link-netnsid 3
    inet6 fe80::7002:c9ff:feb6:80dd/64 scope link
       valid_lft forever preferred_lft forever

这个新的bridge分配了ip address 172.18.0.1/1

再来看看容器里面发生了什么,上面我们提到,容器的namespace默认不会添加到宿主机上runtime上,因为在宿主上看不到这个容器的namespace。
首先我们按上面同样的办法将namespace暴露到宿主机上方法参考Step1.

ln -sfT /proc/20119/ns/net /var/run/netns/containerB

现在在宿主机可以查看到这个containerB

> ip netns
containerB (id: 3)
containerA (id: 2)

下面我们就可以知道容器里面添加了一个新的network device eth0

> ip netns exec containerB ip addr
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
283: eth0@if284: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff link-netnsid 0
    inet 172.18.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe12:2/64 scope link
       valid_lft forever preferred_lft forever

通过docker exec也可以查看到同样的变化

> docker exec 39a4d9145fa3  ip addr
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
283: eth0@if284: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
    link/ether 02:42:ac:12:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.18.0.2/16 scope global eth0
       valid_lft forever preferred_lft forever
    inet6 fe80::42:acff:fe12:2/64 scope link
       valid_lft forever preferred_lft forever

总结

通过上面的练习,可以了解docker网络是如何通过namespace来建议网络通信的,我们尝试了两种方式来建立这种网络通信:Linux原生的网络命令和docker network;
它们都可以实现同样的目的,并且通过对比,我们发现它们的原理是一样的,docker network最终也是帮我们自动做了很多需要手工完成的步骤。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值