系列链接

Docker系列之一:Docker介绍及在Ubuntu上安装

Docker系列之二:Docker 入门

Docker系列之三:使用Docker镜像和仓库

Docker系列之四:Dockerfile的使用

Docker系列之五:Volume 卷的使用——以Redis为例

Docker系列之六:Volume 卷的使用——在Dockerfile中的用法

Docker系列之七:Docker 网络(内部网络、容器之间的连接)

Docker系列之八:在Dockerfile中使用多段构建Muti-stage build

Docker系列之九:Docker用于持续集成,构建Jenkins和Docker服务器

Docker系列之十:Docker Compose的使用

简介

到目前为止,我们的所有Docker容器都是公开端口并绑定到本地网络接口的,这样可以把容器里的服务在本地Docker宿主机所在的外部网络上(比如 docker run -p 81:80 nginx, 就是把容器里的80端口公开给宿主机的81端口)公开。除了这种用法外,还有一种就是内部网络

在安装Docker时,会在宿主机上创建一个新的网络接口,名字是docker0,如下图


root@iZbp13z6cxj72rb7bxf0smZ:/home/cong/DockerDemo# ifconfig

docker0   Link encap:Ethernet  HWaddr 02:42:a0:ee:6d:22
          inet addr:172.17.0.1  Bcast:172.17.255.255  Mask:255.255.0.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:135232 errors:0 dropped:0 overruns:0 frame:0
          TX packets:183102 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:10375138 (10.3 MB)  TX bytes:410126623 (410.1 MB)

eth0      Link encap:Ethernet  HWaddr 00:16:3e:12:3d:bb
          inet addr:172.16.252.142  Bcast:172.16.252.255  Mask:255.255.255.0
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:256622292 errors:0 dropped:0 overruns:0 frame:0
          TX packets:250880563 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:1000
          RX bytes:28440795076 (28.4 GB)  TX bytes:33484115651 (33.4 GB)
veth0653184 Link encap:Ethernet  HWaddr da:a2:c2:62:b8:ca
          UP BROADCAST RUNNING MULTICAST  MTU:1500  Metric:1
          RX packets:6 errors:0 dropped:0 overruns:0 frame:0
          TX packets:9 errors:0 dropped:0 overruns:0 carrier:0
          collisions:0 txqueuelen:0
          RX bytes:452 (452.0 B)  TX bytes:624 (624.0 B)

网络接口docker0是一个虚拟的以太网桥,它专门用于连接容器和本地宿主机网络,每个Docker容器都会在这个接口上分配一个IP地址,接口本身的地址是172.17.0.1,子网掩码是255.255.0.0,也就是说Docker会默认使用172.17.X.X作为子网地址。

如果进一步查看Docker宿主机的其它网络,发现在一系列名这以veth开头的网络接口。Docker每创建一个容器就会创建一组互联的网络接口,这组接口就像管道的两端(就是说,从一端发送的数据会在另一端接收到)。这组接口其中一端作为容器里的eth0接口(注意不是上图的eth0),而另一端统一命名为类似veth0653184这种名字,作为宿主机的一个端口。可以把veth接口认为是虚拟网线的一端,这个虚拟网线的一端插在名为docker0的网桥上,另一端插到容器里的eth0上。通过把每个veth*接口绑定到docker0网桥,Docker创建了一个虚拟的子网,这个子网有宿主机和所有的Docker容器共享。

进入容器看看子网的另一端,IP地址为172.17.0.3,如下图。

root@iZbp13z6cxj72rb7bxf0smZ:/home/cong/DockerDemo# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
70ea150288c3        redis               "docker-entrypoint.s…"   About an hour ago   Up About an hour    0.0.0.0:6380->6379/tcp   optimistic_curran
1b25aef5f610        redis               "docker-entrypoint.s…"   About an hour ago   Up About an hour    0.0.0.0:6379->6379/tcp   kind_curran

root@iZbp13z6cxj72rb7bxf0smZ:/home/cong/DockerDemo# docker exec -it 70ea bash

root@70ea150288c3:/data# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST>  mtu 1500
        inet 172.17.0.3  netmask 255.255.0.0  broadcast 172.17.255.255
        ether 02:42:ac:11:00:03  txqueuelen 0  (Ethernet)
        RX packets 1787  bytes 8247431 (7.8 MiB)
        RX errors 0  dropped 0  overruns 0  frame 0
        TX packets 1510  bytes 108655 (106.1 KiB)
        TX errors 0  dropped 0 overruns 0  carrier 0  collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING>  mtu 65536
        inet 127.0.0.1  netmask 255.0.0.0
        loop  txqueuelen 1  (Local Loopback)
        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

 我们从容器内对外通信,观察这个路由,看看是如何建立连接的。

