前言

有时,我们需要在容器内访问宿主机上的某个服务,例如 MySQL。

方案一

使用 host 模式启动服务。

在默认情况下,Docker 服务会采用桥接模式启动。这意味着 Docker 容器会使用 Docker 自建的虚拟网络,容器之间可以进行通信,但无法直接访问宿主机的网络服务。如果想要让宿主机或其他网络主机访问,Docker 容器需要暴露自身服务的端口映射。

然而,如果采用 host 模式启动,Docker 容器将与宿主机共享同一网络命名空间,即直接使用宿主机的网络。这样,Docker 容器就能使用宿主机的 IP 地址和端口,直接访问宿主机的网络服务。但是,host 模式也有其局限性,比如 Docker 容器之间无法直接通信,且 Docker 容器的网络性能可能会受到影响。

所以此时启动命令可以改成下边这样:

1
2
3
4
5
6
7
docker run --name one-api -d  \
--restart always \
--network host \
-e SQL_DSN="root:xxx@tcp(127.0.0.1:13306)/oneapi" \
-e TZ=Asia/Shanghai \
-v /data/one-api:/data \
justsong/one-api

这样容器就能直接访问到宿主机的 3000 了。

然而,由于这种模式的限制,实际生产中很少有人使用,因此不建议采用这种方式。相反,我们更推荐采用第二种方案。

方案二

docker 官方提供了一种支持方案,可通过指向 host.docker.internal 来指向宿主机的 IP。参见文档:从容器连接到主机上的服务

https://minio.kl.do/picture/images/typora/2b3d76594d9c640972dc7d057c906ecf.png

但请注意,该方案存在一个局限性:它仅支持 Mac 和 Windows 桌面环境,并不适用于 Linux 系统,因此无法直接在 Linux 中使用。

于是,有人在官方提交了这个 issue:Support host.docker.internal DNS name to host (opens new window)

在其中的一个回答里,找到了一种可行方案:

https://minio.kl.do/picture/images/typora/32b2895a2f667f7d33407ec0a30ae4d6.png

按照如上说明,可以使用如下命令进行启动:

1
2
3
4
5
6
7
8
9
# 运行项目
docker run --name one-api -d \
--restart always \
--add-host="host.docker.internal:host-gateway" \
-p 3000:3000 \
-e SQL_DSN="root:xxx@tcp(localhost:13306)/oneapi" \
-e TZ=Asia/Shanghai \
-v /data/one-api:/data \
justsong/one-api

于是我在自己的 Mac 以及 Linux 都使用这种方案做了测试,发现是可以的。

需要注意的是,这个功能在 docker 版本过低的时候,可能支持的有问题,所以你的 docker 版本最好不低于 20。

如果使用的是 docker-compose,则通过添加如下内容进行配置:

1
2
extra_hosts:
- 'host.docker.internal:host-gateway'

比如上边的项目改成 docker-compose 部署,就变成下边这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
version: '3.9'
services:
one-api:
image: justsong/one-api
container_name: one-api
environment:
- SQL_DSN=root:xxx@tcp(localhost:13306)/oneapi
- TZ=Asia/Shanghai
ports:
- "3000:3000"
volumes:
- /data/one-api:/data
restart: always
extra_hosts:
- host.docker.internal:host-gateway

现在,你可以在容器内使用 host.docker.internal 这个主机名来访问宿主机。例如,如果你想从容器中访问宿主机上的一个服务,可以使用

1
curl http://host.docker.internal:PORT

其中 PORT 是宿主机上运行的服务的端口。