Connect to Every Container:Docker 网络理论
Docker 被广泛用于服务器上,为各种服务提供相对独立的轻量级运行环境,容器与容器之间互不影响。
而正是 Docker 容器的良好隔离性质,使得容器中的服务并不能直接访问到其他容器中的服务,导致各种不便。
而 Docker 网络同样是容器环境的一部分,将容器从宿主机的网络环境中独立出来,相互连接。
1 Docker 网络模式
Docker 提供了四种网络模式,分别是 host、bridge、none、container。
Docker 在安装之时会默认创建 host、bridge、none 模式对应的网络。
1.1 host
你可以使用--network host
参数指定容器使用 host 网络模式。
docker run --network host --name alpine_container alpine ip addr
演示使用的 Alpine 镜像。由于空 Alpine 镜像在启动后会立即退出,因此我们直接让其执行ip addr
命令。
被设定为 host 网络模式的容器不再拥有自己的虚拟网卡,从而拥有和宿主机一样的 IP 地址。这一点我们可以在宿主机中执行ip addr
来验证。
host 网络模式显然便于访问容器提供的服务,但同时也降低了隔离性。
而且容器映射到外部的端口将被忽略。
docker run --network host --name alpine_container -p 4700:4700 alpine
WARNING: Published ports are discarded when using host network mode
1.2 none
使用--network none
参数指定容器使用 none 网络模式。
docker run --network none --name alpine_container alpine 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
被设定为 none 网络模式的容器将获得一个完全隔离的网络环境,容器内部只能使用 localhost 上的网络设备。
1.3 bridge
bridge 网络模式是 Docker 的默认网络。
Docker 进程启动时会在宿主机上创建一个名为docker0
的虚拟网桥。宿主机上启动的 Docker 容器会默认连接到这个虚拟网桥上,从而所有容器都通过docker0
连接在一个二层网络中。
在这种模式下,Docker 会为每个容器创建虚拟网卡,容器与容器之间可以通过这个虚拟网卡指定的 IP 地址进行通信。
我们运行两个 Alpine 容器,并进入容器内部执行ip addr
。
598: eth0@if599: <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
600: eth0@if601: <BROADCAST,MULTICAST,UP,LOWER_UP,M-DOWN> mtu 1500 qdisc noqueue state UP
link/ether 02:42:ac:11:00:03 brd ff:ff:ff:ff:ff:ff
inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0
valid_lft forever preferred_lft forever
注意到容器 1 的 IP 地址为 172.17.0.2,容器 2 的 IP 地址为 172.17.0.3。
在容器 2 内部 ping 容器 1 的 IP,证明两个容器是可以通过虚拟网桥相通。
PING 172.17.0.2 (172.17.0.2): 56 data bytes
64 bytes from 172.17.0.2: seq=0 ttl=64 time=0.439 ms
64 bytes from 172.17.0.2: seq=1 ttl=64 time=0.123 ms
64 bytes from 172.17.0.2: seq=2 ttl=64 time=0.137 ms
64 bytes from 172.17.0.2: seq=3 ttl=64 time=0.084 ms
^C
--- 172.17.0.2 ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.084/0.195/0.439 ms
1.4 container
container 网络模式需要在创建容器时指定一个已经创建好的容器,新创建的容器将不会拥有自己的虚拟网卡,而是和指定容器共用网卡。
docker run --name alpine_1 alpine sleep 3600
docker run --network container:alpine_1 --name alpine_2 alpine sleep 3600
同样分别进入容器查看ip addr
。
602: eth0@if603: <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
602: eth0@if603: <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
这说明二者共用同一虚拟网卡、同一 IP。同样,若两个容器中同时监听同一端口,将会发生冲突。
需要注意的是,此时容器 2 并无自己的虚拟网卡,若将容器 1 停止,容器 2 将只剩下 localhost 网络。
2 Docker 自定义网络
上述几种方式显然不够灵活,而自定义网络可以将任意容器添加进网络中。实际开发中也更建议使用自定义网络。
创建自定义网络:
docker network create CUSTOM_NETWORK
在创建容器时连接到自定义网络:
docker run --net CUSTOM_NETWORK --name CONTAINER_NAME IMAGE
连接已有容器到自定义网络:
docker network connect CUSTOM_NETWORK CONTAINER_NAME
断开容器连接的自定义网络:
docker network disconnect CUSTOM_NETWORK CONTAINER_NAME
移除自定义网络:
docker network rm CUSTOM_NETWORK
注意,如果某个容器在创建之时指定了自定义网络,那么这个自定义网络无法删除。
3 实战:一种为任意容器提供网络代理的优雅方法
网络代理是在服务器上有着非常普遍的应用,然而对于 Docker 容器内部的服务而言,很难访问到宿主机上的网络代理。
如果代理同样也是部署在容器中,就可以通过上述提到的 bridge 网络模式借助 IP 地址进行通信。
看起来很合理,对吧?但是 IP 地址是会变的,因此这种方法并不可行。
我们选择建立自定义网络。在自定义网络中,Docker 实现了一个内部 DNS 服务器,使得处于同一网络内的容器可以通过容器名称进行通信。
这里采用 Clash 提供网络代理,SillyTavern 作为需要网络代理的应用程序容器。
3.1 创建容器并连接网络
为了方便管理,这里使用 Docker Compose。
version: '3'
services:
clash:
image: laoyutang/clash-and-dashboard:latest
container_name: clash
restart: always
logging:
options:
max-size: 1m
volumes:
- ./data/config.yaml:/root/.config/clash/config.yaml
ports:
- "7888:8080"
- "7890:7890"
SillyTavern仓库中已经提供docker-compose.yml
文件。
分别执行docker compose up -d
。
需要说明的是,使用 Docker Compose 创建的容器都会自动创建一个_default
网络。
如clash
容器在创建之时,就会同时创建clash_default
网络。
接下来将sillytavern
容器连接至clash
网络。
docker network connect clash_default sillytavern
进入sillytavern
容器内部检查网络能否接通。
docker exec -it sillytavern sh
Ping一下clash
容器。得益于自定义网络的内嵌 DNS 服务,我们可以使用容器名称进行通信。
ping clash
PING clash (192.168.16.2): 56 data bytes
64 bytes from 192.168.16.2: seq=0 ttl=64 time=0.216 ms
64 bytes from 192.168.16.2: seq=1 ttl=64 time=0.100 ms
64 bytes from 192.168.16.2: seq=2 ttl=64 time=0.109 ms
64 bytes from 192.168.16.2: seq=3 ttl=64 time=0.088 ms
^C
--- clash ping statistics ---
4 packets transmitted, 4 packets received, 0% packet loss
round-trip min/avg/max = 0.088/0.128/0.216 ms
说明可以接通clash
容器。
3.2 启用 SillyTavern 代理
在 SillyTavern 的配置文件config.yaml
中启用代理。
requestProxy:
enabled: true
url: http://clash:7890
bypass:
- localhost
- 127.0.0.1
重启容器应用设置。
3.5 管理 Clash 服务
这个 Clash 镜像提供了一个管理面板,借助它可以管理 Clash 服务。
其默认端口为7888
,默认的docker-compose.yml
已经映射出了这个端口,只需在云服务器安全组(如果有的话)和系统防火墙中放行7888
端口即可。
进入管理面板后需要在设置中打开允许来自局域网的连接。
3.6 (可能有用)关于 Clash 管理面板的安全性
如你所见,这个 Clash 面板比较简单,当你把端口暴露到公网上后,任何人都可以访问你的面板,极大降低了安全性。
我们可以使用 VSCode 自带的端口转发来解决这一问题。
首先关闭云服务器安全组(如果有的话)和系统防火墙中放行的7888
端口。
将 VSCode 连接到你的服务器,然后转发7888
端口到你的本地电脑即可。
3.7 (可能有用)关于配置文件自动更新
大部分情况下 Clash 的配置文件来自服务提供商,因此我们需要及时更新配置文件。
写个脚本定时执行就好啦:
#!/bin/bash
cd [Clash的Docker-compose.yml所在目录]
wget -O ./data/config.yaml [服务提供商提供的订阅链接]
docker restart clash
如果你细心观察的话还会发现,当重启clash
容器后,允许来自局域网的连接这一设置可能会自动关闭。
这是由于你的服务提供商提供的配置文件中allow-lan
这一字段为false
造成的。
只需要在脚本docker restart clash
前面一行添加:
sed -i 's/allow-lan: false/allow-lan: true/' ./data/config.yaml
将false
替换为true
即可。