root@70ea150288c3:/data# apt-get update && apt-get install -y traceroute

root@70ea150288c3:/data# traceroute baidu.com
traceroute to baidu.com (123.125.115.110), 30 hops max, 60 byte packets
 1  172.17.0.1 (172.17.0.1)  0.025 ms  0.011 ms  0.009 ms
 2  * * *
 3  11.223.44.53 (11.223.44.53)  4.664 ms 11.223.48.189 (11.223.48.189)  5.392 ms 11.223.44.53 (11.223.44.53)  4.753 ms
 4  * 11.223.48.6 (11.223.48.6)  5.342 ms 11.223.48.142 (11.223.48.142)  5.624 ms
 5  11.223.80.42 (11.223.80.42)  1.225 ms 11.223.80.38 (11.223.80.38)  1.336 ms  1.337 ms
 6  106.11.37.50 (106.11.37.50)  1.316 ms  1.528 ms 116.251.118.30 (116.251.118.30)  1.495 ms
 7  42.120.247.62 (42.120.247.62)  4.186 ms 42.120.247.70 (42.120.247.70)  1.692 ms 42.120.247.66 (42.120.247.66)  1.558 ms
 8  124.160.190.57 (124.160.190.57)  2.119 ms 124.160.190.69 (124.160.190.69)  2.384 ms 124.160.190.85 (124.160.190.85)  1.644 ms
 9  124.160.189.117 (124.160.189.117)  7.897 ms 124.160.189.121 (124.160.189.121)  9.376 ms 124.160.189.113 (124.160.189.113)  7.0                                                           31 ms
10  219.158.107.217 (219.158.107.217)  29.105 ms  29.066 ms  28.828 ms
11  61.49.214.2 (61.49.214.2)  29.539 ms  29.504 ms  29.322 ms
12  61.51.115.110 (61.51.115.110)  31.945 ms  31.897 ms  31.632 ms
13  61.49.168.98 (61.49.168.98)  30.156 ms 202.106.43.66 (202.106.43.66)  28.249 ms 61.49.168.102 (61.49.168.102)  30.589 ms
14  * * *

 可以看到,容器地址后的下一跳是宿主网络上docker0接口的网关ip 172.17.0.1 。

不过Docker网络还有另一个部分配置才能允许建立连接:防火墙规则和NAT配置,这些配置允许Docker在宿主网络和容器间路由。如下是宿主机上的IPTables NAT 配置。

