Docker 网络要先分清两件事:容器之间怎么通信,宿主机怎么访问容器。
最常见的问题,是在容器里把数据库地址写成 localhost:
如果数据库就在宿主机本机,这行没问题;放进容器里就不对了:容器里的 localhost 指的是容器自己,不是数据库容器,也不是宿主机。
这篇我先讲 Docker 网络本身,然后再看这些概念在 Docker Compose 里怎么配置的。
▎先分清三种流量
看 Docker 网络,先分清流量方向:
容器访问外网,比如容器里 curl https:
容器访问容器,比如应用连数据库。这个时候应该让两个容器在同一张 Docker 网络里。更准确一点:在用户自定义 bridge 网络或 Compose 默认网络里,通常用容器名或服务名访问。
宿主机访问容器,比如浏览器打开本地应用。这个时候看的是端口发布,也就是 -
▎bridge 是日常开发的主角
装好 Docker 后看一下网络:
常见输出会有:
别把它理解成 Docker 只有三种网络。这里看到的是已经存在的网络。Docker 真正说“类型”时,看的是网络驱动,也就是 driver。
日常开发最常用的是 bridge。可以把它想成宿主机里的一座软件网桥,容器接到这座桥上,形成一个小局域网。
同一张 bridge 网络里的容器可以互相通信。不同 bridge 网络之间默认隔开。外部要访问容器服务,通常要把容器端口发布到宿主机。
▎bridge 原理,知道这点就够用
不用一上来就啃 Linux 网络命名空间,但最好知道 Docker 大概做了什么。
容器启动后,会有自己的网络空间。你在容器里看到的网卡,并不是宿主机那张真实网卡。Docker 会给容器创建一对虚拟网卡,常见说法叫 veth pair:一头放进容器里,另一头接到宿主机上的 bridge。
可以粗略想成这样:
同一张 bridge 上的容器,像接在同一个小交换机上,所以它们能互相访问。Docker 还会给这张网络分配子网,比如 172.
容器访问外网时,Docker 会做一层地址伪装,也就是常说的 NAT。外面的服务通常看不到容器自己的内部 IP,只看到宿主机出去的地址。
外部访问容器时,逻辑反过来。你写了:
Docker 就把宿主机 8080 上的流量转发到容器 80。端口发布可以理解成 Docker 在中间加了一条转发规则,容器自己并没有真的变成宿主机端口。
▎端口发布只管宿主机访问
跑一个 Nginx:
这句里的 -
浏览器访问 http:
但另一个容器访问它时,不应该写 localhost:
这就是很多人最容易混的地方:ports 是给宿主机进容器用的,不是给容器之间互相找服务用的。
还可以把端口只绑到本机:
这样通常只有宿主机本机能访问。开发数据库、Redis、管理后台时,更建议这样绑定,少开一个对局域网暴露的口子。
Dockerfile 里的 EXPOSE 也顺手提一下:
它更像说明书,告诉别人这个镜像里的程序预期监听 8080。它不会自动把端口发布到宿主机。真正发布端口,还是靠 -
▎默认 bridge 和自定义 bridge 的差别
默认 bridge 适合临时实验。
正式一点的项目,更建议创建用户自定义网络。原因有两个。
一个是隔离清楚。这个项目的容器放 app-
另一个是名字访问更自然。应用连数据库写 db:
比如这样建一张网络:
然后把容器挂进去:
进到 Alpine 容器里,访问数据库就用 db:
查看网络详情:
这条命令很有用。它能看到网络的子网、网关、接入的容器、容器 IP。网络不通时,可以先看这里。
容器启动后也能接入网络:
也能断开:
调试时挺方便。比如起了一个临时工具容器,忘了接项目网络,补一下就行。
▎其他网络驱动什么时候用?
日常主力是 bridge,但 Docker 不止它。
host 是让容器直接使用宿主机网络。少了一层隔离,也少了一层端口转发。某些代理、监控、性能测试场景会用它。普通 Web 服务不建议默认选它,因为端口冲突和边界问题会更直接。
none 基本就是不给容器配网络,只剩 loopback。适合完全不需要网络的任务,或者你想自己手动配置网络。
overlay 用来跨多个 Docker daemon 通信,常见于 Swarm。单机 Compose 开发基本碰不到。
macvlan 会让容器像局域网里一台独立机器,有自己的 MAC 地址。某些老系统迁移、局域网直连场景会用。这个就要懂宿主机网卡、交换机、网段和路由了。
ipvlan 和 macvlan 接近,但不额外分配独立 MAC。网络环境限制 MAC 数量时,它会更合适。
可以按场景这样选:
没碰到对应场景,不用硬上复杂驱动。
▎Compose 只是把这些关系写下来
现在再看 Docker Compose,就顺了。
比如一个应用加数据库:
执行:
Compose 会自动创建一张默认网络,名字通常是:
app 和 db 都接到这张网络里。Docker 会提供内部 DNS,所以 app 能用 db 这个服务名访问数据库。
这就是前面那条规则在 Compose 里的落地:DATABASE_
▎Compose 里的 ports 和 expose
在 Compose 里,ports 会发布端口到宿主机:
更安全一点的本地写法,是只绑定本机地址:
这样本机数据库客户端能连,局域网其他机器连不到。
expose 不会发布到宿主机:
它更像内部声明。实际项目里,同一张 Compose 网络里的服务本来就能访问监听端口,很多时候不需要专门写 expose。
▎什么时候在 Compose 里自定义 networks?
小项目不需要。一个 app、一个 db、一个 redis,默认网络通常够用:应用连数据库写 db:
需要自定义网络,通常是为了表达边界。
比如前端代理能访问应用,但不能访问数据库:
访问关系很清楚:
这类配置的价值,是让架构边界落在文件里。后来别人看 compose.
▎几个真正会用到的 Compose 网络配置
driver 用来指定网络驱动:
本地开发基本就是 bridge。
name 可以固定真实网络名:
默认情况下,Compose 会把项目名拼进网络名,比如 demo_
external 用来接入已经存在的网络:
这个适合多个 Compose 项目本地联调。比如认证服务一个项目,业务服务另一个项目,两边都接到 inter-
aliases 可以给服务加别名:
老项目里如果写死了 api.
ipam 用来自定义网段:
能用服务名,就别固定 IP。固定 IP 适合 VPN 网段冲突、老系统白名单、网络实验这些场景。普通业务配置里,它会增加维护成本。
internal 可以创建内部网络:
这个适合数据库、缓存、内部队列之间的封闭网络。下手前要想清楚应用是否需要访问外部 API、对象存储、公司代理。如果服务只接了这张 internal 网络,应用自己也可能出不去。
extra_
这个最常见的用途,是让容器访问宿主机上的服务。比如本机跑了一个 Mock 服务、MySQL、Redis,容器里不能写 localhost,因为那会指向容器自己。用 host.
network_
它会直接指定容器网络模式,绕开“接入某张 Compose 网络”这条常规路径。常见值有 host、none、bridge,还有 service:
比如 sidecar 场景:
debug 会复用 app 的网络命名空间,就像贴着 app 做网络调试。这个挺有用,但别乱写。Compose 里用了 network_
dns 适合处理解析问题:
公司内网、自建域名、VPN 环境里,容器偶尔会解析不到内部域名。这时可以给服务指定 DNS。别一遇到访问失败就改 DNS,先用 getent hosts xxx 或 nslookup xxx 确认真是解析问题。
还有一个老配置叫 links。现在基本不建议用了。Compose 默认网络和 service name 已经能解决服务发现,links 更多是历史包袱,看到老项目里有,知道它大概是干嘛的就行。
▎连不上时,按这个顺序查
先看服务状态:
看网络:
进应用容器:
看服务名能不能解析:
看端口通不通:
镜像里没这些工具,就起一个调试容器接到同一张网:
再测:
按这个顺序判断:
还有一点这里补充说明一下:depends_
这篇先讲到这里。后面有机会再单独聊聊 Docker 网络排查和线上部署里的坑。
· END ·
如果你愿意,欢迎关注。后面继续分享更多实用内容~
评论 (0)