在华为云 ECS 上跑容器:host 与 bridge 模式怎么选,端口不通怎么查
一篇真实的排查记录
把服务容器化部署到华为云 ECS 后,最常见的一类故障是:"我明明开放了端口,容器也在跑,为什么外网访问不了?"
我自己在这台 2 vCPU 的 ECS 上排查过这个问题,前后花了小半天,最后发现问题根本不在服务器上,而在安全组那一层。这篇就把整个排查链和结论写清楚,顺带讲讲 host 和 bridge 两种网络模式该怎么选。
一、先把网络层级理清楚
一台运行着容器的 ECS 上,从外到内一共有四道关卡,请求必须逐层通过:
外网请求 ↓ ① 云平台安全组(控制台配置,最容易漏) ↓ ② 宿主机防火墙(iptables / firewalld / UFW) ↓ ③ Docker 自身的 NAT 规则(由 dockerd 自动维护) ↓ ④ 容器内应用是否真的 listen 在 0.0.0.0
关键点:这四层是"与"的关系,任何一层拦截,你看到的现象都完全一样——连接超时或被拒绝。 新手最常见的错误就是只查自己熟悉的那一层。
排查顺序建议从下往上,因为越往下成本越低:先在服务器本机用 curl localhost:端口 确认容器活着,再查宿主机防火墙,最后才查安全组。
二、host 模式:简单但要小心
--net=host 让容器直接共享宿主机的网络栈,没有端口映射这回事,容器里 listen 什么端口,外网就能直接访问什么端口。
docker run -d --name my-service --net=host my-image
优点非常实在:
- 不需要
-p做端口映射,容器端口和宿主机端口一一对应,排查时心智负担为零。 - 网络性能优于 bridge 模式,少一层 NAT 转发。
- 容器内的进程能直接看到宿主机网络,监控、日志采集类工具用起来方便。
代价也必须说清楚:
- 端口冲突风险。宿主机上已经有 3000 端口的进程,容器再用 host 模式起 3000 就直接失败,而且是启动即退出。
- 隔离性差。一个容器监听的端口,等于整个宿主机监听了这个端口。多个容器共存时,端口规划必须提前统一。
- 不能做端口隔离。host 模式下容器的进程绑定在哪个地址上,服务就出现在哪个地址上。想让服务只对本机开放,得应用自己绑
127.0.0.1——这跟防火墙无关。
我自己的用法是:对外暴露的核心服务用 host(少一层转发、少一类问题),内部工具类容器用 bridge(隔离端口、随便映射到高位端口)。
三、bridge 模式:默认选择,注意两个坑
bridge 是 Docker 的默认网络模式。容器有自己独立的网络命名空间,宿主机通过端口映射把流量转发进去。
docker run -d -p 127.0.0.1:8080:8080 my-image
坑 1:绑定地址不是默认的 0.0.0.0。
默认写法 -p 8080:8080 实际等价于 -p 0.0.0.0:8080:8080,也就是在所有网卡上监听。如果只是想给宿主机本机用(比如一个内部管理后台),应该显式写成 -p 127.0.0.1:8080:8080,这样服务不会出现在公网 IP 上。
坑 2:bridge 模式下,宿主机防火墙规则容易"看起来对但没用"。
原因是 dockerd 启动时会自动维护 iptables 的 DOCKER 链,这套规则会绕过部分常规防火墙的 INPUT 链策略。也就是说,UFW 里没有放行 8080,容器映射出来的端口照样能通。
这会造成一个很混乱的认知:同一个端口,宿主机上的进程占用时必须放行防火墙,容器占用时反而不用。排查时如果搞混这一点,会白白怀疑很久。
我的习惯是:需要外网访问的容器端口,宿主机防火墙和云安全组两边都放行,不依赖 DOCKER 链的隐式行为。行为可预期,排查时也不会有"玄学"。
四、端口不通的完整排查链
这是我最想分享的部分。下面是一套按顺序执行的流程,每一步都有明确的判据。
第 1 步:容器活着吗,端口在里面通吗
docker ps -a # STATUS 是不是 Up,PORTS 列显示什么 docker logs --tail 50 my-service # 有没有启动即退出的报错
进容器再确认一次:
docker exec -it my-service sh -c "netstat -tlnp || ss -tlnp"
判据:容器内必须看到 0.0.0.0:8080 这样的监听地址。 如果看到的是 127.0.0.1:8080,那外部永远访问不到——这是应用自己绑错了地址,改应用配置即可,跟防火墙无关。
第 2 步:宿主机本机通不通
curl -sv http://127.0.0.1:8080/health
curl -sv http://$(hostname -I | awk '{print $1}'):8080/health
第一个用回环地址测,第二个用网卡 IP 测。两者结果不同就说明应用只绑了回环。
第 3 步:宿主机防火墙
ufw status verbose # Ubuntu/Debian firewall-cmd --list-all # CentOS/EulerOS iptables -L INPUT -n -v # 兜底看实际规则和计数
看计数是关键。-v 里的 pkts 列如果一直是 0,说明规则压根没被匹配到——问题不在这一层,而在更外层或者更内层。
第 4 步:抓包定位
这是最有效的手段,能一刀切开"云端拦截"和"本机拦截":
tcpdump -i any -nn port 8080
然后从外部发起一次连接,观察抓包结果:
| 抓包现象 | 结论 | 下一步 |
|---|---|---|
| 完全没有 SYN 包 | 请求没到服务器 | 云端安全组或上游网络,去控制台查安全组 |
| 看到了 SYN,但没有 SYN-ACK | 到了但被本机丢弃 | 查宿主机防火墙规则 |
| 看到 SYN-ACK 但客户端仍超时 | 回程路径问题 | 查路由/云平台网络策略 |
| 完整握手成功 | 网络没问题 | 查应用层和 HTTP 响应 |
我那次就是靠这一步定性的:抓包 0 packets,说明 SYN 根本没到机器上。UFW 明明放行了,那就是安全组——上控制台一查,果然没有对应端口的入方向规则。
第 5 步:云端安全组
在华为云控制台进入 ECS → 安全组 → 更改安全组规则,确认:
- 入方向规则里 TCP 端口有对应条目,源地址按需设置(
0.0.0.0/0全开,或限定为你的固定出口 IP)。 - 规则优先级没有被其他"拒绝"规则压过。安全组是按优先级从上到下匹配,命中即停,一条靠前的 Deny 会让后面所有 Allow 失效。
- 如果实例绑定了多个安全组,每个安全组都会独立生效,规则是叠加而非覆盖。
安全组改完是立即生效的,不需要重启实例,这一点和部分云厂商不同。
五、几个额外经验
端口规划先于部署。 我在给这台机器规划端口时固定了分段:3000 段给业务网关,3400 段给工具类服务,29000+ 给临时调试。这样半年后回头看 docker ps 的 PORTS 列,一眼就知道哪个容器在干什么。
优先考虑不走公网。 如果只是给自己用,云服务器完全没必要暴露 0.0.0.0。可以走内网访问(同一 VPC 内的其他实例用内网 IP 直连),或者用 Cloudflare Tunnel 这类隧道方案。我实测过同一份服务,直连公网约 30ms,走隧道中转约 1.2~1.7s,差了四十多倍——延迟敏感的内部工具,务必走直连。
别用容器内嵌的数据库当生产存储。 顺手提一句,容器的可写层不是持久存储,容器一删数据就没了。数据库要么挂 named volume,要么挂宿主机目录,这是 Docker 使用里最容易被忽略的一条。
小结
容器化部署在云主机上,端口不通的排查思路可以概括成三句话:
- 先分清层级:安全组 → 宿主机防火墙 → Docker NAT → 应用监听,四层逐个排除,不要凭感觉跳。
- 用抓包定性:
tcpdump看到 0 个包就是云端问题,看到 SYN 没有 SYN-ACK 就是本机问题。这一步能省掉一半的猜测。 - 行为要可预期:不要依赖 Docker 自动维护的 iptables 规则的隐式行为,该放行的两边都显式放行。
把这套流程记下来,下次遇到同类问题,从第 1 步走到第 5 步,一般十分钟内就能定位到具体是哪一层出的问题。
- 点赞
- 收藏
- 关注作者
评论(0)