宝塔 + Docker 混用踩坑:容器停了网站还能开?CNI 残留端口排查实战

📅 2026/7/22 2:13:22
宝塔 + Docker 混用踩坑:容器停了网站还能开?CNI 残留端口排查实战
宝塔 Docker 混用踩坑域名能访问但页面不更新、容器停了还能打开站点一篇讲透排查与修复适用场景腾讯云 CVM 宝塔 Nginx 反代 Docker Compose 部署前端/后端卸载/重装 Docker、混用过 Podman 后出现「怪现象」。本文按真实排障顺序书写方便对照复现与复盘。一、现象汇总你可能中招的几种代码已经在masterDocker 也 build / up 了浏览器页面还是旧 UI例如旧「热门榜」侧栏而不是新的「热门爆款」。docker ps里前端/后端容器已经Exited访问 https://你的域名 却仍能打开。ss/lsof看不到18090监听但curl http://127.0.0.1:18090却返回 200。清理容器、删网络、甚至systemctl restart docker之后iptables/nft 里仍有指向172.28.0.x的 DNAT。装官方 Docker CE 时 yum 冲突、拉 Hub 超时、compose 找不到等问题附属坑。表面看像「反代坏了」「构建没生效」根因往往是流量根本没打到你以为的那个容器上。二、目标架构先建立正确心智模型以测试站test.web.cn为例正确链路应是浏览器 → HTTPS :443宝塔 Nginx → proxy_pass http://127.0.0.1:18090 → docker-proxy → 前端容器如 172.30.0.11:80 → /api 再反代到后端容器如 172.30.0.10:8000宝塔站点配置里关键几行通常长这样server_name test.web.cn; root /www/wwwroot/test.web.cn; # 有反代时业务流量主要不靠这个 root location ^~ / { proxy_pass http://127.0.0.1:18090; proxy_set_header Host $http_host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; }因此改代码 / 重建镜像→ 影响的是18090后面的前端容器内容宝塔 Nginx 挂了→ 域名443直接ERR_CONNECTION_REFUSED18090被旧 CNI 拐走→ 域名「能开」但永远是旧页面。三、排障路线图建议严格按顺序① 源码是否已是新版 ② 运行中的前端镜像/静态资源是否含新关键字 ③ 宝塔反代是否指向 127.0.0.1:18090 ④ 18090 到底是谁在应答ss / curl / iptables ⑤ 是否存在残留 netns / 残留 CNI DNAT ⑥ 清理后用新网段重新 up并确认 Last-Modified / DNAT 目标 IP ⑦ 宝塔 Nginx 443 是否在听下面按步骤展开命令与判读方法。四、步骤 1先确认代码本身有没有新页面在项目目录执行cd/path/to/web-new-pcgitrev-parse--shortHEADgitbranch --show-current# 新版前端组件是否存在lsfrontend/apps/web-ele/src/views/yb/order-center/overseas-story/components/hot-burst-row.vuegrep-nlibrary-tabs\|HotBurstRow\frontend/apps/web-ele/src/views/yb/order-center/overseas-story/index.vue|head判读结果含义文件存在且 grep 有HotBurstRow/library-tabs源码已是新版问题在部署链路没有这些文件分支/提交不对先git pull再谈构建补充若git pull因本地改过backend/.env失败gitstash push-mserver-env-- backend/.envgitpullgitstash pop# 冲突时以服务器真实库地址/密码为准五、步骤 2确认「正在跑的前端包」是不是旧的dockerps--filternameYoubaoNewPc-frontend--format{{.ID}} {{.Image}} {{.CreatedAt}}IMG$(dockerinspect YoubaoNewPc-frontend--format{{.Image}})dockerrun--rm--entrypointsh$IMG-c\grep -Rsl 热门爆款\|library-tabs /usr/share/nginx/html/assets 2/dev/null | head; echo donecurl-sIhttp://127.0.0.1:18090/|grep-iEHTTP|Last-Modified|ETag|Server判读镜像里搜不到新关键字 /Last-Modified是很早以前的日期 →镜像是旧包需要exportDOCKER_REGISTRY_MIRRORdocker.m.daocloud.io/library/# 国内拉 Hub 常用dockercompose-fdocker-compose.prod.yml build --no-cache frontenddockercompose-fdocker-compose.prod.yml up-d--force-recreate frontend源码新、镜像也新浏览器仍旧 → 清浏览器缓存或无痕窗口并检查宝塔proxy_cache_dir。六、步骤 3核对宝塔反代很多人第一步就查错方向grep-Rsn你的域名/www/server/panel/vhost/nginx/grep-nEroot |proxy_pass/www/server/panel/vhost/nginx/你的域名.conf若已是proxy_pass http://127.0.0.1:18090;说明反代配置本身通常没问题不要先怀疑「指到了网站根目录静态文件」。有location ^~ / { proxy_pass ... }时业务流量走反代不走root。七、步骤 4抓住「矛盾现场」本文核心典型矛盾输出dockerps-a|grep-iweb# → YoubaoNewPc-frontend Exitedss-lntp|grep18090# → 空curl-sIhttp://127.0.0.1:18090/# → HTTP/1.1 200 OK Last-Modified 却是旧日期这时立刻查 NATiptables-tnat-L-n-v|grep-E18090|18000nft list ruleset2/dev/null|grep-E18090|18000若看到类似DNAT tcp dpt:18090 to:172.28.0.11:80且计数器在涨packets 0结论就是宿主机 18090 被 CNI/iptables DNAT 到了旧网桥 IP真正应答的不是当前docker ps里的容器。再验证残留 IPcurl-sI--max-time2http://172.28.0.11/|head-10ip-4addr|grep172.28ls/run/netns若存在cni-podman1、/run/netns/netns-xxxx基本实锤Podman/旧 CNI 残留网络命名空间即使系统里已经没有podman命令。八、步骤 5定位并清理「残留 netns」列出残留命名空间里的地址与监听fornsin/run/netns/*;doname$(basename$ns)echo$nameipnetnsexec$nameip-4addripnetnsexec$namess-lntpdone真实案例中常见netnsIP监听含义netns-A172.28.0.11nginx :80残留前端旧静态资源netns-B172.28.0.10php :8000残留后端按ss里显示的 PID 强杀PID 以你机器为准kill-9nginx主进程及worker PID...php PIDipnetnsexec前端netns名killall-9nginx2/dev/nullipnetnsexec后端netns名killall-9php2/dev/null删除 netns、拆掉旧网桥ipnetns del前端netns名ipnetns del后端netns名ls/run/netns# 应为空iplinksetcni-podman1 down2/dev/nulliplinkdelete cni-podman12/dev/nullip-4addr|grep172.28||echo172.28 gone清理 18090/18000 相关 NAT循环删到 grep 为空forchaininPREROUTING OUTPUT;dowhilenum$(iptables-tnat-L$chain-n--line-numbers|awk/18090|18000|CNI-DN-|CNI-HOSTPORT/{print $1; exit});do[-n$num]||breakiptables-tnat-D$chain$num||breakdonedoneiptables-tnat-L-n|grep-E18090|18000||echoNAT cleared验收这两步必须失败/超时curl-sI--max-time2http://172.28.0.11/||echostale frontend gonecurl-sI--max-time2http://127.0.0.1:18090/||echo18090 dead只有确认残留进程已清掉再compose up才有意义否则宝塔反代会继续打到旧 IP。九、步骤 6用干净网段重新拉起 Compose旧项目若把 bridge 固定在172.28.0.0/16极易与cni-podman1冲突。建议改为例如172.30.0.0/16docker-compose.prod.yml示例services:backend:networks:YoubaoNewPcBridgeNet:ipv4_address:172.30.0.10frontend:networks:YoubaoNewPcBridgeNet:ipv4_address:172.30.0.11networks:YoubaoNewPcBridgeNet:driver:bridgeipam:config:-subnet:172.30.0.0/16gateway:172.30.0.1前端容器内 Nginx 反代后端也要同步例如location /api/ { proxy_pass http://172.30.0.10:8000; }注意只改容器 IP、不改subnet会报no configured subnet or ip-range contain the IP address 172.30.0.10需要docker compose down删掉旧 network 再 up或先docker network rm ...。启动cd/path/to/web-new-pc# 不要在错误目录执行systemctl startdockerdockercompose-fdocker-compose.prod.yml up-d--force-recreate ss-lntp|grep-E18090|18000iptables-tnat-L-n|grep-E18090|18000# 期望to:172.30.0.11:80 / to:172.30.0.10:8000# 不应再出现 172.28.0.11curl-sIhttp://127.0.0.1:18090/|grep-iEHTTP|Last-Modified|Serverdockerps若仍同时存在172.28与172.30两套 DNAT把旧 CNI 规则按上一节继续删干净。十、步骤 7域名仍ERR_CONNECTION_REFUSED时查 443容器 OK、curl 127.0.0.1:18090OK但浏览器提示「拒绝连接」多半是宝塔 Nginx 没在听 80/443ss-lntp|grep-E:80|:443nginx-t/etc/init.d/nginx restart# 或 systemctl restart nginxcurl-Ik--resolvetest.web.cn:443:127.0.0.1 https://test.web.cn/|head-15整条链路自检curl-sIhttp://127.0.0.1:18090/|head-5curl-Ikhttps://127.0.0.1/-HHost: test.web.cn|head-10浏览器用无痕窗口打开域名。十一、附属坑安装 Docker / 构建镜像时常见问题11.1 宝塔 Docker 卸掉后docker变成 Podman 模拟报错示例Emulate Docker CLI using podman looking up compose provider failed处理安装真正的 Docker CE compose 插件不要继续用宝塔 Docker也不要混用未清理干净的 Podman CNI。11.2 TencentOS 装 Docker CE$releasever变成 3.3 → 404rm-f/etc/yum.repos.d/docker-ce.repocurl-fsSL-o/etc/yum.repos.d/docker-ce.repo\https://mirrors.aliyun.com/docker-ce/linux/centos/docker-ce.reposed-is/\$releasever/8/g/etc/yum.repos.d/docker-ce.repo yuminstall-ydocker-ce docker-ce-cli containerd.io docker-compose-plugin--allowerasingsystemctlenable--nowdocker--allowerasing用于解决containerd.io与系统runcAppStream/Podman 模块冲突。11.3 构建拉不到php:8.4-cliDocker Hub 超时配置 daemon 镜像加速或构建时加前缀# /etc/docker/daemon.json{registry-mirrors:[https://mirror.ccs.tencentyun.com,https://docker.m.daocloud.io]}systemctl restartdocker# 或单次构建exportDOCKER_REGISTRY_MIRRORdocker.m.daocloud.io/library/ ./scripts/exc_build.sh build11.4 容器访问腾讯云 MySQL 内网No route to host对照实验# 宿主机往往通nc-zv172.21.0.73306# 默认 bridge 可能通dockerrun--rmbusyboxnc-zv172.21.0.73306# 项目自定义网桥旧 172.28可能不通dockerrun--rm--networkweb-new-pc_YoubaoNewPcBridgeNet busyboxnc-zv172.21.0.73306若「默认通、项目网不通」优先怀疑网段与 cni-podman 冲突 / FORWARD/NAT而不是数据库账号。内网连接注意端口配对内网 IP 用3306外网域名常用映射端口如 23250不要混用。十二、复盘这次问题的「一句话因果链」历史上用 Podman/CNI或宝塔 Docker起过前端/后端网桥落在172.28.0.0/16并写入 hostport DNAT18090→172.28.0.11。后来容器在docker ps里显示退出甚至 Docker 重装netns 里的 nginx/php 与 CNI 规则却还活着。宝塔 Nginx 一直proxy_pass到127.0.0.1:18090流量被 DNAT 进残留 netns所以容器停了域名还能开新镜像 up 了页面仍是旧的或和残留规则抢流量。清掉残留 netns 旧 NAT改用172.30网段重新 up宝塔 443 正常后链路才真正指向新前端。十三、建议的「健康检查」清单可做成上线脚本每次发布后执行#!/bin/bashset-eecho containers ;dockerps--formattable {{.Names}}\t{{.Status}}\t{{.Ports}}echo listen ;ss-lntp|grep-E18090|18000|:443|:80||trueecho frontend header ;curl-sIhttp://127.0.0.1:18090/|grep-iEHTTP|Last-Modified|Serverecho nat ;iptables-tnat-L-n|grep-E18090|18000||trueecho leftover netns ;ls/run/netns2/dev/null||echo(none)echo 172.28 ;ip-4addr|grep172.28||echo(none)异常信号/run/netns非空且与业务无关NAT 同时出现172.28与172.30docker ps无容器但curl :18090仍 200443无 nginx 监听。十四、经验教训发到 CSDN 也可当结语docker ps不是真理还要看ss、curl、iptables/nft、/run/netns。宝塔反代「配置正确」≠「打到正确进程」中间可能被 CNI DNAT 劫持。不要混用宝塔 Docker / Podman / 官方 Docker 而不清理 CNI。固定容器 IP 时避开常见冲突网段如本机已有cni-podman的172.28/16。验证部署是否更新看静态资源Last-Modified/ 包内关键字比「我 build 过了」更可靠。域名CONNECTION_REFUSED先查宿主机 80/443别只盯着容器。十五、快速命令速查复制区# 宝塔反代指向grep-nEproxy_pass|root /www/server/panel/vhost/nginx/你的站点.conf# 谁在答 18090ss-lntp|grep18090curl-sIhttp://127.0.0.1:18090/|headiptables-tnat-L-n-v|grep18090# 残留 netnsls/run/netnsfornsin/run/netns/*;doipnetnsexec$(basename$ns)ss -lntp;done# 清理后重建示例cd/path/to/web-new-pcdockercompose-fdocker-compose.prod.yml downdockercompose-fdocker-compose.prod.yml up-d--force-recreate /etc/init.d/nginx restart作者说明本文基于腾讯云 CVM 宝塔 Nginx Docker Compose 实战排障整理命令中的 IP、PID、域名请按现场替换。若你有更干净的「一键清理 CNI hostport」脚本欢迎在评论区补充。完