网络是虚拟化技术中最复杂的部分,当然也是Docker应用中的一个重要环节。Docker使用Linux的Namespace进行资源隔离,这些资源就包含了网络资源,对网络的隔离通过Network Namespace进行实现。一个Network Namespace提供了一份独立的网络环境,包括网卡、路由、Iptable规则等都与其他的Network Namespace隔离,一般Docker容器都会分配一个独立的Network Namespace。

Docker的四种网络模式:

  • host模式:共享宿主机网络,使用--net=host指定

  • bridge模式:桥接网络模式,使用--net=bridge指定

  • container模式:使用容器网络,使用--net=container:NAME_or_ID指定

  • none模式:不指定网络,使用--net=none指定(默认为该选项,可不指定)

可以使用docker network ls查看docker网络模式:

[root@centos7 ~]# docker network lsNETWORK ID          NAME                DRIVER              SCOPE08892cb5e787        bridge              bridge              local43fd42bc0f76        host                host                local364fc46102ac        none                null                local

host模式:

上面已经提到Docker通过Network Namespace来进行网络隔离,而一般情况下每个Docker都会分配一个独立的Network Namespace,但是如果在Docker容器启动时使用了host模式,那么这个容器不会获取一个独立的Network Namespace,而是和宿主机公用一个Network Namespace。同样的,容器将不会虚拟出自己的网卡,配置自己的IP等,而是使用宿主机的IP和端口。

001.png

host模式具有如下特点:

  • host模式下的容器没有隔离的 network namespace;

  • 容器的 IP 地址同 Docker host 的 IP 地址;

  • 需要注意容器中服务的端口号不能与 Docker host 上已经使用的端口号相冲突;

  • host 模式能够和其它模式共存。

具体示例:

# 检查宿主机网络地址[root@centos7 ~]# ifconfig |grep -A1 eth0 eth0: flags=4163
  mtu 1500        inet 192.168.49.40  netmask 255.255.255.0  broadcast 192.168.49.255# 创建一个docker容器[root@centos7 ~]# docker run -itd --name host-network --network host centos ad59993de7cf910cea4cfa20980968191d5ae7793d4f6018bf1015be16eea536# 进入docker容器中,查看容器网络地址[root@centos7 ~]# docker exec -it host-network /bin/bash[root@centos7 /]# ifconfig eth0eth0: flags=4163
  mtu 1500        inet 192.168.49.40  netmask 255.255.255.0  broadcast 192.168.49.255        inet6 fe80::20c:29ff:fe52:f550  prefixlen 64  scopeid 0x20
        ether 00:0c:29:52:f5:50  txqueuelen 1000  (Ethernet)        RX packets 8070  bytes 9993439 (9.5 MiB)        RX errors 0  dropped 0  overruns 0  frame 0        TX packets 1548  bytes 138564 (135.3 KiB)        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0# 使用inspect命令查看容器的网络设置信息[root@centos7 ~]# docker inspect host-network --format='{
{json .NetworkSettings.Networks}}' | jq .{  "host": {    "IPAMConfig": null,    "Links": null,    "Aliases": null,    "NetworkID": "43fd42bc0f76afeb16dbb847ea5f4d19410cffc1cb870618c318eeb2b1ac0c5c",    "EndpointID": "786130e14678b5399e39a5d472a5f9913562d98241f9ffb2969fbffad1d3e028",    "Gateway": "",    "IPAddress": "",    "IPPrefixLen": 0,    "IPv6Gateway": "",    "GlobalIPv6Address": "",    "GlobalIPv6PrefixLen": 0,    "MacAddress": ""  }}

bridge模式:

bridge模式是Docker默认的网络设置,此模式会为每一个容器分配Network Namespace、设置IP等,并将该宿主机上的Docker容器连接到一个虚拟网桥上。当Docker server启动时,会在主机上创建一个名为docker0的虚拟网桥,此主机上启动的Docker容器会连接到这个虚拟网桥上。虚拟网桥的工作方式和物理交换机类似,这样主机上的所有容器就通过交换机连在了一个二层网络中。接下来就要为容器分配IP了,Docker会选择一个和宿主机不同的IP地址和子网分配给docker0,连接到docker0的容器就从此子网中选择一个未占用的IP使用。

001.png

bridge模式的特点:

  • 使用一个 linux bridge,默认为 docker0;

  • 使用 veth 对,一端在容器的网络 namespace 中,另一端在 docker0 上;

  • 该模式下Docker Container不具有一个公有IP,因为宿主机的IP地址与veth pair的 IP地址不在同一个网段内;

  • Docker采用 NAT 方式,将容器内部的服务监听的端口与宿主机的某一个端口port 进行“绑定”,使得宿主机以外的网络可以主动将网络报文发送至容器内部;

  • 外界访问容器内的服务时,需要访问宿主机的 IP 以及宿主机的端口 port;

  • NAT 模式基于三层网络上的实现方式,故肯定会影响网络的传输效率;

  • 容器拥有独立、隔离的网络栈,以便于使得容器和宿主机以外的网络通过NAT建立通信;

