遇到一个诡异的问题,在固定的 VPC 环境里运行了一年的 ECS 机器,突然连不上 RDS 数据库,而这个问题在早些时候,也曾在另外一台机器上出现过。

为了避免后续在业务日常运行和重大活动过程中出现类似问题,我们和阿里云进行了反馈,并进行的排查。

写在前面

由于同一业务分组的几台机器中,有两台(暂命名为 host-prehost-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-02host-03 的网卡指定网段都在 172.27~30.1.1/8 内,而 host-01host-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