Docker 網絡 IP 地址衝突了,我該怎麼辦呢?

1前置知識

因爲交換機的能力有限制,以及網線的連接不可能無限長, 所以我們不可能把所有的主機都連到同一個交換機上,然後處於同一個二層網絡中。

就算能,主機間的 ARP 廣播也會讓這個網絡瞬間癱瘓。

所以我們得把主機拆分到一個個的小的子網裏,然後通過路由器再併成三層網絡。

我們俗稱的 IP 地址其實由兩個部分組成,網絡地址和主機地址。比如 10.0.0.1/8 中,第一個 10 是網絡地址,後面的 0.0.1 是主機地址。那我怎麼知道怎麼去拆分這兩段呢?靠的就是子網掩碼,也就是那個 /8。

IP 其實是由 32 位的二進制組成的,x.x.x.x 只是爲了方便人類閱讀將其轉成了十進制。有個簡單粗暴的辦法就是可以認爲每一段都是 8 位,所以 /8 就代表第一段是網絡地址。

同理,/16 就代表前兩段都是網絡地址,10.0.0.0/16 中, 網絡地址是 10.0,後兩位 0.0 代表每個子網中的主機地址。

比如 10.0.0.1/16 和 10.0.0.2/16 是同一個子網的兩個主機。10.0.0.1/16 和 10.1.0.1/16 是不同的子網的兩個主機。

這裏只是簡單粗暴的介紹下,更多的信息還是自己去查資料瞭解學習。

2問題表現

docker 進程無法啓動
容器端口無法訪問,抓包顯示爲有入站但是沒有出站

3排查

此處針對的是 dockerd 無法啓動的情況,如果 dockerd 能啓動,可以直接跳到解決一節

首先是看下日誌

$ systemctl status docker
$ journalctl -u docker
$ dmesg | grep docker

一般能看到如下日誌:

docker0: link is not ready
docker_gwbridge: link is not ready

或者更簡單的排查方法,直接手動啓動 dockerd 看看。啓動方法可以通過 grep ExecStart /usr/lib/systemd/system/docker.service 查看。

一般來說按如下執行就行:

$ /usr/bin/dockerd --debug
然後能看到最後輸出:

INFO[2021-07-29T02:25:55.811673622Z] stopping event stream following graceful shutdown  error="<nil>" module=libcontainerd namespace=moby
failed to start daemon: Error initializing network controller: list bridge addresses failed: PredefinedLocalScopeDefaultNetworks List: [10.252.0.0/24 10.252.1.0/24 10.252.2.0/24]: no available network

這時候可以看下 ip addr,是否有 docker0 和 docker_gwbridge,

如果發現沒有 docker0,那基本可以肯定是 docker0 無法創建導致 dockerd 啓動失敗了。

4解決

找到佔用的網段

默認的 docker0 網段是 172.17.0.0/16,docker_gwbridge 網段是 172.18.0.0/24,你需要確認下這兩個網段是否被佔用了。

最簡單的方法就是 ping 一下,如果無響應的話,那麼就說明沒有被佔用。

其次就是看一下本機的路由表 route -n,確認一下有沒有衝突的段。一般來說,你會發現相關網段要麼已經被佔用,要麼是路由表裏存在衝突。

案例
比如在我的環境裏,我給 docker0 配置爲 10.252.0/24,然後 dockerd 起不來。

排查後發現 route -n 裏有這麼一條:

10.0.0.0        172.21.0.1      255.0.0.0       UG    0      0        0 eth0

也就是說 10/8 被佔用了,導致和我的 10.252.0/24 衝突。後來聯繫網管刪除了這條 10/8 的路由後解決。

修改 docker 佔用的網段
以下操作需要先停止 docker 進程

$ systemctl stop docker

如果你用 docker service,那麼 docker 會佔用四個網段:

docker0
docker_gwbridge
ucp(docker engine 佔用,不常見)
ingress

其中前三個的網段可以通過配置文件配置,第四個只能手動創建。

前三個網段會讀取 /etc/docker/daemon.json 這個配置文件, 這個文件默認是沒有的,需要手動創建。

{
    "bip": "",
    "default-address-pools": [
        {"base": "10.252.0.0/24", "size": 24},
        {"base": "10.252.1.0/24", "size": 24},
        {"base": "10.252.2.0/24", "size": 24}
    ]
}

注意這個 default-address-pools 至少要有兩項,按順序依次爲:

docker0
docker_gwbridge
ucp
以防萬一的話,配置三個是最好的😄。

接下來是修改 ingress,這個比較麻煩,需要手動創建。

我的辦法是在初始化 docker swarm 以後,啓動 docker stack/service 以前,執行以下腳本:

刪除 ingress
刪除 my-ingress(不一定存在,不存在就忽略)
新建 my-ingress

$ yes 'y' | docker network rm ingress
$ yes 'y' | docker network rm my-ingress 2>&1 | true
$ docker network create \
  --driver overlay \
  --ingress \
  --subnet=10.252.3.0/24 \  # 指定 ingress 的網段,不要和上面 daemon.json 的網段衝突
  --gateway=10.252.3.2 \
  --opt com.docker.network.driver.mtu=1200 \
  my-ingress

ingress 是默認名,之所以新建一個名字不一樣的,是因爲刪除 ingress 是異步的。如果你刪除 ingress 後立刻新建一個也叫 ingress 的網絡,很可能會報一個重名的錯誤。

重啓 docker 前,最好先清理一下 docker 的緩存:

$ ip link del dev docker0
$ ip link del dev docker_gwbridge
$ rm -rf /var/lib/docker/network

另一種粗糙簡單的解決辦法
另一種粗糙簡單的解決辦法就是乾脆直接手動創建一個 docker0。

這種解決方法最簡單,但是機器 reboot 後 docker0 會自動被刪掉, 所以這方法並不持久。

而且,實際上也不一定能解決網段衝突的問題,只是說 docker 能啓動了而已。

ip link add name docker0 type bridge
ip addr add dev docker0 10.252.0.1/24

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章