The Will Will Web

記載著 Will 在網路世界的學習心得與技術分享

如何在 Windows Server 2022 使用 containerd 執行 Windows Containers

其實 Windows Containers 都是以 Windows Server 為基礎的 base image 來運行的,但從 2021 年 9 月微軟發佈了 Updates to the Windows Container Runtime support 文章,預告了未來將不再使用 DockerMsftProvider API 來安裝 Docker EE (企業版) 軟體,之後預計會全部移轉到開源的 containerdMoby 或即將轉付費方案的 Mirantis Container Runtime 等等。這幾天我就想說來研究一下如何用 containerd 來執行 Windows Container,結果根本就是在玩踩地雷,我研究了足足三天才搞定完整可成功運行 Windows Containers 的 containerd 容器環境!這篇文章我將分享我在 Windows Server 2022 實作的完整過程。

準備 Windows Server 2022 虛擬機器

如果你用 Hyper-V 執行 Windows Server 2022 虛擬機器,請務必記得要啟用 VM 的 Nested Virtualization 功能:

  1. 先安裝好 Windows Server 2022 作業系統到 VM 中,並且先關機

  2. 回到 Host 主機執行以下命令:

    Set-VMProcessor -VMName WS2022 -ExposeVirtualizationExtensions $true
    

    假設 VM 的名稱為 WS2022

安裝 containerd 步驟

  1. 安裝 Windows Server 2022 的 Containers 相關功能 (可能需要重開機)

    在 VM 內透過 PowerShell 啟用以下功能:

    Add-WindowsFeature Containers,Hyper-V,Hyper-V-Tools,Hyper-V-PowerShell -Restart -IncludeManagementTools
    
  2. 安裝 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
    
  3. 設定容器網路

    因為 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
    
  4. 執行容器

    你有兩種方法可以跟 containerd 互動:

    1. ctr (在本機執行容器主要用這個)
    2. crictl (通常 Kubernetes 會用這個)

透過 ctr 執行 containerd 中的容器

  1. 先查詢 Windows 核心版號

    cmd /c ver
    

    我的 Windows Server 2022 作業系統核心版本號為 10.0.20348.587

    Microsoft Windows [Version 10.0.20348.587]
    
  2. 拉取 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
    
  3. 執行容器

    單純的用 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-ltsc2022hello-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 會用這個。

  1. 安裝 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"
    
  2. 設定 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
    
  3. 拉取支援 Windows Container 的 hello-world image 回來

    crictl pull hello-world:nanoserver-ltsc2022
    
  4. 建立 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 資料夾,否則最後容器依然是無法啟動的!

  5. 建立容器

    @"
    {
      "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)
    
  6. 啟動容器

    crictl start $CONTAINER_ID
    
  7. 查看容器中的紀錄

    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/
    
  8. 查看 Pod 與 Container 清單

    crictl pods
    crictl ps -a
    
  9. 刪除 Container 與 Pod

    crictl rm $CONTAINER_ID
    crictl stopp $POD_ID
    crictl rmp $POD_ID
    

相關連結

光是這篇文章我看了以下這麼多資訊,實在有點累人,這資訊量有點大啊 😒

留言評論