Windows Server 2004之docker-compose啟動多組NAT網路後HNS服務CPU飆高且容器無法正常啟動

Windows Server 2004之docker-compose啟動多組NAT網路後HNS服務CPU飆高且容器無法正常啟動

近期,我們有意將 Windows Container 升級至最新半年通道 Windows Server 2004,從 1709、1803、1809、1903、1909 一路被虐過來,總覺得會越走越輕鬆,結果沒有。前面,在解決了 VMTools 造成容器服務會被中斷的問題之後。馬上又碰到第二個問題。

問題說明

如果先啟動第一個 docker-compose 的服務,之後又啟第二個 docker-compose 服務,有很大的機會 docker-compose 在啟動過程會被卡住,然後整個就 docker-compose 卡死在那裡(最多有成功至第4個才卡住)。一開始覺得是 docker-compose 的問題,但由於是新主機,習慣性都會去 docker-compose 去下載最新版來使用。也有找了一下 compose 的 Issues,沒有看到類似的問題。從 Get-EventLog 去查詢,因為程式是那種卡住,並不是被中斷或 Crash,也沒有什麼日誌可以參考。

問題重現

這部分可以說花了我好多的時間,以下是最簡單問題重現方式。我們準備多組 docker-compose.yml。為了簡化問題,我們只使用官方映像檔,不去使用有額外封裝過的映像檔。

version: '3.8'
services:
  iis8080:
    image: "mcr.microsoft.com/windows/servercore/iis:windowsservercore-2004"
    ports:
      - 8080:80
    restart: on-failure
version: '3.8'
services:
  iis8081:
    image: "mcr.microsoft.com/windows/servercore/iis:windowsservercore-2004"
    ports:
      - 8081:80
    restart: on-failure
version: '3.8'
services:
  aspnet9090:
    image: "mcr.microsoft.com/dotnet/framework/aspnet:4.8-windowsservercore-2004"
    ports:
      - 9090:80
    restart: on-failure

執行 docker-compose.exe updocker-compose.exe up -d 都行。

在反覆測試的過程,看到一個不正常的現象。如下圖,在第4次(第三個執行個體)啟動時,發現建立容器但無法啟動的現象:

HNS CPU High

這個 HNS 服務的 CPU 使用率不斷被拉高。Windows Host Network Service (HNS,主機網路服務),簡單來說,我們 Docker 建立的網路與端點,最終還是要跟實體主機上進行對應,而這個虛實之間的對應工作就是 HNS 服務的內容。

無解

說真的,由於我們是採用最新的 Windows Server 2004 (OS Build 19041.450) 與 Windows Container 的組合,找遍了網路沒有看到任何案例。而 Windows Server 2004 剛推出時,有著不少 Issues 的風評,一項一項看著,也沒看到類似的 Issue 被回報。想說不會那麼賽吧,沒人回報的 Server 級 bug 也能讓我碰到。

NAT

將自己沉澱下來,細想著整個 Windows Server 與 Windows Container 運作流程,突然回想一篇參與過的 Github Issue 3076 討論串。Windows Server 官方在 Windows Server 2019 做了一個重大的改變,它們改變了 NAT 網路的行為。文件只簡單寫了一句:重新開機之後,在 Windows Server 2019 (或更新版本上建立的 NAT 網路將不再保存) 。

不要小看這句話,影響不小。沒它我還解不出來。

解法:One NAT

很明顯,Windows Server 對於這種多組 NAT 網路有一定的考量,重開機就讓它消失不見。事情不單純。不過我沒空查它。我要先解決的我的 Windows Container 啟動卡住的問題。

既然 Windows Server 2019 之後不希望這種"多組 NAT 網路"的出現,那麼是否讓所有容器都跑在預設 NAT 網路之下就好呢?

PS C:\> docker network ls
NETWORK ID          NAME                DRIVER              SCOPE
ed8f896b69b0        nat                 nat                 local
cee682c39629        none                null                local

要讓 docker-compose 以預設的 nat 網路來啟動,也很簡單,參考文件加入以下組態到 docker-compose.yml:

networks:
 default:
  external:
   name: "nat"

Yes,順利啟動多組 compose 的容器服務,一切又回到美麗的世界。一般而言,到這裡就可以結束了。

Redis HA

不過,那個"我"不要高興的太早!

Redis HA for Windows 介紹過,我為了讓 Redis HA 機制能正常在 Windows Container 運作,所以在 redis.config 的組態檔裡預先設計好網路架構。原本的想法是,我們依照 Docker Host 的 NAT 預設值重新修改組態與建置新映像檔。結果查了其他台 Docker Host 的 NAT 設定:

"Config": [
    {
        "Subnet": "172.29.16.0/20",
        "Gateway": "172.29.16.1"
    }
]

"Config": [
    {
        "Subnet": "172.31.112.0/20",
        "Gateway": "172.31.112.1"
    }
]

"Config": [
    {
        "Subnet": "172.25.224.0/20",
        "Gateway": "172.25.224.1"
    }
]

簡單說,每一台 Docker Host 的 NAT 的網段都不一樣。這樣根本就不能製作出通用的映像檔。怎麼辦呢?

統一 NAT 網段