root@iZbp13z6cxj72rb7bxf0smZ:/home/cong/DockerDemo# iptables -t nat -L -n
Chain PREROUTING (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  0.0.0.0/0            0.0.0.0/0            ADDRTYPE match dst-type LOCAL

Chain INPUT (policy ACCEPT)
target     prot opt source               destination

Chain OUTPUT (policy ACCEPT)
target     prot opt source               destination
DOCKER     all  --  0.0.0.0/0           !127.0.0.0/8          ADDRTYPE match dst-type LOCAL

Chain POSTROUTING (policy ACCEPT)
target     prot opt source               destination
MASQUERADE  all  --  172.20.0.0/16        0.0.0.0/0
MASQUERADE  all  --  172.17.0.0/16        0.0.0.0/0
MASQUERADE  all  --  172.18.0.0/16        0.0.0.0/0
MASQUERADE  tcp  --  172.17.0.2           172.17.0.2           tcp dpt:6379
MASQUERADE  tcp  --  172.17.0.3           172.17.0.3           tcp dpt:6379

Chain DOCKER (2 references)
target     prot opt source               destination
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
RETURN     all  --  0.0.0.0/0            0.0.0.0/0
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:6379 to:172.17.0.2:6379
DNAT       tcp  --  0.0.0.0/0            0.0.0.0/0            tcp dpt:6380 to:172.17.0.3:6379

注意这里默认容器是无法访问的,从宿主网络与容器通信时,必须明确指定打开的端口,上面的DNAT(即目标NAT)这个规则中,就是把容器里的访问(端口6379)路由到Docker宿主机的6380端口。

既然是内部网络,那们直接通过IP地址访问容器里的redis,查看容器的网络地址,并使用redis-cli 连接,发现是可以使用ip直接与redis容器通信的,如下:

root@iZbp13z6cxj72rb7bxf0smZ:~# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
70ea150288c3        redis               "docker-entrypoint.s…"   6 days ago          Up 6 days           0.0.0.0:6380->6379/tcp   optimistic_curran
1b25aef5f610        redis               "docker-entrypoint.s…"   6 days ago          Up 6 days           0.0.0.0:6379->6379/tcp   kind_curran

root@iZbp13z6cxj72rb7bxf0smZ:~# docker inspect -f '{{.NetworkSettings.IPAddress}}' 70ea
172.17.0.3

root@iZbp13z6cxj72rb7bxf0smZ:~# redis-cli -h 172.17.0.3
172.17.0.3:6379> get foo
"1"

但这里会有一个问题,如果重启容器的话,Docker会改变容器的IP地址,要在应用程序中对redis容器的IP地址做硬编码是行不通的,这里就引出了Doker Networking.

Doker Networking

它允许用户创建自己的网络,容器可在这个网络中互相通信。我们先来熟悉一下操作Networking的基本命令。

#创建一个新的网络congnet
root@iZbp13z6cxj72rb7bxf0smZ:~# docker network create congnet
a627c0ddc08828706882f2ae0ae1fb91f6fbb6e339d40c34bf84121dd6ffe97d
#查看所有网络
root@iZbp13z6cxj72rb7bxf0smZ:~# docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
0a5324c3bd83        bridge              bridge              local
a627c0ddc088        congnet             bridge              local
fa685cfac4f3        host                host                local
22f6181538a9        none                null                local
#检查congnet网络信息
root@iZbp13z6cxj72rb7bxf0smZ:~# docker network inspect congnet
[
    {
        "Name": "congnet",
        "Id": "a627c0ddc08828706882f2ae0ae1fb91f6fbb6e339d40c34bf84121dd6ffe97d",
        "Created": "2018-11-12T11:51:58.67769031+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.21.0.0/16",
                    "Gateway": "172.21.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]
#删除网络congnet
root@iZbp13z6cxj72rb7bxf0smZ:~# docker network rm congnet
congnet

接下来,我们需要两个容器,一个是redis容器(已有6379端口),一个是nginx容器(需要创建),并将这两个容器放在同一个网络congnet(需要创建)中,在redis容器里通过nginx容器的容器名mynginx(即docker 内部DNS name) 访问nginx容器。

#查看正在运行的容器
root@iZbp13z6cxj72rb7bxf0smZ:~# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
70ea150288c3        redis               "docker-entrypoint.s…"   6 days ago          Up 44 minutes       0.0.0.0:6380->6379/tcp   optimistic_curran
1b25aef5f610        redis               "docker-entrypoint.s…"   6 days ago          Up 6 days           0.0.0.0:6379->6379/tcp   kind_curran

#创建网络congnet
root@iZbp13z6cxj72rb7bxf0smZ:~# docker network create congnet
c4f47431e5b1e22c1901bdcb91d2de4d53aa7347d0ce496dd0b17d11b6aaccf8
#创建nginx容器,名为mynginx,并加入到网络congnet中
root@iZbp13z6cxj72rb7bxf0smZ:~# docker run -itd -p 80:80 --name mynginx --net congnet nginx
78761e99c06f6a4892e78c78d2a2eeeca4957ae7c3acade2ce42668d4e1b2e22

#将已存在的redis容器加入到网络congnet中
root@iZbp13z6cxj72rb7bxf0smZ:~# docker network connect congnet 1b25

#检查网络,发现网络中的Containers中有两个,nginx容器的IP为172.24.0.2,redis容器IP为172.24.0.3
root@iZbp13z6cxj72rb7bxf0smZ:~# docker network inspect congnet
[
    {
        "Name": "congnet",
        "Id": "c4f47431e5b1e22c1901bdcb91d2de4d53aa7347d0ce496dd0b17d11b6aaccf8",
        "Created": "2018-11-12T12:24:11.2083913+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.24.0.0/16",
                    "Gateway": "172.24.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "1b25aef5f610668a210e5bfb92c0cd9bf3cf05260e8d3376a676d509463fe4f2": {
                "Name": "kind_curran",
                "EndpointID": "9bc2e6be3022f43ea344dcee0ff03111796cb089ccd18ef5710990dad8efc131",
                "MacAddress": "02:42:ac:18:00:03",
                "IPv4Address": "172.24.0.3/16",
                "IPv6Address": ""
            },
            "78761e99c06f6a4892e78c78d2a2eeeca4957ae7c3acade2ce42668d4e1b2e22": {
                "Name": "mynginx",
                "EndpointID": "3059ee174de65d5cb5ac3be60f1d27afcceb675ac933f7949564c869e656a9c3",
                "MacAddress": "02:42:ac:18:00:02",
                "IPv4Address": "172.24.0.2/16",
                "IPv6Address": ""
            }
        },
        "Options": {},
        "Labels": {}
    }
]

#进入到redis容器中,查看hosts,并没有mynginx
root@iZbp13z6cxj72rb7bxf0smZ:~# docker exec -it 1b25 bash
root@1b25aef5f610:/data# cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.17.0.2      1b25aef5f610
172.24.0.3      1b25aef5f610
#安装curl
root@1b25aef5f610:/data# apt-get update -y && apt-get install curl -yqq
...
done.
#重点:通过容器名访问nginx容器
root@1b25aef5f610:/data# curl mynginx
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
    body {
        width: 35em;
        margin: 0 auto;
        font-family: Tahoma, Verdana, Arial, sans-serif;
    }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>

<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>

<p><em>Thank you for using nginx.</em></p>
</body>
</html>

#安装ping
root@1b25aef5f610:/data# apt-get install iputils-ping -y
...

#通过容器名也是可以ping通的
root@1b25aef5f610:/data# ping mynginx
PING mynginx (172.24.0.2) 56(84) bytes of data.
64 bytes from mynginx.congnet (172.24.0.2): icmp_seq=1 ttl=64 time=0.065 ms
64 bytes from mynginx.congnet (172.24.0.2): icmp_seq=2 ttl=64 time=0.074 ms

--- mynginx ping statistics ---
9 packets transmitted, 9 received, 0% packet loss, time 7998ms
rtt min/avg/max/mdev = 0.055/0.078/0.091/0.011 ms

#回头进入nginx容器,看看里面的hostname和hosts
root@iZbp13z6cxj72rb7bxf0smZ:~# docker exec -it mynginx bash

#这里可以看出,容器的ID就是容器的主机名
root@78761e99c06f:/# cat /etc/hostname
78761e99c06f

# hosts中也没有mynginx
root@78761e99c06f:/# cat /etc/hosts
127.0.0.1       localhost
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
172.24.0.2      78761e99c06f

运行完上面的命令后,发现在同一网络中的两个容器,它们之间是可以直接使用容器名进行通信的,但容器名并不保存在/etc/hosts中,容器名直接被当作了网络内部的域名。

到这里,Docker的网络就介绍到这里,演示完成后,我们需要断开网络,可以使用如下命令。

root@iZbp13z6cxj72rb7bxf0smZ:~# docker ps
CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS                    NAMES
78761e99c06f        nginx               "nginx -g 'daemon of…"   41 minutes ago      Up 41 minutes       0.0.0.0:80->80/tcp       mynginx
1b25aef5f610        redis               "docker-entrypoint.s…"   6 days ago          Up 6 days           0.0.0.0:6379->6379/tcp   kind_curran
root@iZbp13z6cxj72rb7bxf0smZ:~# docker network disconnect congnet mynginx
root@iZbp13z6cxj72rb7bxf0smZ:~# docker network disconnect congnet 1b25
root@iZbp13z6cxj72rb7bxf0smZ:~# docker network inspect congnet
[
    {
        "Name": "congnet",
        "Id": "c4f47431e5b1e22c1901bdcb91d2de4d53aa7347d0ce496dd0b17d11b6aaccf8",
        "Created": "2018-11-12T12:24:11.2083913+08:00",
        "Scope": "local",
        "Driver": "bridge",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "default",
            "Options": {},
            "Config": [
                {
                    "Subnet": "172.24.0.0/16",
                    "Gateway": "172.24.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {},
        "Options": {},
        "Labels": {}
    }
]

相关链接

https://docs.docker.com/network/

https://docs.docker.com/v17.09/engine/userguide/networking/configure-dns/

总结

“桥接”和“NAT”的区别:使用NAT模式可以实现在虚拟系统里访问互联网,虚拟系统无法和本局域网中的其他真实主机进行通讯。而使用桥接模式下虚拟出来的操作系统就像是局域网中的一台独立的主机,它可以访问网内任何一台机器。所以Docker0是属于桥接模式。

 

博主生活艰难,支付宝扫如下二维码救助,十块八块都行。

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