具体示例:

# 创建一个桥接模式的容器

[root@centos7 ~]# docker run -itd --name bridge-network -p 8080:80/tcp centos30919e3ea354ad01233cacfbf580a95b6035eca3e3b3f449dbb50c337e54af6b

# 查看宿主机docker0网段

[root@centos7 ~]# ifconfig docker0 |grep -A1 docker0docker0: flags=4163
  mtu 1500        inet 172.17.0.1  netmask 255.255.0.0  broadcast 0.0.0.0

# 查看网桥信息

[root@centos7 ~]# brctl showbridge namebridge idSTP enabledinterfacesdocker08000.0242a680db7enoveth6e54224[root@centos7 ~]# ifconfig veth6e54224: flags=4163
  mtu 1500        inet6 fe80::e8ed:feff:fe31:b60d  prefixlen 64  scopeid 0x20

# 进入容器内部查看网络信息

[root@centos7 ~]# docker exec -it bridge-network /bin/bash[root@30919e3ea354 /]# ifconfig |grep -A1 eth0eth0: flags=4163
  mtu 1500        inet 172.17.0.2  netmask 255.255.0.0  broadcast 0.0.0.0[root@30919e3ea354 /]# exitexit

# 查看宿主机的iptables规则,可以看到DOCKER链多了一条DPORT转发规则

[root@centos7 ~]# iptables -t nat -vnLChain PREROUTING (policy ACCEPT 106 packets, 7376 bytes) pkts bytes target     prot opt in     out     source               destination             4   172 DOCKER     all  --  *      *       0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCALChain INPUT (policy ACCEPT 4 packets, 946 bytes) pkts bytes target     prot opt in     out     source               destination         Chain OUTPUT (policy ACCEPT 2 packets, 124 bytes) pkts bytes target     prot opt in     out     source               destination             0     0 DOCKER     all  --  *      *       0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCALChain POSTROUTING (policy ACCEPT 2 packets, 124 bytes) pkts bytes target     prot opt in     out     source               destination           102  6430 MASQUERADE  all  --  *      !docker0  172.17.0.0/16        0.0.0.0/0               0     0 MASQUERADE  tcp  --  *      *       172.17.0.2           172.17.0.2           tcp dpt:80Chain DOCKER (2 references) pkts bytes target     prot opt in     out     source               destination             0     0 RETURN     all  --  docker0 *       0.0.0.0/0            0.0.0.0/0               0     0 DNAT       tcp  --  !docker0 *       0.0.0.0/0            0.0.0.0/0            tcp dpt:8080 to:172.17.0.2:80

# 在宿主机上尝试ping容器网络

[root@centos7 ~]# ping 172.17.0.2PING 172.17.0.2 (172.17.0.2) 56(84) bytes of data.64 bytes from 172.17.0.2: icmp_seq=1 ttl=64 time=0.042 ms64 bytes from 172.17.0.2: icmp_seq=2 ttl=64 time=0.060 ms--- 172.17.0.2 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 999msrtt min/avg/max/mdev = 0.042/0.051/0.060/0.009 ms

# 在宿主机上尝试访问容器内服务

[root@centos7 ~]# curl-I http://localhost:8080/HTTP/1.1 200 OKDate: Fri, 01 Mar 2019 07:28:37 GMTServer: Apache/2.4.6 (CentOS)Last-Modified: Fri, 01 Mar 2019 07:27:22 GMTETag: "d-58303567354cf"Accept-Ranges: bytesContent-Length: 13Content-Type: text/html; charset=UTF-8[root@centos7 ~]# curl -I http://localhost:8080/curlhello,world.

container模式:

这个模式指定新创建的容器和已经存在的一个容器共享一个Network Namespace,而不是和宿主机共享。新创建的容器不会创建自己的网卡,配置自己的IP,而是和一个指定的容器共享IP、端口范围等。同样,两个容器除了网络方面,其他的如文件系统、进程列表等还是隔离的。两个容器的进程可以通过lo网卡设备通信。

002.png

具体示例:

# 创建一个容器,指定该容器使用bridge-network的网络

[root@centos7 ~]# docker run -itd --name container-network --network container:bridge-network centos39191c944656dd5564bd0c4b2c172a04d64c810117521f6b34e57cfeef377659

# 使用docker inspect命令查看bridge-network容器的网络设置