山不轉,路再轉一次。那有無可能我們統一所有 Docker Host 的 NAT 網段,而且是我們想要的網段。查詢後,Yes,太棒了,兩者只要一行組態就能一次滿足。

"fixed-cidr": "10.10.0.0/20"

只要在 daemon.json 加入我們想要的網段組態。這裡要注意,Restart-Service docker 並不會讓組態生效,要進行重開機(Restart-Computer -Force)才會生效。

Windows Container 能設的組態與 Linux Container 不一樣,細節請查詢 daemon.json for Windows 文件。

PS C:\> docker network inspect nat
[
    {
        "Name": "nat",
        "Id": "ed8f896b69b0c029b8512ef20c27238001b7bc198d1f9a2374e98a267f70dd8c",
        "Created": "2020-09-10T17:51:42.2092873+08:00",
        "Scope": "local",
        "Driver": "nat",
        "EnableIPv6": false,
        "IPAM": {
            "Driver": "windows",
            "Options": null,
            "Config": [
                {
                    "Subnet": "10.10.0.0/20",
                    "Gateway": "10.10.0.1"
                }
            ]
        },
        "Internal": false,
        "Attachable": false,
        "Ingress": false,
        "ConfigFrom": {
            "Network": ""
        },
        "ConfigOnly": false,
        "Containers": {
            "2231f2c8ca7c166ea3374f0f2a6f7b1d626540fd26a0616b164454f6f67e8849": {
                "Name": "iis8080_iis8080_1",
                "EndpointID": "a269731591ebc257a46c017a790fd3f0a7d1b993749b621f42205d64e41a886d",
                "MacAddress": "00:15:5d:b9:08:db",
                "IPv4Address": "10.10.9.66/8",
                "IPv6Address": ""
            },
            "2eeeaa0d9586133dfb3c00d8599894cdce1e269e8af31c02630d04448ab4930b": {
                "Name": "aspnet9090_aspnet9090_1",
                "EndpointID": "6adb00dcd33177a9689d9f4797de26771c875b3af5009cc4c5b737bac8998313",
                "MacAddress": "00:15:5d:b9:00:8f",
                "IPv4Address": "10.10.4.219/8",
                "IPv6Address": ""
            },
            "355df4c40180148b8211e2b2c3f2c06919681ce415e0ddd073c1606b02df6e2c": {
                "Name": "redisha_master_1",
                "EndpointID": "42a2ba34914e594512ad0b8eff130572dd3f9b845f3255e214fbc77417495dd6",
                "MacAddress": "00:15:5d:b9:04:06",
                "IPv4Address": "10.10.10.10/20",
                "IPv6Address": ""
            },
            "918c28a67cca7071758adb616401dfc52d3edffd693bdc154640c4ac7a7a091d": {
                "Name": "redisha_sentinel_1",
                "EndpointID": "8a5690315a1b67cc0f890e93260f9e3d4f8b134c5ae84fc19024440fb79cf9f9",
                "MacAddress": "00:15:5d:b9:03:13",
                "IPv4Address": "10.10.4.169/8",
                "IPv6Address": ""
            },
            "9d7910bf75cd5910e05055cc702477eeef2c52f42ddb39e742bd2d3c8ef09ae0": {
                "Name": "nginx_nginx_1",
                "EndpointID": "58eebbcac637b18a37c7767448c26dc6273df1ac67ed0fd2b5fb572e37f47f85",
                "MacAddress": "00:15:5d:b9:06:31",
                "IPv4Address": "10.10.3.222/8",
                "IPv6Address": ""
            },
            "f7bb57ce581abc97ed96537dd45603774a6fbadb1dc9cb0957d874e71f1fd956": {
                "Name": "redisha_slave_1",
                "EndpointID": "d41eb4f813a0331e184c3e8f12d4b52f235fe44fdf13ee7537840952561e859d",
                "MacAddress": "00:15:5d:b9:00:34",
                "IPv4Address": "10.10.8.210/8",
                "IPv6Address": ""
            }
        },
        "Options": {
            "com.docker.network.windowsshim.hnsid": "40703147-2B99-4CFD-BA32-5F060F66EFAD",
            "com.docker.network.windowsshim.networkname": "nat"
        },
        "Labels": {}
    }
]

預設 Redis HA 的 docker-compose.yml 只改吃預設 NAT 網路,其他完全不變。我們可以看到 redisha_master_1 順利的拿到我們希望的 "10.10.10.10" 這組 IP 位置。

小結

要小心 One NAT 這件事,因為現在大家都在同一網段之內,所以容器互相是可見的,用 docker exec 進去隨便一個容器內 ping 一下其他的 Container Name ,就能驗證。也就是,原本的網路獨立這件事在 One NAT 架構下給取捨掉了。

這段山路有夠不好走。前前後後追了二週以上(中間有去忙其他事)。想放棄,又出現一絲絲希望,又卡關,想放棄,又出現一絲絲希望,又卡關,想放棄,又出現一絲絲希望,又卡關。但總算有個好結果,有走到山頭。

1 則留言:

感謝您的留言,如果我的文章你喜歡或對你有幫助,按個「讚」或「分享」它,我會很高興的。