尧图精选

Ubuntu服务器安全加固与Docker网络配置实战指南

🕒 发布时间:2026/11/4 15:13:38 📁 来源:尧图网络
前阵子帮人迁移一台业务服务器登录进去一看root 账号密码登录、22 端口大开、Docker 装完两个容器全靠默认网络互联、ufw 干脆没装。最离谱的是同事说反正内网跑加那么多安全措施干嘛。我当时就想这哪是生产级 Ubuntu 服务器这是等着被爆破的裸奔状态。后来花了半天整理安全加固和 Docker 网络配置顺手把几个典型网络故障排查完才有了这篇实战记录。内容核心就三件事初始安全基线怎么打、Docker 守护进程和容器怎么加固、网络模式怎么选才不踩坑。适合刚拿到服务器准备上生产、或者已经在用 Docker 但对网络原理没捋清的读者。1. 裸奔的 Ubuntu 离生产环境有多远初始安全基线1.1 主机名、时区和 APT 源一开始就定规矩拿到一台 Ubuntu 服务器先别急着装 Docker。系统初始状态默认开着 SSH 密码认证、root 可登录、时间可能还是在 UTC这些在开发环境无所谓上了生产就是事故隐患。我每次上手第一件事就是按下面这套顺序做基础初始化# 修改主机名方便后面日志和监控识别 sudo hostnamectl set-hostname prod-web-01 # 设置时区业务日志和容器日志都靠它对齐 sudo timedatectl set-timezone Asia/Shanghai # 安装基础工具链 sudo apt update sudo apt upgrade -y sudo apt install -y curl wget git vim htop tcpdump net-tools chrony为什么时区这么重要之前遇到过容器日志里的时间戳和宿主机差 8 小时查日志时还得心算换算排障效率极低。只要宿主机时区对了容器默认继承同一套时间日志就对得上了。时间同步这里我推荐 chrony 而不是 ntpdate。systemd-timesyncd 虽然也能用但在虚拟机里时钟漂移严重时chrony 的调整更快更稳。装完 chrony 后执行sudo systemctl enable --now chrony再用chronyc tracking确认一下同步状态。APT 源方面如果服务器在国内强烈建议把/etc/apt/sources.list换成云厂商提供的镜像源。换源后跑一遍apt update注意观察有没有报错有些老源签名过期会让你后面装包失败。记住换源这件事要在部署任何服务之前做否则装到一半发现下载速度几十 KB/s心态很容易崩。1.2 禁用 root 直接登录SSH 密钥认证配置很多云服务器默认是用 root 登录的但这在生产环境是巨大的风险。root 是系统最高权限如果 SSH 账号被爆破成功整个服务器直接沦陷。我见过太多人图省事留 root 密码登录结果第二天起来看/var/log/auth.log全是扫描尝试。正确做法是先创建普通用户赋予 sudo 权限然后改用密钥登录# 创建用户并加入 sudo 组 sudo adduser deploy sudo usermod -aG sudo deploy下一步在本地机器生成密钥对如果还没有的话然后把公钥放到服务器上# 本地生成密钥 ssh-keygen -t ed25519 -C deployyour-server # 上传公钥到服务器 ssh-copy-id deployyour-server-ip公钥放好后修改 SSH 配置/etc/ssh/sshd_config里的关键项PermitRootLogin no PasswordAuthentication no PubkeyAuthentication yes AllowUsers deploy这里AllowUsers deploy可以进一步限制只有指定用户能 SSH 登录。如果团队人多建议在/etc/ssh/sshd_config.d/里放一个单独的文件不要直接改主配置免得将来更新 SSH 时配置冲突。修改完一定要先验证再重载服务sudo sshd -t sudo systemctl reload sshd然后保持当前 SSH 连接不要断开新开一个终端窗口尝试登录。确认新窗口能登录成功后再关旧窗口。这一步看起来傻但确实有人因为配置写错新旧连接都断了最后只能去控制台通过 VNC 救援折腾一晚上。1.3 Fail2ban 与 UFW别让暴力破解碰到业务即使关闭了密码登录服务器也会持续受到扫描和探测。Fail2ban 的作用就是监听日志里的失败登录触发阈值后动态封禁源 IP。安装和基础配置非常简单sudo apt install -y fail2ban sudo cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local sudo systemctl enable --now fail2banjail.local里我会重点调这几个参数[DEFAULT] bantime 1h findtime 10m maxretry 5 [sshd] enabled true port ssh logpath %(sshd_log)s backend systemdbantime我习惯设置成 1 小时。太短了起不到震慑作用太长了容易把自己人误封。maxretry设成 5 次兼顾误杀和防护。担心误封的话可以先从 15 分钟试起运行一周看 auth.log 里有没有正常用户被触发再逐步调大。UFW 是 Ubuntu 自带的防火墙前端上手难度比 nftables 低很多。我常用的初始化规则sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable这里有个细节不要直接ufw allow 22/tcp然后不管了。如果你前面改了 SSH 端口比如换成 2222那么一定要把22换成实际端口否则防火墙会把你挡在外面。同样的ufw status numbered可以查看规则编号必要时可以精确删除某条。1.4 自动安全更新省心但要锁住版本无人值守更新unattended-upgrades是 Ubuntu 自带的安全更新机制它只更新安全补丁不更新一般软件版本适合生产环境。安装配置sudo apt install -y unattended-upgrades sudo dpkg-reconfigure --prioritylow unattended-upgrades在/etc/apt/apt.conf.d/50unattended-upgrades里我建议关掉自动重启或者只允许在有重启需求时发邮件通知不要自动 reboot。生产环境自动重启很可能拉不起来业务尤其是数据库和 Docker 容器。稳妥做法是让它自动更新补丁但重启操作人工安排窗口执行。这里有个我需要特别提醒的坑Ubuntu 的自动更新偶尔会把 Docker 或内核给升级掉。虽然一般不会出问题但我遇到过内核升级后需要重启、而 Docker 的 overlay2 存储驱动和旧内核模块有兼容问题的情况。所以建议在配置里加上 Docker 包的 blacklistUnattended-Upgrade::Package-Blacklist { docker-ce; docker-ce-cli; containerd.io; linux-image-.*; };内核这类基础组件手动维护反而更可控。自动更新只覆盖软件包层面的漏洞修复其他敏感操作还是留给人来决策吧。2. Docker 装上只是开始守护进程与基础运行安全2.1 官方源安装 Docker CE 的好处Ubuntu 自带docker.io包很多人图省事直接apt install docker.io。但那个包版本老Docker Compose v2 可能没有甚至连 containerd 的版本都落后不少。生产环境要装就用 Docker 官方 apt 源# 安装依赖 sudo apt install -y ca-certificates curl gnupg lsb-release # 安装官方 GPG 密钥 sudo install -m 0755 -d /etc/apt/keyrings curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg sudo chmod ar /etc/apt/keyrings/docker.gpg # 添加 apt 源 echo \ deb [arch$(dpkg --print-architecture) signed-by/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \ $(lsb_release -cs) stable | sudo tee /etc/apt/sources.list.d/docker.list /dev/null sudo apt update sudo apt install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin装完后启用服务sudo systemctl enable --now docker docker version用官方源的好处是版本更新及时而且docker compose插件直接打包进docker-compose-plugin不用再单独装 Python 版的老旧 docker-compose。实际生产环境里官方源也更容易匹配 Docker Engine 和 containerd 的兼容性避免奇怪的问题。2.2 daemon.json 里的关键参数日志、存储驱动、网络默认设置Docker 守护进程的默认配置其实只适合本机测试生产环境必须改/etc/docker/daemon.json。我贴一份常用基线配置{ exec-opts: [native.cgroupdriversystemd], log-driver: json-file, log-opts: { max-size: 50m, max-file: 3 }, storage-driver: overlay2, iptables: true, ip-forward: true, default-address-pools: [ { base: 10.88.0.0/16, size: 24 } ] }exec-opts里把 cgroup 驱动设成 systemd 不是随便写的。Docker 默认用 cgroupfs而 systemd 本身也管理 cgroup。如果 Docker 和 systemd 各自维护一套 cgroup 状态长时间运行后容易出现资源泄漏尤其在主机上跑了很多容器时。统一成 systemd 后资源管理路径对齐行为更可控。log-driver和log-opts用来限制容器日志大小。这个非常关键一个容易忽略的问题是json-file 日志会无限增长如果应用的日志特别多几天就能把磁盘写满。我见过有业务容器一晚上写入 20GB 日志直接拖垮宿主机的案例。配置max-size: 50m和max-file: 3后每个容器的日志最多占 150MB心里踏实很多。default-address-pools是给 Docker 自动创建自定义网络时分配子网用的。默认情况下Docker 会在 172.17.0.0/16 等网段里选但如果你创建大量自定义网络可能和现有内网网段冲突。提前指定一个独立网段后面网络避坑能省一半事。改完 daemon.json 千万记得sudo systemctl restart docker docker info | grep -A 5 Docker Root Dir看日志驱动、存储驱动有没有生效。改网络配置会重建 Docker 的默认网络跑在旧网络里的容器会中断所以这种变更尽量选维护窗口做。2.3 理解 Docker 组与权限边界安装 Docker 后系统会创建docker组。把普通用户加进这个组就不用每条命令都敲 sudo 了sudo usermod -aG docker $USER但这里有一个我必须放大加粗的提醒docker组权限约等于 root 权限。为什么这么说因为 Docker 客户端可以通过docker run -v /etc:/etc ...之类的参数把宿主机目录挂载进容器容器里一旦有恶意代码就可以直接改宿主机的/etc/passwd或authorized_keys实现提权。我见过有的团队为了方便把所有开发都加进 docker 组然后服务器被走后门。生产环境遵守最小权限原则要么只有运维同学在宿主机上执行 docker 命令要么全部走 sudo 审计。如果你用自动化平台管理容器那尽量不要把普通用户加进 docker 组而是允许平台通过 API 或 SSH 执行受控命令。2.4 容器运行时的基础隔离概念Docker 之所以轻量是因为它复用宿主机内核通过 namespace 和 cgroups 实现隔离。namespace 让容器看到独立的进程、网络、挂载点cgroups 负责限制资源。但注意容器不是虚拟机它没有自己的内核内核是共享的。这就引出一个安全结论容器里的 root 用户其实只是 namespace 里映射的 root如果配置了特权模式或者挂载了宿主机敏感目录它就能突破隔离。所以在运行容器时需要明确三个概念namespace 隔离的是视野不隔离权限。cgroups 限制的是资源不限制行为。和宿主机共享的内核一旦有漏洞所有容器都会受影响。理解了这几点后面做容器安全加固就有方向了尽量缩小权限、限制资源、避免挂载不必要的主机路径。3. Docker 网络避坑那些让你半夜被叫醒的故障3.1 四种网络模式别选错Docker 默认提供 bridge、host、none 三种单机网络模式再加上 overlay 用于跨主机。很多初学者搞不清什么时候该用哪个实际生产环境最常见的是 bridge 和 host。模式隔离性网络性能端口管理适用场景bridge容器有自己的网络栈和宿主机通过 veth 桥接略低于 host但可接受通过 -p 或 -P 映射灵活默认选择适合大多数单机容器host共享宿主机网络栈无隔离接近物理机性能直接使用宿主机端口容易冲突对网络性能敏感或需要固定端口的服务如某些 UDP 服务none无网络接口只有 lo不适用无离线任务、安全性要求极高的临时容器overlay容器跨主机通信属于分布式网络依赖底层网络一般走 VXLAN需配合 swarm 或 k8s多节点集群环境我建议单机多容器部署能走 bridge 就走 bridge别图省事直接用 host。host 模式虽然省掉了端口映射的 NAT 开销但有几个麻烦容器内进程占用的端口直接在宿主机上暴露一旦出现端口冲突你排查起来会很痛苦而且 host 模式下容器没有独立网络命名空间swarm 和 Docker 内置 DNS 都不好使。生产环境我更倾向于用 bridge 加反向代理统一入口既灵活又安全。3.2 UFW 与 Docker 端口映射冲突一次外部访问 503 的排查这是一个老掉牙但依然频繁踩到的坑你用docker run -p 8080:80发布了一个 Nginx 容器UFW 也放行了 8080但外部就是访问不通或者时通时断。根源在于 Docker 和 UFW 对 iptables 的管理策略不一致。Docker 启动时会直接操作 iptables 的 DOCKER 链、FORWARD 链并在 NAT 表里添加 DNAT 规则。而 UFW 也在管理自己的链。当防火墙策略收紧时比如ufw default deny incoming外部请求经过 FORWARD 链可能被 UFW 的规则拦截而 Docker 生成的规则又在 FORWARD 链的某个位置优先放行规则顺序不同行为就不一样。我当时的排查过程是这样的# 确认容器端口映射 docker ps --format table {{.Names}}\t{{.Ports}} # 查看 iptables FORWARD 链 sudo iptables -L FORWARD -n -v --line-numbers输出里能看到 Docker 在 FORWARD 链里插入的DOCKER-USER和DOCKER链规则。Docker 官方推荐把 UFW 的默认 forward 策略允许掉但很多时候 UFW 默认是 deny导致容器之间的转发也被切断。解决方案有两个方案 A关闭 UFW 对 Docker 网段的影响明确放行 docker0 桥sudo ufw allow in on docker0但这个方法对自定义网络不一定有效而且每次新建网络都要加规则麻烦。方案 B更推荐的方式——不把容器端口直接暴露在宿主机上而是让容器跑在内部 bridge 网络只用 Nginx 这类反向代理容器监听 80/443再把流量转发到业务容器。这样宿主机防火墙只需要放行 80/443容器端口完全不用暴露攻击面小很多也不会有 UFW 和 Docker 端口映射打架的问题。如果你的环境必须用-p直接映射有一个临时解法在daemon.json里设置iptables: false然后由你自己写 iptables 规则。但这非常不推荐因为 Docker 网络隔离、DNS 转发、负载均衡都会受影响除非你极为熟悉 iptables否则别碰这个开关。3.3 容器互访为什么不能用 IP自定义 bridge 的 DNS 解析默认 bridge 网络下两个容器可以通过 IP 互访比如 Nginx 容器要访问后端 API 容器直接curl http://172.17.0.5:8080看起来能通。但这样做有个致命的坑容器重建后 IP 大概率会变一旦硬编码了 IP重启后服务就断了。默认 bridge 网络还支持--link这个老参数它会在容器/etc/hosts里写一条目标容器的映射。但 Docker 官方早把--link标记为遗留特性不推荐在新项目里使用原因很简单--link是单向的A 链接 BB 并不知道 A而且只在同一台宿主机上有效。正确做法创建自定义 bridge 网络并让容器加入这个网络。docker network create --driver bridge myapp-net docker run -d --name web --network myapp-net -p 80:80 nginx docker run -d --name api --network myapp-net -p 8080:8080 myapp-api在web容器里你可以直接curl http://api:8080/health。Docker 自带的 DNS 服务会自动把容器名api解析成对应 IP。这是因为自定义 bridge 网络内置了 DNS 解析而默认 bridge 网络没有这点区别很关键。如果你用 docker compose 部署那更简单同一个 compose 文件里的 services 会自动加入项目默认网络直接用 service 名互访即可。前提是不要手动指定network_mode: host否则 DNS 就废了。3.4 改 docker0 网段当容器 IP 跟办公网冲突时Docker 默认给 docker0 网桥分配 172.17.0.0/16 网段这在大多数环境下没问题但一旦你的办公网或者物理网络也用 172.17.0.0/16就会出现容器无法访问内网资源的现象。现象通常是容器里ping 172.17.0.5一个真实内网服务器被 Docker 自己的网段吞掉了因为路由优先级指向了 docker0。解决思路是修改 Docker 默认网段。编辑/etc/docker/daemon.json设置 bip 参数{ bip: 10.88.0.1/24 }重启 Dockersudo systemctl restart docker改完后验证ip addr show docker0docker0 的 IP 会变成 10.88.0.1。这个变更会导致已有的默认 bridge 容器 IP 变化因此需要先停掉依赖固定 IP 的服务再在维护窗口里执行。如果你经常创建自定义网络建议在 daemon.json 里统一配置默认地址池避免 Docker 自动分配时又随机挑到一个冲突网段。前面配置里的default-address-pools就是干这个的。这里我说一下size: 24表示每个自定义网络分配一个 /24 子网base: 10.88.0.0/16表示总共可以创建 256 个这样的网络对绝大多数生产服务器完全够用。3.5 容器重启后 IP 漂移 vs 服务名稳定的实战取舍默认情况下容器重启后如果没指定静态 IPDocker 会从网络的可用地址池里重新分配。这本来是为了避免冲突但对于依赖固定 IP 的旧系统就是灾难。我有一次处理过一个数据库同步失败的问题查了半天才发现是对端白名单里写的容器 IP 已经变成了另一个容器。怎么取舍我通常这么说新服务一律用服务名/DNS 方式互访不要碰静态 IP。少数没法改代码的遗留系统可以用静态 IP但必须保证静态 IP 不会被其他容器占用。指定静态 IP 的方法docker run -d --name legacy-db --network myapp-net --ip 10.88.1.10 mysql:8或者 compose 里networks: myapp-net: ipam: config: - subnet: 10.88.1.0/24 services: legacy-db: networks: myapp-net: ipv4_address: 10.88.1.10但要注意指定静态 IP 后该容器不会出现在 Docker DNS 中吗不会只要在同网络里服务名解析依然有效。静态 IP 只是额外约束不是替代方案。生产上能用动态就尽量动态把注意力放在服务名解析上省心得多。4. 生产环境容器安全加固能跌落但不给漏洞机会4.1 去掉 privileged用 capabilities 精细授权很多人在跑容器时习惯加--privileged因为这样在容器内操作设备、挂载文件、调整网络都不受限制。但这等于把 Docker 的全部安全防线都拆了。容器内的 root 直接获得宿主机 root 的绝大部分能力一旦应用被攻破宿主机的数据就是裸奔。更好的方案是只给必要的 Linux capabilities。比如容器需要修改系统时间却不想给整个 privileged就可以docker run -d --cap-add SYS_TIME \ --cap-drop ALL \ --security-opt no-new-privileges \ my-app--cap-drop ALL先移除所有能力再按需--cap-add这是最小权限的经典做法。常见应用其实只需要很有限的能力Nginx / Apache不需要任何特殊 cap只监听端口即可。Redis / MySQL默认就能跑不需要额外 cap。需要绑定低端口80/443的容器Docker 直接支持不需要 NET_BIND_SERVICE。需要调整网络配置的容器加NET_ADMIN但要评估风险。除了 capabilities--security-opt no-new-privileges也很重要它禁止容器内进程通过 setuid 等机制提升权限即使二进制文件有 SUID 位也没用。对于跑 Web 服务的容器我建议一律加上。Compose 写法services: app: image: my-app:latest cap_drop: - ALL cap_add: - NET_BIND_SERVICE security_opt: - no-new-privileges:true4.2 内存与 CPU 限制防止一个容器拖垮整台机器生产环境多容器共存只要有容器出现内存泄漏就可能导致宿主机 OOM其他业务跟着遭殃。所以每个容器都应该设置资源上限。docker run 参数docker run -d \ --name api \ --memory512m \ --memory-swap512m \ --cpus1.5 \ --restart unless-stopped \ myapp-api--memory限制内存上限--memory-swap如果不设容器会使用宿主机 swap可能导致实际内存超过--memory限制。我一般把两者设成一样彻底禁用 swap 对容器可用减少 OOM 的不可控性。--cpus设置 CPU 配额1.5 表示最多使用 1.5 个 CPU 核心。这套配置能让单个容器即使写死循环也不会影响其他容器。排查资源问题用docker statsdocker stats --no-stream如果经常被 OOM 告警打扰就趁早看哪个容器内存曲线异常调整限制或者治理代码。有个细节--restart unless-stopped不要写成always。always会导致容器即使被手动 stop重启 Docker 后也会自动起来排障时很烦unless-stopped则尊重手动停止的状态。4.3 日志轮转、只读根文件系统与 seccomp日志轮转在前面 daemon.json 已经统一配置了那是全局策略。如果某个容器想单独覆盖也可以在 run 参数里写--log-opt max-size10m。只读根文件系统是进一步收紧容器的手段。很多容器运行时会往/写临时文件如果直接--read-only应用可能会启动失败。常见做法是docker run -d \ --read-only \ --tmpfs /tmp:rw,noexec,nosuid,size128m \ my-app这样根文件系统是只读的只有/tmp可写且不允许执行文件。如果容器需要往/var/log写日志需要挂载命名卷或绑定挂载。这个方案不是所有应用都能直接套建议测试后再上但它确实能极大降低被写入了恶意文件的风险。seccomp 是 Linux 内核的系统调用过滤机制。Docker 默认给每个容器加载一个白名单自动阻止一部分危险系统调用。除非你有很特殊的需求否则不需要自定义 seccomp profile。只需要知道一点不要轻易给容器加--privileged或--security-opt seccompunconfined一旦关闭安全过滤容器内的攻击面会大幅增加。4.4 数据卷持久化与权限陷阱数据卷是容器持久化的核心。但 bind mount宿主机目录直接挂载有个经典权限问题容器内进程以非 root 用户运行而宿主机目录属主是 root容器内进程没有写权限启动直接报错。最典型的例子是 PostgreSQLdocker run -d \ --name pg \ -v /data/pg:/var/lib/postgresql/data \ -e POSTGRES_PASSWORD... \ postgres:16启动日志报/var/lib/postgresql/datahas wrong ownership原因是 postgres 镜像里默认用户postgres的 uid 是 999而宿主机/data/pg的属主是 root。解决办法是先把目录属主改成 999sudo mkdir -p /data/pg sudo chown -R 999:999 /data/pg但更通用的做法是在容器声明中使用命名卷由 Docker 管理属主和数据生命周期。Docker 会在第一次创建命名卷时把镜像里对应目录的属主和权限复制到卷上这样基本不会踩权限坑。命名卷定义在 compose 里services: pg: image: postgres:16 volumes: - pg_data:/var/lib/postgresql/data volumes: pg_data:但生产环境备份时很多人还是习惯 bind mount因为备份直接操作宿主机路径就行。如果确实要用 bind mount就把权限提前确认好。我遇到过有人 chown 了根目录导致宿主机系统异常所以权限操作一定只针对挂载目录不要扩大到上层。5. 实测排障记录容器访问外网超时的完整链路5.1 三个容器、一个故障、七条 iptables有一次我帮客户处理故障拓扑很简单宿主机上跑了三个容器分别是nginx、api、worker。nginx通过-p 80:80暴露到宿主机api和worker跑在默认 bridge 网络。事故现象是从worker容器里访问外网 APIHTTPS一直超时但宿主机访问同样地址是正常的nginx容器访问外网也正常。第一反应是 DNS 或者出网路由问题但排查了一圈都没毛病。最后用docker exec -it worker sh进入容器curl -v https://api.example.com卡在连接阶段毫无响应。接着用 tcpdump 抓包# 宿主机上抓取容器网卡流量 sudo tcpdump -i docker0 -n host worker-container-ip发现容器发出去了 SYN 包但没有收到回包。这说明问题出在 NAT 层容器发出的包源地址是 172.17.0.x如果不经过恰当的 MASQUERADE外网回包就找不到这个容器。5.2 从 tcpdump 到 tcpdump一个老问题的新答案顺着 NAT 方向继续查看到 iptables 的 POSTROUTING 链sudo iptables -t nat -L POSTROUTING -n -v输出里没有看到针对 172.17.0.0/16 的 MASQUERADE 规则。正常情况 Docker 会有一条MASQUERADE all -- 172.17.0.0/16 !172.17.0.0/16这条规则的作用是容器访问外部网络时把源 IP 改成宿主机 IP。现在规则缺失了外网回包不知道要交给谁自然就卡住。为什么规则会丢回溯操作记录发现这台机器前两天有人手工执行过iptables -F然后重启 Docker 时因为 daemon.json 里曾经设置过iptables: falseDocker 没有自动重建规则。后来把iptables: false改回了true但 Docker 网络规则的重建需要重启整个 Docker 服务而当时只重启了容器所以残留了这种容器能起、网络规则不完整的状态。这就是一个非常典型的操作顺序坑Docker 是否管理 iptables完全取决于 Docker 服务启动那一刻 daemon.json 的配置。中途改配置必须systemctl restart docker单纯重启容器不生效而且有时候还要删除重建网络才能彻底恢复。5.3 修复默认 bridge 换自定义网络后的变化当时为了快速恢复我先手动补上那条缺失的 NAT 规则sudo iptables -t nat -A POSTROUTING -s 172.17.0.0/16 ! -d 172.17.0.0/16 -j MASQUERADE然后 worker 容器立刻就能访问外网了。但我知道这是临时救火不能留着这种脆弱状态。真正修复是修改daemon.json确认iptables: true然后重启 Docker 服务让 Docker 自己管理所有规则。重启后我还做了一件事把api和worker从默认 bridge 网络迁移到自定义网络mynet。原因有两条一是自定义网络的 DNS 解析能力更强服务之间不用维护 IP 映射二是自定义网络的 iptables 规则维护更清晰不容易出现默认 bridge 规则被误删后找不到的问题。迁移方法docker network create mynet docker network connect mynet api docker network connect mynet worker确认迁移后服务间通信改用容器名nginx上游配置也改成api:8080彻底摆脱 IP 依赖。这个问题后来再没有复发过。5.4 从这次故障里总结的 Docker 网络排查清单那次之后我整理了一份 Docker 网络问题排查清单现在每次遇到类似故障就直接按顺序走先看容器状态和网络配置docker ps、docker network ls、docker inspect 容器名确认容器连接到了哪个网络IP 是否正常。在容器内测试连通性docker exec -it 容器名 curl -v 目标地址区分是容器内问题还是跨网络问题。抓包确认包去向宿主机上用tcpdump -i docker0 -n host 容器IP观察请求是否发出、有无回包。检查 NAT 规则iptables -t nat -L POSTROUTING -n -v看看容器网段有没有对应的 MASQUERADE 规则。检查 FORWARD 链策略和 DOCKER 链iptables -L FORWARD -n -v确认 ufw 或自建防火墙没有拦截 docker 桥的转发。确认 IP 转发已开启sysctl net.ipv4.ip_forward如果为 0容器外网必然不通。如果以上都正常重启 Docker 服务之前先看 daemon.json确认iptables是否为true再做systemctl restart docker。这套清单对 90% 的 Docker 网络问题都有效。很多时候问题不是出在应用层而是底层网络规则被别人或者脚本动过所以排查时一定要有耐心别一上来就重启 Docker先看规则再动服务。最后再分享一个小技巧生产环境容器里往往没有 curl、tcpdump、ping 这些工具临时排障时不用装包可以直接启动一个网络诊断容器共享目标容器的网络命名空间docker run -it --rm --network container:目标容器名 nicolaka/netshoot这样诊断容器能看到目标容器完全一样的网络接口和路由里面自带 ping、tcpdump、nmap、drill 等工具排障效率高用完即删不会污染宿主机环境。我在前面那起故障排查里就靠它节约了不少时间。
上一篇/下一篇内容由系统自动关联 返回资讯列表 →