[root@centos7 ~]# docker inspect bridge-network --format='{
{json .NetworkSettings}}'|jq .{  "Bridge": "",  "SandboxID": "0e0376642d908c6844ea1b936420e2e69432c09f4fa1e35a95ac735d866ae229",  "HairpinMode": false,  "LinkLocalIPv6Address": "",  "LinkLocalIPv6PrefixLen": 0,  "Ports": {    "80/tcp": [      {        "HostIp": "0.0.0.0",        "HostPort": "8080"      }    ]  },  "SandboxKey": "/var/run/docker/netns/0e0376642d90",  "SecondaryIPAddresses": null,  "SecondaryIPv6Addresses": null,  "EndpointID": "6df44381183d3af48dbe4d549ebeca2049c9201fec408b0c9525e34623bfaf81",  "Gateway": "172.17.0.1",  "GlobalIPv6Address": "",  "GlobalIPv6PrefixLen": 0,  "IPAddress": "172.17.0.2",  "IPPrefixLen": 16,  "IPv6Gateway": "",  "MacAddress": "02:42:ac:11:00:02",  "Networks": {    "bridge": {      "IPAMConfig": null,      "Links": null,      "Aliases": null,      "NetworkID": "683b6614154a16dfcf42e6934a9bf2490dd719e4d83c0a1f7d72e1f90b56c432",      "EndpointID": "6df44381183d3af48dbe4d549ebeca2049c9201fec408b0c9525e34623bfaf81",      "Gateway": "172.17.0.1",      "IPAddress": "172.17.0.2",      "IPPrefixLen": 16,      "IPv6Gateway": "",      "GlobalIPv6Address": "",      "GlobalIPv6PrefixLen": 0,      "MacAddress": "02:42:ac:11:00:02"    }  }}

# 使用docker inspect命令查看container-network容器的网络设置(输出的结果类似host模式容器的结果)

[root@centos7 ~]# docker inspect container-network --format='{
{json .NetworkSettings}}'|jq .{  "Bridge": "",  "SandboxID": "",  "HairpinMode": false,  "LinkLocalIPv6Address": "",  "LinkLocalIPv6PrefixLen": 0,  "Ports": null,  "SandboxKey": "",  "SecondaryIPAddresses": null,  "SecondaryIPv6Addresses": null,  "EndpointID": "",  "Gateway": "",  "GlobalIPv6Address": "",  "GlobalIPv6PrefixLen": 0,  "IPAddress": "",  "IPPrefixLen": 0,  "IPv6Gateway": "",  "MacAddress": "",  "Networks": {}}

# 进入容器内部,查看容器的ip地址,发现跟bridge-network容器是一致的

[root@centos7 ~]# docker exec -it container-network /bin/bash[root@30919e3ea354 /]# ifconfig |grep -A1 eth0eth0: flags=4163
  mtu 1500        inet 172.17.0.2  netmask 255.255.0.0  broadcast 0.0.0.0

# 注意:因为此时两个容器要共享一个 network namespace,因此需要注意端口冲突情况,否则第二个容器将无法被启动。

none模式:

none模式下,Docker容器拥有自己的Network Namespace,但是,并不为Docker容器进行任何网络配置和构造任何网络环境。

Docker 容器采用了none 网络模式,那么容器内部就只能使用loopback网络设备,不会再有其他的网络资源。Docker Container的none网络模式意味着不给该容器创建任何网络环境,容器只能使用127.0.0.1的本机网络。

用户自定义网络:

通过命令可以向docker中添加自定义网络,通过自定义网络可以更灵活的配置容器的网络,你可以控制哪些容器可以通信,那些容器可以访问宿主机,甚至自动将容器名称解析为IP地址等。你可以根据需要创建任意数量的网络,并且可以在任何给定时间将容器连接到这些网络中的零个或多个网络。此外,您可以连接并断开网络中的运行容器,而无需重新启动容器。当容器连接到多个网络时,其外部连接通过第一个非内部网络以词法顺序提供。

具体示例:

# 创建一个docker自定义网络,驱动为bridge,名称为br0

[root@centos7 ~]# docker network create -d bridge br04fb906c45b587cecfe9670ed9a62d5b76147c83ab1fe17002d5afeabfece42b4

# 查看docker自定义网络br0的详细信息

[root@centos7 ~]# docker network inspect br0[    {        "Name": "br0",        "Id": "4fb906c45b587cecfe9670ed9a62d5b76147c83ab1fe17002d5afeabfece42b4",        "Created": "2019-03-02T00:11:26.905361179+08:00",        "Scope": "local",        "Driver": "bridge",        "EnableIPv6": false,        "IPAM": {            "Driver": "default",            "Options": {},            "Config": [                {                    "Subnet": "172.18.0.0/16",                    "Gateway": "172.18.0.1"                }            ]        },        "Internal": false,        "Attachable": false,        "Containers": {},        "Options": {},        "Labels": {}    }]

