容器网络实质上也是由 docker 为应用程序所创造的虚拟环境的一部分,它能让应用从宿主机操作系统的网络环境中独立出来,形成容器自有的网络设备、IP 协议栈、端口套接字、IP 路由表、防火墙等等与网络相关的模块。
在 docker 网络中,有三个比较核心的概念:沙盒(Sandbox)、网络(Network)、端点(Endpoint)。
这三者形成了容器网络模型(Container Network Model)。
容器网络模型是给容器引擎提供的一套标准的网络对接范式,而在 docker 中,实现这套范式的是 docker 封装的 libnetwork 模块。
而 docker 对于网络的具体实现,在发展过程中也逐渐抽象形成了统一的抽象定义。通过这些抽象定义,便可以对 docker 网络的实现方式进行不同的变化。
目前 docker 官方为我们提供了五种 Docker 网络驱动,分别是:bridge driver、host driver、overlay driver、macvlan driver、none driver。
由于 Docker 提倡容器与应用共生的轻量级容器理念,所以容器中通常只包含一种应用程序;要让一个容器连接到另外一个容器,可以在使用 docker create
或 docker run
命令创建容器时通过 --link
选项进行配置。
例如,这里创建一个 MySQL 容器,将 Web 应用的容器连接到这个 MySQL 容器上,打通两个容器间的网络,实现它们之间的网络互通:
$ sudo docker run -d --name mysql -e MYSQL_RANDOM_ROOT_PASSWORD=yes mysql
$ sudo docker run -d --name webapp --link mysql webapp:latest
此时,mysql 与 webapp 之间的网络已经打开了,webapp 使用 jdbc 连接 mysql 时,只需要将容器的网络命名填入到连接地址中,就可以访问需要连接的容器了,那么 url 可以这么写 :
String url = "jdbc:mysql://mysql:3306/webapp";
连接地址中的 mysql 就就像是域名,docker 会将其指向 MySQL 容器的 ip 地址。这样有一个好处就是从开发环境切换到测试环境的时候不需要频繁切换 ip 地址了。
需要注意的是,虽然容器间的网络打通了,但并不意味着可以任意访问被连接容器中的任何服务。docker 为容器网络增加了一套安全机制,只有容器自身允许的端口,才能被其他容器所访问。
docker 开放端口可以通过镜像定义,也可以在创建容器时通过 --expose
参数定义,可以指定多个端口:
$ sudo docker run -d --name redis --expose 6379 --expose 26379 redis:5.0.7
在 docker ps
的结果中的 PORTS 就是容器暴露给其他容器访问的端口:
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
93aa5326931f redis:5.0.7 "docker-entrypoint.s…" 31 seconds ago Up 30 seconds 6379/tcp, 26379/tcp redis
可以看到,前面在名称为 redis 的容器开放的端口 6379 与 26379 是成功开放了的,在连接到 redis 容器后,只能对这两个端口进行访问。
容器开放端口类似打开容器的防火墙,具体能不能通过这个端口访问容器中的服务,还需要容器中的应用监听并处理来自这个端口的请求。
纯粹的通过容器名来打开容器间的网络通道缺乏一定的灵活性,在 docker 里还支持连接时使用别名来摆脱容器名的限制。
$ sudo docker run -d --name webapp --link mysql:database webapp:latest
在这里使用 --link
的形式,连接到 MySQL 容器,并设置它的别名为 database。需要在 Web 应用中使用 MySQL 连接时,就可以使用 database 来代替连接地址了。
$ String url = "jdbc:mysql://database:3306/webapp";
容器能够互相连接的前提是两者同处于一个网络中(这里的网络是指容器网络模型中的网络)。
网络可以理解为 docker 所虚拟的子网,而容器网络沙盒可以看做是虚拟的主机,只有当多个主机在同一子网里时,才能互相看到并进行网络数据交换。
当启动 docker 服务时,它会创建一个默认的 bridge 网络,而创建的容器在不专门指定网络的情况下都会连接到这个网络上。所以前面之所以能够把 webapp 容器连接到 mysql 容器上,其原因是两者都处于 bridge 这个网络上。
通过 docker inspect
命令查看容器,可以在 Network 部分看到容器网络相关的信息 :
$ sudo docker inspect redis
[
{
......
"NetworkSettings": {
......
"Networks": {
"bridge": {
"IPAMConfig": null,
"Links": null,
"Aliases": null,
"NetworkID": "57cba05dde080f4d84a3e100cce1fc568fb3cf7d04ae0706f6cd7d945bbd2f50",
"EndpointID": "900184a27a54f27517b85c5c39ef2114ccd3b3131c67d1ebcfe772f779f7ef09",
"Gateway": "172.17.0.1",
"IPAddress": "172.17.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:11:00:02",
"DriverOpts": null
}
}
}
}
]
这里我们能够看到 mysql 容器在 bridge 网络中所分配的 ip 地址,其自身的端点、mac 地址,bridge 网络的网关地址等信息。
在没有明确指定容器网络时,容器都会连接到这个网络中。
在 docker 里,可以创建网络,形成自己定义虚拟子网的目的。
在 docker 命令行里与网络相关的命令都以 docker network
开头,其中创建网络的命令是 docker network create
。
$ sudo docker network create -d bridge my-bridge-name
通过 -d
可以为新网络指定驱动的类型,其值可以是 bridge、host、overlay、maclan、none,也可以是其他网络驱动插件所定义的类型。这里使用的是 bridge driver(当不指定网络驱动时,docker 也会默认采用 bridge driver 作为网络驱动)。
通过 docker network ls
或者 docker network list
可以查看 docker 中已经存在的网络 :
$ sudo docker network ls
NETWORK ID NAME DRIVER SCOPE
57cba05dde08 bridge bridge local
3a00cd261b29 host host local
3974f46bcd21 my-bridge-name bridge local
aefd8de0db63 none null local
之后在创建容器时,可以通过 --network
来指定容器所加入的网络,一旦这个参数被指定,容器便不会默认加入到 bridge 网络中了(但是仍然可以通过 --network bridge
让其加入)。
sudo docker run -d --name nginx --network my-bridge-name nginx:1.17.6
通过 docker inspect
查看容器的网络:
root@dreamboy:~# sudo docker inspect nginx
[
{
"Id": "c708e06fdb3268cf807d36dec72ebe7d9ac1e02182f3ccaebd9472209367d99f",
"Created": "2019-12-05T07:59:52.316873323Z",
"Path": "nginx",
"Args": [
"-g",
"daemon off;"
],
"State": {
......
"Networks": {
"my-bridge-name": {
"IPAMConfig": null,
"Links": null,
"Aliases": [
"c708e06fdb32"
],
"NetworkID": "3974f46bcd2114b369d1f2e14e087e4fb850f829ca96bb20989c209506ac23ac",
"EndpointID": "119dce14105a7291613f2506efcc571b514f77c8957e8b6ddfd855d48e5768d1",
"Gateway": "172.18.0.1",
"IPAddress": "172.18.0.2",
"IPPrefixLen": 16,
"IPv6Gateway": "",
"GlobalIPv6Address": "",
"GlobalIPv6PrefixLen": 0,
"MacAddress": "02:42:ac:12:00:02",
"DriverOpts": null
}
}
}
}
]
可以看到,容器所加入网络已经变成了 my-bridge-name
这个网络了。
前面都是容器直接通过 docker 网络进行的互相访问,在实际使用中,还有一个非常常见的需求,就是在容器外通过网络访问容器中的应用。最简单的一个例子,外部客户端访问容器内的 web 应用。
在 docker 中,有端口映射的功能,通过 docker 端口映射功能可以把容器的端口映射到宿主操作系统的端口上,当从外部访问宿主操作系统的端口时,数据请求就会自动发送给与之关联的容器端口。
在创建容器时通过 -p
或者 --publish
选项实现端口映射
$ sudo docker run -d --name nginx -p 8081:80 -p 443:443 nginx:1.17.6
使用端口映射选项的格式是 -p
,其中:ip 是宿主操作系统的监听 ip,可以用来控制监听的网卡,默认为 0.0.0.0,也就是监听所有网卡。host-port 和 container-port 分别表示映射到宿主操作系统的端口和容器的端口,这两者是可以不一样的,可以将容器的 80 端口映射到宿主操作系统的 8081 端口,传入 -p 8081:80
即可。
执行命令后,就可以在容器列表里看到端口映射的配置:
$ sudo docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
adac71332d9d nginx:1.17.6 "nginx -g 'daemon of…" About a minute ago Up About a minute 0.0.0.0:443->443/tcp, 0.0.0.0:8081->80/tcp nginx
打印的结果里用符号 ->
标记了端口的映射关系。