遇到一个诡异的问题,在固定的 VPC 环境里运行了一年的 ECS 机器,突然连不上 RDS 数据库,而这个问题在早些时候,也曾在另外一台机器上出现过。
为了避免后续在业务日常运行和重大活动过程中出现类似问题,我们和阿里云进行了反馈,并进行的排查。
写在前面
由于同一业务分组的几台机器中,有两台(暂命名为 host-pre
、host-01
)都出现了这个问题,所以我们将同一业务分组的几台机器同时作为样本进行问题排查。
在解决问题过程的时候,我们首先收集了基础环境的相关线索:
- 机器运行应用均为无状态容器,没有持久化内容存在。
- 被测试的机器均处于相同 VPC 环境内,为避免容器网络问题,2019 年初始化 VPC 时使用了比较不容易撞车的
192.168.73.x
网段。。 - 机器、数据库都没有关闭 ICMP。
- 机器的安全策略组允许访问 RDS。
- 机器在数据库白名单之内。
- 这几台机器都是于去年购买,在一个月前执行过系统版本和软件升级。
- 这几台机器在半年前执行过硬件配置升降级。
问题状况:连不通的数据库
分别使用服务器对数据库进行 ping
:
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
请求本地服务,都有正常的反馈,所以首先可以排除是容器应用自身的问题。
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 的工程师建议我们进行路由表的排查,于是我们分别在几台机器上查看了路由规则状况:
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
的网卡:
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
,先再次打印路由表,以及查看容器创建的网卡列表:
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 官方文档,我们可以找到我们需要的 daemon 配置项 default-address-pools
,来主动规避掉和阿里云 RDS 网络冲突的问题。
修改 /etc/docker/daemon.json
,声明和 192.168.0.0/16
不冲突的地址:
{
...
"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
,接着重新启动应用,触发重新创建内部网卡逻辑,然后再次查看容器网卡列表:
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
检查网卡分配网络:
docker inspect 5089b1504e22 --format='{{json .IPAM.Config}}'
[{"Subnet":"172.19.0.0/24","Gateway":"172.19.0.1"}]
可以看到分配网络与我们预期一致,规避了 192.168.0.0/16
,至此问题解决。
最后
如同“墨菲定律”所言,凡是可能出错的事情就一定会出错。
为了避免出错,靠谱的方案是:彻底查出可能出现事故的问题所在和根本原因,对其进行标本兼治的方案,才能防患于未然。
–EOF