zl程序教程

您现在的位置是:首页 >  工具

当前栏目

Docker容器实战十:容器网络

2023-06-13 09:14:21 时间

本文将讲解关于容器网络的技术原理。

一. 容器网络模型(CNM)

在最初的版本中,Docker的网络功能集成在Docker Daemon的代码中,这使得整体架构变得臃肿且缺乏灵活性,无法适应复杂的网络需求。为此,Docker公司在后面提出了CNM(Container Network Model,可译为容器网络模型)规范,并将网络功能独立出来作为一个组件,即Libnetwork网络库。

Libnetwork的设计遵循着CNM规范,在该规范中包含着三个重要概念:Sandbox(沙盒)、Endpoint(端点)和 Network(网络)。

1. Sandbox(沙盒)

Sandbox可以看成是在容器中独立的网络空间,在里面包含了容器的网络栈配置,包括网络接口、路由表和DNS设置等。Sandbox的标准实现基于Linux中的Network Namespace特性。一个Sandbox可以包含多个Endpoint,并且连接到不同的网络中。

2. Endpoint(端点)

Endpoint的作用在于将容器连接到网络,Endpoint通常由一对Veth Pair(成对出现的一种虚拟网络设备接口)组成,其中一端在Sandbox中,另一端连接到网络中。

3.Network(网络)

可以连接多个Endpoint的一个子网。

CNM示例图:

如图所示,三个容器中都有独立的Sandbox,而每个Sandbox中包含自己的Endpoint,并且通过Endpoint连接到网络。可以看到第二个容器有两个Endpoint,这使得其可以同时连接两个不同的网络。而容器1和容器3由于处于不同的网络中,彼此之间将无法直接通信 。

使用CNM规范的好处,在于容器可以不用关心网络的具体实现,只要能提供网络接入点给到容器接入即可。而对于网络的具体实现,则交由驱动来完成。这种方式解耦了容器与网络,容器可以接入不同类型的网络,而第三方只要按照CNM规范开发驱动,即可保证容器的无缝接入,架构的灵活性得到很大的提升。

二. 容器网络

在安装完Docker后会自动创建三个网络,我们通过 docker network ls 命令可查看相关的网络信息。

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
ad87b0b5afaa   bridge    bridge    local
19bd38b4d728   host      host      local
a83dd07d2ba1   none      null      local

Docker默认创建的网络类型有bridge、host和null,在启动容器时,可以通过--network 选项指定使用的网络。如果不指定,则默认会使用bridge网络。

1. bridge网络

当启动docker时,会自动生成一个名为docker0的Linux bridge(网桥)。bridge可以看成是一个软件交换机,负责对挂载在它上面的接口进行包转发。

$ ifconfig docker0
docker0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.1  netmask 255.255.0.0  broadcast 172.17.255.255
        inet6 fe80::42:8bff:fe36:8c00  prefixlen 64  scopeid 0x20<link>
        ether 02:42:8b:36:8c:00  txqueuelen 0  (Ethernet)
        RX packets 0  bytes 0 (0.0 B)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 5  bytes 446 (446.0 B)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

运行brctl show 命令查看网桥信息,可以看到在没有启动任何容器的情况下,docker0的 "interfaces" 处为空,表明该网桥目前未挂载任何接口。

$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02425cf9d61c       no

现在,我们启动一个容器后,再次来查看网桥情况。可以看到,当前网桥已经挂载了一个接口,名称为veth1ec2b44。

$ docker run -d --name nginx -p 80:80 nginx:1.20-alpine
07a46c03d17b40545a090dab60eac9a75bfa8050c572ae2c330ca98700ce68d5

$ brctl show
bridge name     bridge id               STP enabled     interfaces
docker0         8000.02428b368c00       no              veth1ec2b44

我们查看一下容器里面的网络配置,可以看到容器的网卡名称为eth0@if11,它与挂载到网桥的veth1ec2b44接口即是一对Veth Pair。

$ docker exec nginx ip addr
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    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
10: eth0@if11: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP 
    link/ether 02:42:ac:11:00:02 brd ff:ff:ff:ff:ff:ff
    inet 172.17.0.2/16 brd 172.17.255.255 scope global eth0
       valid_lft forever preferred_lft forever
前面我们说过,Veth Pair是成对的网络设备,此时的容器网络架构如下所示:
Veth Pair就如一根网线的两端,其中一端收到的网络包会从另一端出去,这使得连接到同一个网桥的容器可以彼此通信,在容器与网桥间共同组成了一个虚拟局域网。

当bridge网络被创建时,系统会为其分配一个子网段,用于容器使用。当容器接入时,会从IP池中分配IP给到容器网卡,而容器的网关则会指向bridge,也即是docker0的IP地址。

2. host网络

在启动容器时使用--network host指定为本机网络,则容器将与宿主机共享网络栈,此时容器会使用主机的IP以及其他网络配置。

如下:

$ docker run -d --network host --name nginx2 nginx:1.20-alpine
6f2023e6e714f9bb212bca9aec12dd7c3befd51a01d28216cea12c64136f6924

$ docker exec -it nginx2 ip addr            
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN qlen 1000
    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