# 查看宿主机上br0对应的网卡信息

[root@centos7 ~]# brctl showbridge namebridge idSTP enabledinterfacesbr-4fb906c45b588000.02423ff261c3no[root@centos7 ~]# ifconfig br-4fb906c45b58br-4fb906c45b58: flags=4099
  mtu 1500        inet 172.18.0.1  netmask 255.255.0.0  broadcast 0.0.0.0        ether 02:42:3f:f2:61:c3  txqueuelen 0  (Ethernet)        RX packets 0  bytes 0 (0.0 B)        RX errors 0  dropped 0  overruns 0  frame 0        TX packets 0  bytes 0 (0.0 B)        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

# 创建两个使用docker自定义网络的容器

[root@centos7 ~]# docker run -itd --name docker01 --network br0 centos61638d20c2a2b115e47f0c5fe5a37612613e3e775b8929a74d21d73d1d397e36[root@centos7 ~]# docker run -itd --name docker02 --network br0 centosf7711bce271687669473368d4a40e37a500e0ed81d468373c0b40ce7930acb70

# 进入docker01,尝试去ping另一个容器

[root@centos7 ~]# docker exec -it docker01 /bin/bash[root@61638d20c2a2 /]# ping docker02PING docker02 (172.18.0.3) 56(84) bytes of data.64 bytes from docker02.br0 (172.18.0.3): icmp_seq=1 ttl=64 time=0.152 ms64 bytes from docker02.br0 (172.18.0.3): icmp_seq=2 ttl=64 time=0.054 ms^C--- docker02 ping statistics ---2 packets transmitted, 2 received, 0% packet loss, time 1002msrtt min/avg/max/mdev = 0.054/0.103/0.152/0.049 ms

# 进入docker02,尝试去ping另外一个容器

[root@centos7 ~]# docker exec -it docker02 /bin/bash[root@f7711bce2716 /]# ping docker01PING docker01 (172.18.0.2) 56(84) bytes of data.64 bytes from docker01.br0 (172.18.0.2): icmp_seq=1 ttl=64 time=0.145 ms64 bytes from docker01.br0 (172.18.0.2): icmp_seq=2 ttl=64 time=0.040 ms64 bytes from docker01.br0 (172.18.0.2): icmp_seq=3 ttl=64 time=0.065 ms64 bytes from docker01.br0 (172.18.0.2): icmp_seq=4 ttl=64 time=0.040 ms^C--- docker01 ping statistics ---4 packets transmitted, 4 received, 0% packet loss, time 3004msrtt min/avg/max/mdev = 0.040/0.072/0.145/0.043 ms

修改容器的DNS和配置文件:

同一个Docker镜像可以启动很多Docker容器,但各自容器的主机名并不一样,即主机名并非是被写入镜像中。

通过在容器中运行mount命令可以查看/etc/目录下有3个文件,即容器启动后被虚拟文件覆盖的,分别是/etc/hostname、/etc/hosts、/etc/resolv.conf:

[root@centos7 ~]# docker exec -it bridge-network /bin/bash[root@30919e3ea354 /]# mount ......./dev/mapper/centos-root on /etc/resolv.conf type ext4 (rw,relatime,data=ordered)/dev/mapper/centos-root on /etc/hostname type ext4 (rw,relatime,data=ordered)/dev/mapper/centos-root on /etc/hosts type ext4 (rw,relatime,data=ordered)......

注意:这里三个文件在容器内部是可写的,可以通过修改对应的文件使配置临时生效,但是一旦容器停止所做的修改都将丢失。

修改DNS和Hostname的方法:

docker容器的dns和主机名配置可通过Docker提供的参数进行相关设置,配置方式如下:

        -h HOSTNAME 或 --hostname=HOSTNAME:设置容器的主机名,此名称会写入/etc/hostname和/etc/hosts文件中,也会在容器的bash提示符看到。但是在外部,容器的主机名是无法查看的,不会出现在其他容器的hosts文件中,即使使用docker ps命令也无法查看。此参数是docker run命令的参数,而非docker daemon的启动参数。

        --dns=IP_ADDRESS...:为容器配置DNS,写入/etc/resolv.conf中。该参数可以在docker daemon 启动的时候设置,也可以在docker run时设置,默认为8.8.8.8或8.8.4.4。

注意:对以上3个文件的修改不会被docker commit保存,即不会保存在镜像中,重启容器也会导致修改失效。

配图摘自:https://www.cnblogs.com/itzgr/p/10179003.html