Docker Compose部署多服务时网络模式选bridge还是host
Docker Compose部署多服务时网络模式选bridge还是host
Docker Compose里部署多服务,网络模式最常见的纠结就是 bridge 和 host。实际使用中发现,大多数业务并不是卡在“容器能不能联网”,而是卡在端口暴露、服务互访、性能损耗、故障排查这几件事上。
默认情况下,Compose创建的是 user-defined bridge network。这个和 Docker 默认的 bridge 不是一回事,Compose里的服务可以直接用 service name 互相访问,比如 app 访问 mysql:3306、redis:6379,不需要写容器IP。容器重启、IP变化都不影响服务名解析,这一点对多服务编排很关键。
bridge模式更适合多数业务系统
Web应用、API服务、MySQL、Redis、RabbitMQ、Nginx、后台任务这些放在一组 Compose 里,bridge 通常是更稳的选择。原因很直接:容器之间隔离清楚,对外只暴露需要暴露的端口,服务之间靠内部网络通信。
比如一个常见结构:nginx 对外暴露 80/443,app 不对公网开放,只给 nginx 访问;mysql、redis 只在 bridge 网络里跑,宿主机外部访问不到。这样即使服务器公网IP被扫端口,外面也只能看到 nginx,数据库不会裸露出去。
Compose配置大概是这种思路:
services:
nginx:
image: nginx
ports:
- "80:80"
- "443:443"
depends_on:
- app
app:
image: your-app
environment:
MYSQL_HOST: mysql
REDIS_HOST: redis
mysql:
image: mysql:8
environment:
MYSQL_ROOT_PASSWORD: password
redis:
image: redis:7
这里 app 访问 mysql,不是访问 127.0.0.1,也不是访问宿主机IP,而是访问 mysql:3306。这个地方很多人刚用 Compose 时会踩坑。容器里的 127.0.0.1 指的是容器自己,不是宿主机,也不是其他容器。
host模式的特点很直接:容器共用宿主机网络
host network 下,容器不再有独立的 network namespace,容器进程直接使用宿主机网络栈。容器监听 8080,宿主机就直接监听 8080,不需要 ports 映射,也没有 NAT 转发。
Compose里写法一般是:
services:
gateway:
image: your-gateway
network_mode: host
这里补充一点,使用 network_mode: host 之后,ports 配置就没有意义了。因为容器已经直接占用宿主机端口,再写 ports Docker 也不会按 bridge 那套逻辑做端口映射。
host模式在 Linux 服务器上比较常用,Docker Desktop 上表现不完全一样,尤其是 Mac 和 Windows,本身 Docker 跑在虚拟机里,host 不是你本机真正的 host。生产环境如果是 Linux 云服务器,这个问题不大。
性能差异:别神化host,但高并发网关确实会有收益
bridge模式有一层 veth、iptables/nftables、NAT 转发。正常 Web业务里,这点开销一般不明显。实际跑 API、后台管理、普通企业站,瓶颈更多在应用逻辑、数据库、磁盘IO、外部接口,不在 Docker bridge。
但在高连接数、高 PPS、高吞吐的场景,host 的优势会变得明显一些。比如游戏网关、UDP服务、实时通信入口、代理转发服务、L4转发、采集 Agent,这类服务对网络路径更敏感。
经验值可以这样看:
场景 | bridge表现 | host表现 | 备注
普通HTTP API,几百到几千 QPS | 足够用 | 差异不明显 | 应用和数据库更容易成为瓶颈
Nginx反向代理,1Gbps以内 | 通常没问题 | CPU占用略低 | 具体看连接数和TLS开销
UDP游戏网关,高 PPS | 可能更容易遇到抖动 | 更直接 | NAT和conntrack压力要关注
node_exporter、node-local Agent | 不方便拿宿主机网络视角 | 更合适 | 监控类服务常用host
MySQL、Redis这类内部服务 | 不建议直接host | 风险更高 | 容易暴露端口
多说一句,host不是性能魔法。云服务器网络质量、CPU型号、内核参数、conntrack、应用线程模型、DDoS清洗链路都会影响结果。比如同样跑网关服务,普通海外线路和优质线路在跨境延迟、丢包率上的差别,比 bridge 和 host 的差别更明显。
如果业务是游戏、海外访问、企业站或高防场景,选机器时网络线路要一起看。比如需要香港方向抗攻击入口,可以看 129云 的香港高防-C型,8C 8G、250Mbps峰值、200Gbps单机防御,适合把高防IP前置到网关或反代层;如果是新加坡企业建站,新加坡-A型 2C 2G、10Mbps峰值更偏轻量业务;美国精品网-B型 4C 4G、75Mbps峰值适合三网精品线路访问。需要按业务流量和防护强度确认,也可以直接打客服热线 400-9177118 问线路和防御细节。
多服务部署时,bridge的服务发现更省心
Compose的 user-defined bridge 有内置 DNS。服务名就是域名,这个能力在多服务部署里非常好用。比如 app 连 redis,写 redis:6379;app 连 mysql,写 mysql:3306;nginx 转发 app,写 http://app:8080。
host模式下就没有这套隔离网络里的服务发现了。大家都跑在宿主机网络里,服务之间通常靠 127.0.0.1 加端口,或者宿主机IP加端口。这样看起来简单,但多服务一多,端口管理就会变成负担。
比如两个服务都想监听 8080,bridge 下没问题,因为每个容器都有自己的网络空间;host 下不行,宿主机只有一个 8080,谁先占谁赢,另一个直接启动失败。
这也是为什么业务系统里,app、worker、db、cache 放 bridge 更舒服;真正需要贴近宿主机网络的入口服务,再单独考虑 host。
端口暴露方式差异很大
bridge模式对外暴露端口靠 ports:
services:
app:
image: your-app
ports:
- "8080:8080"
这个意思是宿主机 8080 转发到容器 8080。也可以写成 "127.0.0.1:8080:8080",只允许宿主机本地访问,公网访问不到。这个写法在部署管理后台、内部 API 时很有用。
host模式则是应用监听什么地址,就直接决定宿主机暴露什么地址。应用如果监听 0.0.0.0:8080,公网网卡也会暴露 8080;如果只监听 127.0.0.1:8080,那只能本机访问。
实际排障时,bridge 看 docker compose ps、docker inspect、iptables/nftables;host 直接看 ss -lntup、宿主机防火墙、应用监听地址。host 少了一层 Docker NAT,但安全边界也少了一层。
数据库和中间件不建议随便用host
MySQL、PostgreSQL、Redis、MongoDB、Elasticsearch 这类服务,除非有明确原因,不建议直接 network_mode: host。不是说不能跑,而是容易误暴露。
很多事故不是黑客技术多复杂,就是 Redis 6379、MongoDB 27017、Elasticsearch 9200 暴在公网,弱口令或无认证,扫到就出事。bridge 模式下只要不写 ports,外部根本连不到,安全基线会好很多。
如果确实需要宿主机访问数据库,可以只绑定本地地址:
services:
mysql:
image: mysql:8
ports:
- "127.0.0.1:3306:3306"
这样宿主机上的备份脚本、迁移工具可以连 127.0.0.1:3306,公网不能直接访问。云服务器安全组再把 3306 禁掉,双层限制更稳。
host适合放在入口层、采集层、网络敏感服务
host比较适合这些服务:高并发网关、UDP服务、DNS服务、node_exporter、cAdvisor某些宿主机采集需求、需要读取宿主机网络状态的 Agent、需要广播或组播的服务、L4代理、VPN类服务。
比如 node_exporter 用 host 网络就很自然:
services:
node-exporter:
image: prom/node-exporter
network_mode: host
pid: host
volumes:
- /:/host:ro,rslave
它本来就是看宿主机状态的,用 bridge 反而需要绕。网关类服务也是类似,少一层端口映射,监听和排查都更贴近宿主机。
但如果一个 Compose 文件里十几个服务全写 host,后面维护会比较难。端口冲突、服务名不可用、暴露面扩大、环境迁移不一致,这些问题会慢慢冒出来。
bridge和host混用时要注意通信方式
Compose里可以部分服务 bridge,部分服务 host。比如 nginx 或 gateway 用 host,app、mysql、redis 继续 bridge。这个架构在一些高并发入口场景里会用到。
但这里有个细节:host网络里的容器,不能直接通过 app 这个 Compose service name 访问 bridge 网络里的 app。因为它不在那个 bridge network 里。处理方式通常有两种。
一种是 app 通过 ports 绑定到 127.0.0.1,比如 127.0.0.1:18080:8080,然后 host 模式的 gateway 访问 127.0.0.1:18080。
另一种是 gateway 不用 host,也放到 bridge 里,由 Docker 负责端口映射。除非 gateway 的网络性能压力确实明显,否则这种更简单。
示例:
services:
gateway:
image: your-gateway
network_mode: host
app:
image: your-app
ports:
- "127.0.0.1:18080:8080"
gateway 访问 http://127.0.0.1:18080,流量再进入 app 容器。这比把 app 直接暴露到 0.0.0.0:8080 安全一些。
云服务器环境下还要看安全组和防火墙
Docker层面的 ports、host 只是服务器内部网络绑定方式。到了云服务器,还会经过安全组、防火墙、DDoS防护、WAF、负载均衡这些外层限制。
bridge模式里写了 ports,不代表公网一定能访问,还要看安全组有没有放行。host模式里应用监听 0.0.0.0:8080,也不代表外面一定通,安全组没开还是不通。
排查顺序建议按真实链路来:应用是否监听、容器网络是否正常、宿主机端口是否存在、本机 curl 是否通、安全组是否放行、外部网络是否可达、线路是否有丢包或清洗策略。
常用命令:
ss -lntup
docker compose ps
docker compose logs -f 服务名
docker inspect 容器名
curl -v http://127.0.0.1:端口
curl -v http://宿主机公网IP:端口
iptables -t nat -L -n
nft list ruleset
如果是高防服务器,还要确认业务端口是否接入高防策略。有些高防产品默认只放行常见端口,非标准端口要单独配置转发或策略。
实际选择时可以按服务角色拆开看
业务应用:优先 bridge。服务名互访、隔离清楚、迁移方便。
数据库和缓存:优先 bridge,不需要公网访问就不要 ports;需要宿主机访问就绑定 127.0.0.1。
反向代理:普通业务用 bridge 加 ports 就行;高并发入口、UDP、特殊网络需求再考虑 host。
监控 Agent:看采集对象。如果是采集宿主机网络、进程、端口,host 更合适;如果只是采集应用指标,bridge 也可以。
游戏网关、实时通信、代理转发:可以压测 bridge 和 host。重点看 CPU softirq、conntrack、PPS、延迟抖动,不要只看平均 QPS。
容易踩的坑
容器里访问 127.0.0.1,以为能访问宿主机服务。bridge模式下这通常是错的,127.0.0.1 是容器自己。需要访问宿主机可以用宿主机网关地址,或者 Docker Desktop 下用 host.docker.internal,Linux上可通过 extra_hosts 配置 host-gateway。
把 mysql、redis 端口直接映射到 0.0.0.0。测试阶段图方便,线上忘了收,风险很高。
host模式还写 ports,结果发现不生效。network_mode: host 下 ports 不按 bridge 逻辑工作。
多个 host 服务抢同一个端口。bridge里可以多个容器都监听 8080,只要宿主机映射端口不同;host里不行。
以为 host 一定比 bridge 快很多。普通HTTP业务里,差距经常被数据库、TLS、应用代码、磁盘IO掩盖。真要判断,用 wrk、iperf3、tcpdump、sar、pidstat、perf 这些工具看数据。
一个更贴近生产的Compose拆法
大多数线上服务可以这样拆:入口层只暴露 80/443,业务层和数据层都不暴露公网。需要高性能入口时,再单独评估入口层 host。
services:
nginx:
image: nginx
ports:
- "80:80"
- "443:443"
depends_on:
- app
app:
image: your-app
environment:
DB_HOST: mysql
REDIS_HOST: redis
mysql:
image: mysql:8
volumes:
- ./mysql:/var/lib/mysql
redis:
image: redis:7
networks:
default:
driver: bridge
如果入口层要改 host,可以把 nginx/gateway 拿出来单独处理,app 通过 127.0.0.1 绑定端口给它访问,数据库和缓存仍然留在 bridge 里:
services:
gateway:
image: your-gateway
network_mode: host
app:
image: your-app
ports:
- "127.0.0.1:18080:8080"
environment:
DB_HOST: mysql
REDIS_HOST: redis
mysql:
image: mysql:8
redis:
image: redis:7
这个写法的重点不是配置多复杂,而是入口、应用、数据三层的边界清楚。入口承接公网流量,应用只给入口访问,数据服务只给应用访问。后面要接 WAF、高防IP、负载均衡、备份脚本,也比较好改。