Nova Kwok's Awesome Blog

我 EKS 内的 Pod 怎么连不上某个 EC2 了?奇怪的 Docker Compose 桥接网络 Debug 记

书接上回:「Web 应用想弹性扩缩容还必须用 AWS?在 Amazon Elastic Kubernetes Service (EKS) 上部署一个典型 Web App 的笔记」,已经有了一个 EKS 集群,并且已经成功将一些应用丢到 EKS 中跑起来了,但是由于历史遗留问题,还是有些应用是以 docker-compose 的方式跑在原有的 EC2 下的,且需要和 EKS 内的某些应用联动。

但是在联动的过程中,发现 EKS 的 Pod 始终无法连接到某一个 EC2,表现为所有的连接都会超时,ping 也不通,同 VPC 下的其他 EC2 都是可以正常连接的,于是针对这个问题进行排查。

Network

在网络层面,我们知道 EKS 和 EC2 不在一个 VPC 下,是通过 VPC Peering 和静态路由做通的:

EC2 VPC 段:

EKS VPC 段:

所有的 EC2 机器都没有开 UFW,也没有额外的 iptables 规则,SG 已经配置了允许来自 EKS VPC 的流量,这个也解释了为什么 EKS 内到其他的 EC2 和 RDS 之类的服务都是可以直接通的。

Service

所有 EC2 上的所有服务都是通过 docker-compose.yml 部署的,写法类似如下:

version: "3"
services:
  yyyy:
    image: ghcr.io/xxx/yyyy:latest
    restart: always
    ports:
      - '8080:8080'
    environment:
      DB_HOST: 'db'
      DB_PORT: '3306'
      DB_DATABASE: 'yyyy'
      DB_USERNAME: 'root'
      DB_PASSWORD: 'password'
      APP_DEBUG: 'true'
    volumes:
      - ./.env:/app/.env

  yyyy-service:
    image: ghcr.io/xxx/yyyy-service:latest
    restart: always
    environment:
      - DSN=root:password@tcp(db:3306)/yyyy?charset=utf8mb4&parseTime=True&loc=Local
      - REDIS=redis:6379
      - ENV=mini

    ports:
      - '8090:8080'

Docker 的安装方式也完全一致,但是就某一台 EC2 的机器没法从 EKS 内访问,接下来请聪明的读者花一柱香的时间想想看,这里可能会有什么奇怪的问题~

Debug

排查过程第一反应想到了是不是 UFW 或者 SG 设置不对,但是发现 UFW 根本没开,SG 也是通的,甚至其他的 EC2 都是可以访问的,百思不得其解,感觉 PingCAP 集群内 Pod 莫名无法联网的问题又逐渐回到了心头。

我不会这么背吧,用 EKS 也能遇到这种奇葩问题

看了一下 iptables 也没有奇怪的规则,正准备放弃并开始 Google 时,连接到 EC2 上的一个提示提醒了我:

