本文使用「署名 4.0 国际 (CC BY 4.0)」许可协议,欢迎转载、或重新修改使用,但需要注明来源。 [署名 4.0 国际 (CC BY 4.0)](https://creativecommons.org/licenses/by/4.0/deed.zh) 本文作者: 苏洋 创建时间: 2020年12月04日 统计字数: 6314字 阅读时间: 13分钟阅读 本文链接: https://soulteary.com/2020/12/04/cloud-service-container-network-problems-that-could-not-be-avoided.html ----- # 没能躲开的云服务容器网络问题 遇到一个诡异的问题,在固定的 VPC 环境里运行了一年的 ECS 机器,突然连不上 RDS 数据库,而这个问题在早些时候,也曾在另外一台机器上出现过。 为了避免后续在业务日常运行和重大活动过程中出现类似问题,我们和阿里云进行了反馈,并进行的排查。 ## 写在前面 由于同一业务分组的几台机器中,有两台(暂命名为 `host-pre`、`host-01`)都出现了这个问题,所以我们将同一业务分组的几台机器同时作为样本进行问题排查。 在解决问题过程的时候,我们首先收集了基础环境的相关线索: - 机器运行应用均为无状态容器,没有持久化内容存在。 - 被测试的机器均处于相同 VPC 环境内,为避免容器网络问题,2019 年初始化 VPC 时使用了比较不容易撞车的 `192.168.73.x` 网段。。 - 机器、数据库都没有关闭 ICMP。 - 机器的安全策略组允许访问 RDS。 - 机器在数据库白名单之内。 - 这几台机器都是于去年购买,在一个月前执行过系统版本和软件升级。 - 这几台机器在半年前执行过硬件配置升降级。 ## 问题状况:连不通的数据库 分别使用服务器对数据库进行 `ping`: ```TeXT ssh host-pre ping rm-intra.mysql.rds.aliyuncs.com PING rm-intra.mysql.rds.aliyuncs.com (192.168.0.166) 56(84) bytes of data. From host-pre (192.168.0.1) icmp_seq=1 Destination Host Unreachable From host-pre (192.168.0.1) icmp_seq=2 Destination Host Unreachable From host-pre (192.168.0.1) icmp_seq=3 Destination Host Unreachable ssh host-01 ping rm-intra.mysql.rds.aliyuncs.com PING rm-intra.mysql.rds.aliyuncs.com (192.168.0.166) 56(84) bytes of data. 64 bytes from 192.168.0.166 (192.168.0.166): icmp_seq=1 ttl=102 time=1.11 ms 64 bytes from 192.168.0.166 (192.168.0.166): icmp_seq=2 ttl=102 time=1.09 ms 64 bytes from 192.168.0.166 (192.168.0.166): icmp_seq=3 ttl=102 time=1.12 ms ssh host-02 ping rm-intra.mysql.rds.aliyuncs.com PING rm-intra.mysql.rds.aliyuncs.com (192.168.0.166) 56(84) bytes of data. 64 bytes from 192.168.0.166 (192.168.0.166): icmp_seq=1 ttl=102 time=0.992 ms 64 bytes from 192.168.0.166 (192.168.0.166): icmp_seq=2 ttl=102 time=0.994 ms 64 bytes from 192.168.0.166 (192.168.0.166): icmp_seq=3 ttl=102 time=0.977 ms ssh host-03 ping rm-intra.mysql.rds.aliyuncs.com PING rm-intra.mysql.rds.aliyuncs.com (192.168.0.166) 56(84) bytes of data. 64 bytes from 192.168.0.166 (192.168.0.166): icmp_seq=1 ttl=102 time=1.07 ms 64 bytes from 192.168.0.166 (192.168.0.166): icmp_seq=2 ttl=102 time=1.07 ms 64 bytes from 192.168.0.166 (192.168.0.166): icmp_seq=3 ttl=102 time=1.04 ms ``` 发现只有第一台 `host-pre` 出现了 *Destination Host Unreachable*,其余几台均正常。`host-01` 在早些时候也出现过相同的问题。 ## 登录服务器:进一步探查问题 既然 `host-pre` 出现问题,我们就先来排查下它的容器运行状况是否出现问题。登录机器 ,忽略掉最近更新变动的应用,可以看到机器上目前运行最久的应用的启动时间是七个月前,分别使用 `exec` ,以及 `curl` 请求本地服务,都有正常的反馈,所以首先可以排除是容器应用自身的问题。 ```TeXT docker ps -a ... c092f8c5f41e docker.dev.baai.ac.cn/nesletter-api:0.9.1 "docker-php-entrypoi…" 4 months ago Up 4 months (healthy) 9000/tcp newsletter-api ed0c7fb1945f docker.dev.baai.ac.cn/hub-node-gate:14 "docker-entrypoint.s…" 4 months ago Up 4 months 3000/tcp xxxxx_1 7aba2cbc2e21 traefik:v2.2.0 "/entrypoint.sh trae…" 5 months ago Up 5 months (healthy) 0.0.0.0:80->80/tcp traefik 1a9e0a150133 xxx:4.7.6-xxx "entrypoint.sh docke…" 7 months ago Up 7 months 8080/tcp xxx_1 ``` 接着我们试着在 `host-pre` 上 ping 其他的服务器,发现也是正常的,所以 VPC 网络内的连通性也是没有问题的,那么问题应该是出现在了 “ECS 或 VPC 到 RDS” 的网络被“阻塞”了。 ## 排查路由表:定位问题 随后,阿里云 ECS 的工程师建议我们进行路由表的排查,于是我们分别在几台机器上查看了路由规则状况: ```TeXT ssh host-pre route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.73.253 0.0.0.0 UG 100 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-df03b027a5e8 192.168.0.0 0.0.0.0 255.255.240.0 U 0 0 0 br-c3c094fc6759 192.168.73.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 192.168.73.253 0.0.0.0 255.255.255.255 UH 100 0 0 eth0 ssh host-01 route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.73.253 0.0.0.0 UG 100 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-0cb13ae8df3c 192.168.32.0 0.0.0.0 255.255.240.0 U 0 0 0 br-bb02c47906ee 192.168.73.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 192.168.73.253 0.0.0.0 255.255.255.255 UH 100 0 0 eth0 ssh host-02 route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.73.253 0.0.0.0 UG 100 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-405544233f47 172.28.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-f11216ce202f 192.168.73.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 192.168.73.253 0.0.0.0 255.255.255.255 UH 100 0 0 eth0 ssh host-03 route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.73.253 0.0.0.0 UG 100 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-df03b027a5e8 172.19.0.0 0.0.0.0 255.255.255.0 U 0 0 0 br-5089b1504e22 192.168.73.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 192.168.73.253 0.0.0.0 255.255.255.255 UH 100 0 0 eth0 ``` 可以看到机器基础 VPC 网络都在 `192.168.73.1/24` 段内,唯一有差别的是 docker 创建的桥接网卡。 `host-02` 和 `host-03` 的网卡指定网段都在 `172.27~30.1.1/8` 内,而 `host-01` 和 `host-pre` 的网卡则出现了两张在 `192.168.x.x` 的网卡: ```TeXT host-pre 192.168.0.0 0.0.0.0 255.255.240.0 U 0 0 0 br-c3c094fc6759 host-01 192.168.32.0 0.0.0.0 255.255.240.0 U 0 0 0 br-bb02c47906ee ``` 看到路由表之后,阿里云工程师反馈网络冲突了,第一反应确实如此,因为在排查连通性的时候,我们确实看到了当前连接 RDS 的地址是 `192.168.0.xxx` 的远程地址。但是随后我们又想到了一个问题,为什么这个问题现在才出现,或者说,为什么之前的业务运行没有受到影响呢? 此时阿里云工程师提示我们“RDS 实例IP地址可能发生变化,连接串则始终不变,请使用以上连接串进行实例连接。” 到了这个时候,答案呼之欲出:**容器创建应用内部桥接网卡的网段和阿里云 RDS 网络撞车了。** 虽然概率很小,但是它确实出现了,因为 CI/CD 过程中容器会随机创建新的应用内部网卡,赶巧和 RDS 网络切换后的地址撞在了一起,就会出现这个问题。 和阿里云工程师沟通确认 “ 192.168.0.0/16 是 RDS 所在 VPC 的网段”后,就可以放心动手解决问题了。 ## 解决问题:修改容器网卡地址分配规则 登录 `host-pre` ,先再次打印路由表,以及查看容器创建的网卡列表: ```TeXT host-pre:~# route -n Kernel IP routing table Destination Gateway Genmask Flags Metric Ref Use Iface 0.0.0.0 192.168.73.253 0.0.0.0 UG 100 0 0 eth0 172.17.0.0 0.0.0.0 255.255.0.0 U 0 0 0 docker0 172.18.0.0 0.0.0.0 255.255.0.0 U 0 0 0 br-df03b027a5e8 192.168.0.0 0.0.0.0 255.255.240.0 U 0 0 0 br-c3c094fc6759 192.168.73.0 0.0.0.0 255.255.255.0 U 0 0 0 eth0 192.168.73.253 0.0.0.0 255.255.255.255 UH 100 0 0 eth0 host-pre:~# docker network ls NETWORK ID NAME DRIVER SCOPE 0fba8dbbbff8 bridge bridge local 8b92ba96f640 host host local c3c094fc6759 project-grpup_project-name bridge local 123b8780367b none null local df03b027a5e8 traefik bridge local ``` 可以看到 `br-c3c094fc6759` 这个网卡在 docker 中命名为 `c3c094fc6759`,是由 `project-grpup_project-name` 这个项目创建,使用的网络段确实是和 RDS 发生了冲突。 通过查看 docker [官方文档](https://github.com/docker/cli/blob/master/docs/reference/commandline/dockerd.md),我们可以找到我们需要的 daemon 配置项 `default-address-pools`,来主动规避掉和阿里云 RDS 网络冲突的问题。 修改 `/etc/docker/daemon.json` ,声明和 `192.168.0.0/16` 不冲突的地址: ```TeXT { ... "default-address-pools": [ { "base": "172.18.0.0/16", "size": 24 }, { "base": "172.19.0.0/16", "size": 24 }, { "base": "172.20.0.0/16", "size": 24 }, { "base": "172.21.0.0/16", "size": 24 }, { "base": "172.22.0.0/16", "size": 24 }, ... ] } ``` 在修改配置后,执行 `service docker restart`,接着重新启动应用,触发重新创建内部网卡逻辑,然后再次查看容器网卡列表: ```TeXT docker network ls NETWORK ID NAME DRIVER SCOPE 52a7bbc2b5fe bridge bridge local 8b92ba96f640 host host local 5089b1504e22 project-grpup_project-name bridge local 123b8780367b none null local df03b027a5e8 traefik bridge local ``` 看到创建的新网卡为 `5089b1504e22`,我们使用 `inspect` 检查网卡分配网络: ```TeXT docker inspect 5089b1504e22 --format='{{json .IPAM.Config}}' [{"Subnet":"172.19.0.0/24","Gateway":"172.19.0.1"}] ``` 可以看到分配网络与我们预期一致,规避了 `192.168.0.0/16` ,至此问题解决。 ## 最后 如同“墨菲定律”所言,凡是可能出错的事情就一定会出错。 为了避免出错,靠谱的方案是:彻底查出可能出现事故的问题所在和根本原因,对其进行标本兼治的方案,才能防患于未然。 --EOF