2: ens33: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast state UP qlen 1000
    link/ether 00:0c:29:7b:a8:71 brd ff:ff:ff:ff:ff:ff
    inet 192.168.214.112/24 brd 192.168.214.255 scope global dynamic noprefixroute ens33
       valid_lft 2592730sec preferred_lft 2592730sec
    inet6 fe80::f992:e993:bd8e:2de6/64 scope link noprefixroute 
       valid_lft forever preferred_lft forever
......

使用host网络时,对容器做端口映射将会无效, 容器会忽略 -p 所指定的端口,而直接在宿主机网络层面开启对应端口。例如,容器中如果开放了80端口,那么此时宿主机也将开放同样端口。

host网络相比bridge网络具有更高的性能,同时在容器有大量端口要开放的时候 ,也会省事很多。但同时也增加了不安全性,此时可在容器内对主机的网络栈进行操作,所以并不推荐使用。

3. null网络

null网络会禁用容器的网络栈,使得容器与外部隔离。在启动时通过 --network none 实现。此时,容器除lo外,将不再有其他网卡,完全与外部网络隔离。

$ docker run -d --network none --name nginx3 nginx:1.20-alpine
513e0ce37dab8dd6cb0b15fe0311af5ca7a050378ebcdb3b2c20d5a6bc39c2b5

$ docker exec -it nginx3 ifconfig
lo        Link encap:Local Loopback  
          inet addr:127.0.0.1  Mask:255.0.0.0
          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:1000 
          RX bytes:0 (0.0 B)  TX bytes:0 (0.0 B)

这种场景一般使用很少,只有在某些不需要与外界联网,并且对安全要求非常高的程序才有可能用到。

三. 外部网络访问

docker0本身是作为宿主机的一个本地接口,因此,容器默认情况下可以访问到宿主机自身的网络。但当容器需要访问外部网络时,则需要宿主机做一层NAT转发。

在安装docker时,默认会启用Linux的包转发功能,如下:

$ sudo sysctl net.ipv4.ip_forward
net.ipv4.ip_forward = 1

查看宿主机iptable的nat表上面的POSTROUTING链规则,可看到有一条转发规则。

$ sudo iptables -t nat -nvL POSTROUTING
Chain POSTROUTING (policy ACCEPT 71 packets, 5396 bytes)
 pkts bytes target       prot  opt   in   out        source            destination         
 0     0    MASQUERADE    all   --   *  !docker0   172.17.0.0/16      0.0.0.0/0

该规则的作用是当docker0收到来自容器网段(172.17.0.0/16)的网络包时,将其交给MASQUERADE处理。MASQUERADE与传统的SNAT相似,会将网络包的源IP替换为网卡IP,这样可以保证容器的网络包能够正常外出。

那么,外部网络又是如何访问容器的呢?在前面的学习中,我们知道启动容器时可以使用 -p 的方式,将容器的端口映射出来。在这个过程中,Docker会在本地添加相应的iptable规则,用于网络包的DNAT转换。

以本示例的容器为例,这里我们开放了80端口。当查看相关的iptable规则时,可看到多了一个关于80端口的DNAT规则。

$ docker run -d --name nginx -p 80:80 nginx:1.20-alpine
07a46c03d17b40545a090dab60eac9a75bfa8050c572ae2c330ca98700ce68d5

$ iptables -t nat -nvL  DOCKER
Chain DOCKER (2 references)
 pkts bytes target     prot opt in     out     source               destination         
......       
    0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:80 to:172.17.0.2:80

该规则将访问到主机80端口的网络包,转发到容器地址(172.17.0.2)的80端口,以此实现外部网络对容器的访问。

此时,容器的整体网络架构如下图所示:

附录: Docker网络常用命令

1. 创建网络

除了Docker默认生成的网络外,用户也可以创建自定义的网络。docker network create命令用于创建新的网络。

示例:此处创建一个名为mynet的bridge网络。

$ docker network create -d bridge mynet
592da9b6f8197cb7f11bae42f83f4429d9a97371a4aaccd3701d2998181763e8

2. 列出所有网络

docker network ls命令用于列出当前所有网络。

示例:

$ docker network ls
NETWORK ID     NAME      DRIVER    SCOPE
12bb6c359b1c   bridge    bridge    local
19bd38b4d728   host      host      local
12e7db06ad9b   mynet     bridge    local
a83dd07d2ba1   none      null      local

3. 查看网络详情

docker network inspect命令可查看一个网络的详细信息,包括接入的容器、网络配置等。

示例:

$ docker network inspect mynet
[
    {
        "Name": "mynet",
        "Id": "12e7db06ad9bf7aaaedb0cb33042ea48170d4ef2026da9964acba1cb984f441b",
        "Created": "2022-05-30T03:40:51.977036849-04:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.18.0.0/16",
                    "Gateway": "172.18.0.1"
......

4. 连接网络

docker network connect命令用于将容器连接到网络,通过该命令可以更改容器的接入网络。

示例:

$ docker network connect mynet nginx

5. 删除网络

docker network rm 命令可用于删除指定网络。只有当网络没有容器连接时,才能正常删除。

示例:

$ docker network rm mynet
mynet

感谢你的阅读,如果觉得本文有帮助,欢迎分享到朋友圈和技术群