Welcome to Ubuntu 22.04 LTS (GNU/Linux 5.15.0-1011-aws x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

  System information as of Wed Dec  7 03:32:08 UTC 2022

  System load:                      2.35498046875
  Usage of /:                       55.7% of 193.66GB
  Memory usage:                     79%
  Swap usage:                       0%
  Processes:                        721
  Users logged in:                  1
  IPv4 address for br-65abb51ee0f8: 172.24.0.1
  IPv4 address for br-69e9038f7cbe: 192.168.176.1
  IPv4 address for br-72d131ef9461: 172.19.0.1
  IPv4 address for br-777aba48864a: 192.168.96.1
  IPv4 address for br-a9e07fa9b259: 172.22.0.1
  IPv4 address for br-f06719849d74: 192.168.192.1
  IPv4 address for docker0:         172.17.0.1
  IPv4 address for ens5:            172.31.5.22

  => There are 3 zombie processes.

事后发现这个是非常重要的一个提示,所以这里打算再等一柱香时间请聪明的读者想想看~

这里的主要问题就在于:IPv4 address for br-777aba48864a: 192.168.96.1 这个,为什么这个段看上去和 EKS 的段有重叠?

首先看到是 br 开头的网卡,第一反应就是 Docker 自己搞出来的 Network,通过 docker network ls 我们可以看到这些网络都是哪些容器在搞:

NETWORK ID     NAME                                                      DRIVER    SCOPE
72066261ca9e   bridge                                                    bridge    local
a9e07fa9b259   clickhouse_default                                        bridge    local
65abb51ee0f8   dddd_default                                              bridge    local
cabd6fc84675   xxxx-mxxxxxx_default                                      bridge    local
45ecdfc7a691   host                                                      host      local
777aba48864a   monitoring_default                                        bridge    local
4c95c382c5a8   none                                                      null      local
503d7d864c98   xxx-xxx-up_default                                        bridge    local
c5946111d925   redis-insight_default                                     bridge    local
ad1686916ed0   runner_default                                            bridge    local
72d131ef9461   novaext-data-availability-runnergro-rustct_default        bridge    local
69e9038f7cbe   novaext-data-availability-runnergro-mgolang-new_default   bridge    local
f06719849d74   novaext-data-availability-webassets-mgolang-new_default   bridge    local

可以看到比如 a9e07fa9b259 clickhouse_default 用的都是 172.22.0.1 这种看上去很合理的 IP,但是 f06719849d74 novaext-data-availability-webassets-mgolang-new_default 不知道为啥就开始用到了 192.168.192.1 这种 IP,正好和 EKS 内的段有重合,导致了上面的问题。

我们从 Networking in Compose 中结合实际经验可以知道:

By default Compose sets up a single network for your app. Each container for a service joins the default network and is both reachable by other containers on that network, and discoverable by them at a hostname identical to the container name.

查阅 docker network create 我们可以猜测 docker-compose 底层还是调用了 Docker 原生的的接口来创建网络,并且网段是由 Docker Engine 提供的:

When you create a network, Engine creates a non-overlapping subnetwork for the network by default

When starting the docker daemon, the daemon will check local network (RFC1918) ranges, and tries to detect if the range is not in use. The first range that is not in use, will be used as a the default “pool” to create networks.

https://github.com/moby/moby/issues/37823

至于这里 Engine 具体代码是怎么实现的, 我翻了好久的代码愣是没翻到,这里留个坑,之后找到了填上。

所以问题就很清晰了,这里的问题 Docker 启动的时候没有发现 EKS 也在用 192.168.0.0/16 段,于是认为这个段是可用的,然后生成 Bridged 的网络便和 EKS 网络段部分重叠了,导致 EKS 内的 Pod 无法连接到 EC2 上,至于为什么别的机器没问题,我也登录上去看了一下,发现只是正好没有开到 EKS 的段上而已。

Solution

对于这种问题有两种解决方法,第一种是通过修改 Docker 的 daemon.json,加入类似以下的内容,让 Bridged 的网络开到指定的网段下:

{
  "bip": "192.168.1.5/24", 
  "fixed-cidr": "192.168.1.5/25", 
  "default-address-pools":[
      { "base":"192.168.2.5/24", "size":28 }
  ]
}

这种方法需要修改之后重启整个 Docker,会导致服务下线一定的时间(尤其是如果容器比较多的话)。

另一个方法是在 docker-compose.yml 文件下方指定一下默认 Bridged 的网络的段,写法类似如下:

networks:
  default:
    driver: bridge
    ipam:
     config:
       - subnet: 10.7.0.0/16
         gateway: 10.7.0.1

修改完之后 docker-compose down && docker-compose up -d 重启一下对应的服务即可。

解决了上面的问题之后, EKS 到 EC2 之间的通信立马就恢复正常了。

Reference

  1. Networking in Compose
  2. How to change the default docker subnet IP range
  3. Configuring Docker to not use the 172.17.0.0 range
  4. Docker (compose?) suddenly creates bridge using 192.168.16.0/20 range #37823

#Chinese