其實 Windows Containers 都是以 Windows Server 為基礎的 base image 來運行的,但從 2021 年 9 月微軟發佈了 Updates to the Windows Container Runtime support 文章,預告了未來將不再使用 DockerMsftProvider API 來安裝 Docker EE (企業版) 軟體,之後預計會全部移轉到開源的 containerd、Moby 或即將轉付費方案的 Mirantis Container Runtime 等等。這幾天我就想說來研究一下如何用 containerd 來執行 Windows Container,結果根本就是在玩踩地雷,我研究了足足三天才搞定完整可成功運行 Windows Containers 的 containerd 容器環境!這篇文章我將分享我在 Windows Server 2022 實作的完整過程。
準備 Windows Server 2022 虛擬機器
如果你用 Hyper-V 執行 Windows Server 2022 虛擬機器,請務必記得要啟用 VM 的 Nested Virtualization 功能:
-
先安裝好 Windows Server 2022 作業系統到 VM 中,並且先關機
-
回到 Host 主機執行以下命令:
Set-VMProcessor -VMName WS2022 -ExposeVirtualizationExtensions $true
假設 VM 的名稱為 WS2022
安裝 containerd 步驟
-
安裝 Windows Server 2022 的 Containers 相關功能 (可能需要重開機)
在 VM 內透過 PowerShell 啟用以下功能:
Add-WindowsFeature Containers,Hyper-V,Hyper-V-Tools,Hyper-V-PowerShell -Restart -IncludeManagementTools
-
安裝 containerd 1.6.1
請以系統管理員身份啟動 PowerShell 並執行以下命令:
# 下載 containerd 1.6.1
curl.exe -LO https://github.com/containerd/containerd/releases/download/v1.6.1/containerd-1.6.1-windows-amd64.tar.gz
# 解壓縮 containerd 到 C:\Program Files\containerd 系統資料夾中
tar xvf containerd-1.6.1-windows-amd64.tar.gz
mkdir -force "C:\Program Files\containerd"
Move-Item ./bin/* "C:\Program Files\containerd"
Remove-Item bin
# 建立 containerd 設定檔
. "C:\Program Files\containerd\containerd.exe" config default | Out-File "C:\Program Files\containerd\config.toml" -Encoding ascii
# 設定 Windows Defender 關閉對 `containerd.exe` 的掃毒檢查
Add-MpPreference -ExclusionProcess "$Env:ProgramFiles\containerd\containerd.exe"
# 註冊為 Windows 服務
. "$Env:ProgramFiles\containerd\containerd.exe" --register-service
# 啟動服務
Start-Service containerd
此時你應該設定 PATH 環境變數,讓之後比較方便執行相關命令:
$env:PATH = "C:\Program Files\containerd;" + $env:PATH
-
設定容器網路
因為 containerd 不像 Docker 那樣會直接綁定現有的網路設定,它是透過 CNI (container networking interface) plugin 來設定網路。如果你要在本機架設一個容器開發環境,你會需要用到支援 Windows 的 nat
plugin 即可!如果要用在 Kubernetes 的話,可不能用這個,你可能要改用 Calico, Flannel, Antrea, ... 之類的 CNI Plugins!
先建立兩個必要的資料夾:
mkdir -force "C:\Program Files\containerd\cni\bin"
mkdir -force "C:\Program Files\containerd\cni\conf"
從 microsoft/windows-container-networking 下載 windows-container-networking-cni-amd64-v0.2.0.zip
檔案
curl.exe -LO https://github.com/microsoft/windows-container-networking/releases/download/v0.2.0/windows-container-networking-cni-amd64-v0.2.0.zip
Expand-Archive windows-container-networking-cni-amd64-v0.2.0.zip -DestinationPath "C:\Program Files\containerd\cni\bin" -Force
Remove-Item windows-container-networking-cni-amd64-v0.2.0.zip
注意: 微軟的 microsoft/windows-container-networking 年久失修,在 GitHub 的 Releases 頁面中所提供的 Binary 檔案是相當過時的版本,根本無法在 Windows Server 2022 中正常使用。你必須自行抓取該 Repo 的原始碼回來,並使用 go build
自行建置該專案,取得新的 nat.exe
並替換掉 C:\Program Files\containerd\cni\bin\nat.exe
檔案,之後的步驟才能正確實做成功!🔥 (我有建立一個 The v0.2.0 Alpha Release is outdated #65 追蹤此問題,希望近期會改善。)
建立一個 nat
網路 (注意: HNS 網路名稱必須命名為 nat
才行喔)
curl.exe -LO https://raw.githubusercontent.com/microsoft/SDN/master/Kubernetes/windows/hns.psm1
Import-Module ./hns.psm1
$subnet="10.0.0.0/16"
$gateway="10.0.0.1"
New-HNSNetwork -Type NAT -AddressPrefix $subnet -Gateway $gateway -Name "nat"
建立 containerd 網路設定,使用相同的 Gateway 與 Subnet 設定
@"
{
"cniVersion": "0.2.0",
"name": "nat",
"type": "nat",
"master": "Ethernet",
"ipam": {
"subnet": "$subnet",
"routes": [
{
"gateway": "$gateway"
}
]
},
"capabilities": {
"portMappings": true,
"dns": true
}
}
"@ | Set-Content "C:\Program Files\containerd\cni\conf\0-containerd-nat.conf" -Force
-
執行容器
你有兩種方法可以跟 containerd
互動:
ctr
(在本機執行容器主要用這個)
crictl
(通常 Kubernetes 會用這個)
透過 ctr
執行 containerd 中的容器
-
先查詢 Windows 核心版號
cmd /c ver
我的 Windows Server 2022 作業系統核心版本號為 10.0.20348.587
Microsoft Windows [Version 10.0.20348.587]
-
拉取 mcr.microsoft.com/windows/nanoserver:ltsc2022
image 並執行一個名為 test
的容器
ctr.exe image pull mcr.microsoft.com/windows/nanoserver:ltsc2022
ctr.exe image pull registry.hub.docker.com/library/hello-world:nanoserver-ltsc2022
-
執行容器
單純的用 cmd
輸出一段 Hello World
文字:
# 將容器的 ID 設定為 hello
ctr.exe run mcr.microsoft.com/windows/nanoserver:ltsc2022 hello cmd /c echo Hello World
# 刪除容器
ctr container rm hello
執行 nanoserver-ltsc2022
的 hello-world 容器:
# 將容器的 ID 設定為 hello-world 並透過 --rm 設定執行完畢後直接刪除容器
ctr.exe run --rm registry.hub.docker.com/library/hello-world:nanoserver-ltsc2022 hello-world
執行 curl.exe
取得 https://ifconfig.co/ 網站的內容(取得目前電腦的對外 IP 地址):
# 將容器的 ID 設定為 test 並透過 --cni 指定使用 CNI 給予一個網路環境
ctr run --cni --rm mcr.microsoft.com/windows/nanoserver:ltsc2022 test curl.exe -s https://ifconfig.co/
透過 crictl
執行 containerd 中的容器
通常 Kubernetes 會用這個。
-
安裝 crictl
工具
curl.exe -LO https://github.com/kubernetes-sigs/cri-tools/releases/download/v1.23.0/crictl-v1.23.0-windows-amd64.tar.gz
tar xvf crictl-v1.23.0-windows-amd64.tar.gz
mv crictl.exe "C:\Program Files\containerd"
-
設定 crictl
的預設設定
mkdir -Force "$home\.crictl"
@"
runtime-endpoint: npipe://./pipe/containerd-containerd
image-endpoint: npipe://./pipe/containerd-containerd
timeout: 10
#debug: true
"@ | Set-Content "$home\.crictl\crictl.yaml" -Force
crictl.exe info
-
拉取支援 Windows Container 的 hello-world image 回來
crictl pull hello-world:nanoserver-ltsc2022
-
建立 Pod 沙盒環境
@"
{
"metadata": {
"name": "hello-world-sandbox",
"namespace": "default",
"attempt": 1,
"uid": "hdishd83djaidwnduwk28bcsb"
},
"log_directory": "/tmp"
}
"@ | Set-Content "pod-config.json" -Force
$POD_ID=(crictl runp .\pod-config.json)
上面這段基本上會執行失敗,你會得到以下錯誤訊息:
time="2022-03-23T01:19:40+08:00" level=fatal msg="run pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "035006a1d4b4fd6ab8244f4fef527fc5117b6b37188b61e321f37538f8f01b78": plugin type="nat" name="nat" failed (add): error creating endpoint hcnCreateEndpoint failed in Win32: IP address is either invalid or not part of any configured subnet(s). (0x803b001e) {"Success":false,"Error":"IP address is either invalid or not part of any configured subnet(s). ","ErrorCode":2151350302} : endpoint config &{ 035006a1d4b4fd6ab8244f4fef527fc5117b6b37188b61e321f37538f8f01b78_nat 94530bee-c40a-4f25-9caf-19bbe91e748f [] [{ 0}] { [] [] []} [{10.0.0.1 0.0.0.0/0 0}] 0 {2 0}}"
這是因為從 microsoft/windows-container-networking Repo 下載回來的 windows-container-networking-cni-amd64-v0.2.0.zip
從 2019 年之後就再也沒有更新過,但是原始碼其實是有更新的。若要正確使用 nat
plugin 就需要抓取目前的 Go 原始碼回來重新建置,並手動更新 C:\Program Files\containerd\cni\bin\nat.exe
執行檔,更新之後就可以順利建立 Pod 了!
另外,上述 pod-config.json
設定檔中的 log_directory
設定指向 /tmp
目錄,你必須先在 Windows 建立 C:\tmp
資料夾,否則最後容器依然是無法啟動的!
-
建立容器
@"
{
"metadata": {
"name": "hello-world:nanoserver-ltsc2022"
},
"image":{
"image": "hello-world:nanoserver-ltsc2022"
},
"log_path":"hello-world.0.log"
}
"@ | Set-Content "container-config.json" -Force
$CONTAINER_ID=(crictl create $POD_ID .\container-config.json .\pod-config.json)
-
啟動容器
crictl start $CONTAINER_ID
-
查看容器中的紀錄
crictl logs $CONTAINER_ID
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(windows-amd64, nanoserver-ltsc2022)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
To try something more ambitious, you can run a Windows Server container with:
PS C:\> docker run -it mcr.microsoft.com/windows/servercore:ltsc2022 powershell
Share images, automate workflows, and more with a free Docker ID:
https://hub.docker.com/
For more examples and ideas, visit:
https://docs.docker.com/get-started/
-
查看 Pod 與 Container 清單
crictl pods
crictl ps -a
-
刪除 Container 與 Pod
crictl rm $CONTAINER_ID
crictl stopp $POD_ID
crictl rmp $POD_ID
相關連結
光是這篇文章我看了以下這麼多資訊,實在有點累人,這資訊量有點大啊 😒