The Will Will Web

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

如何修復 Docker Desktop for Windows 各種奇怪的網路問題

Windows Container 的網路有時候會遇到一些奇怪的現象,尤其是在我這台筆電,因為實在安裝過太多版本,作業系統也升級好幾次,有時候為了演講也要調整本機網路設定,所以設定很亂也應該是正常的。這幾天我就發現升級到 Windows 10 版本 1903 之後,網路功能出了一點狀況,這次撰文的過程也徹底將 docker networking 做了深入的研究與理解,收穫頗豐!

執行環境

  • Microsoft Windows 10 [版本 10.0.18362.116]
  • Docker Desktop Community Version 2.0.0.3 (31259) (Channel: Stable)

問題描述

我的筆電自從升級到 Windows 10 (版本 1903) 之後,Docker 的網路設定變得有點奇怪,狀況如下:

  1. 執行 Process 隔離模式時,網路一切正常,可以透過本地迴路進行連線 (這是 Windows 10 1809 之後才有的功能)

    docker run -d --rm -p 80:80 --isolation process mcr.microsoft.com/windows/servercore/iis:windowsservercore-1903
    
  2. 執行 Hyper-V 隔離模式時,容器網路是正常的,但是卻無法透過本地迴路進行連線!

    docker run -d --rm -p 80:80 --isolation hyperv mcr.microsoft.com/windows/servercore/iis:windowsservercore-1903
    

    容器可以正常啟動,雖然我加上了 -p 80:80 參數,但是透過 http://localhost 是無法連上的,很明顯的 nat 網路設定出了問題。

解決過程

我的筆電距離上次重灌已經過了一段時間,歷經了幾代的 Docker Desktop 版本,又歷經了兩次 Windows 10 版本升級,設定混亂也是正常的事。照理說這個問題,很簡單:「只要重新安裝作業系統就能解決」。這個解決方案我當然知道,但是重灌電腦曠日廢時;另一方面來說,如果我選擇了重灌,就真的無法了解技術原理了。因此,我選擇跟他奮戰一個下午外加一個晚上,最後終於解決問題,過程中也學習到不少 Docker Networking 與 Hyper-V 的網路知識。

由於這個問題非常詭異,容器網路其實並沒有問題,只有本地迴路(loopback)連不上而已。況且,在 Process isolation 模式下,本地迴路的網路也是正常的,只有在 Hyper-V 隔離模式下才出現問題。這種鬼打牆的情況之前沒遇過,所以完全不知道從哪裡著手修復,只好亂槍打鳥,只能傻傻的嘗試各種可能。

  1. Restart Docker Desktop (重新啟動)

    無效!

  2. Reset to factory defaults (回復出廠設定)

    無效!

  3. Reinstall Docker Desktop (重新安裝)

    How to completely remove Docker in Windows 10

    Docker Desktop for Windows - Docker Hub

    無效!

  4. 完整移除並重新安裝 Hyper-V

    Disable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V-All,Containers
    Restart-Computer
    Enable-WindowsOptionalFeature -Online -FeatureName Microsoft-Hyper-V,Containers -All
    Restart-Computer
    

    無效!

    我甚至於用了 WindowsContainerNetworking-LoggingAndCleanupAide.ps1 指令檔來清除網路狀態,不過一樣無效

  5. 比對兩台不同電腦的 Docker 設定

    我直接到 Azure 建立一台 D2S_v3 的虛擬機器 (支援 Nested Virtualization 功能),並將 Docker 安裝好,我發現新電腦會自動在 Hyper-V 的 VSwitch 建立一組名為 nat 的虛擬交換器。如果有切換到 Linux container 的話,則會自動建立起一個名為 DockerNAT 的虛擬交換器。

    接著我便手動在我的 Hyper-V 中手動新增一個 Internal 的 VSwitch,並且設定名稱為 nat,結果還是完全沒有效果,本地迴路還是無法連線!

  6. 認真看完、看懂 Windows 容器網路功能 的所有重要知識

    之前我只有看完這份文件,但卻沒有真正理解全部。這次遇到問題重看一遍,發現之前不太理解的地方突然懂了,因此也看的比較深入些。

    Windows 支援可透過 Docker 來建立的五種不同網路驅動程式或模式:natoverlaytransparentl2bridgel2tunnel

    底下這當圖片對我幫助很大,因為無論是 Windows Server ContainerHyper-V Container,每個容器都會使用一張虛擬網卡 (vNIC) 取得連線,而且該 vNIC 會連接到 Hyper-V 的虛擬交換器 (vSwitch) 來進行連線。這些虛擬網卡會由 Docker 自動建立與管理,照理來說你都不需要自己建立才對。

    image

    Docker 引擎第一次執行時,會建立預設 NAT 網路 nat,它會使用內部 vSwitch 和名為 WinNAT 的 Windows 元件。你可以透過 docker network ls 發現 nat 網路的蹤跡。而 nat 網路是在 Windows 上執行的容器的預設網路。任何容器如果在 Windows 上執行,但沒有任何旗標或引數實作特定網路組態,其將會連接到預設的 nat 網路,且系統會自動從 nat 網路的內部首碼 IP 範圍指派 IP 位址給容器。 用於 nat 的預設內部 IP 首碼為 172.16.0.0/16

    我這次的問題其實就出在 nat 網路,因為感覺上 Docker 並沒有幫我建立這個 vSwitch,下圖是我從 Azure VM 取得的截圖,這是一台全新的 Windows 10 安裝,環境最乾淨:

    image

    但是在我的筆電上,執行 docker network ls 會看見 nat 網路,但是從 Get-VMSwitch 就是看不到,非常詭異!

    但因為這次問題,讓我徹底的認識了 主機網路服務 (HNS),原來這是 Windows Container 一個相當重要的服務,用來自動對 Hyper-V 建立虛擬交換器 (vSwitch),還有建立所需的 NAT 及 IP 集區!

    我從完全乾淨的 VM 中,透過 hnsdiag list networks 命令,可以找出所有透過 HNS 管理的網路:

    image

    從我的筆電上,也可以透過 hnsdiag list networks 命令看見一樣的 nat 網路,但是我透過 Get-VMSwitch 就是看不到。也就是說,HNS 幫我建立了 nat 網路,但是 Hyper-V 卻沒有出現相對應的 nat 虛擬交換器,這真的太怪了!

    我後來在我的筆電上,用 hnsdiag list networks 3705E6F2-9EEE-4C14-BF9B-721EE0A11548 -dl 命令,列出 HNS 所管理的 nat 網路的完整資訊,找出了 SwitchId 屬性,然後再透過 Get-VMSwitch | select Id,Name,SwitchType 命令取得所有 vSwitch 的 Id 欄位,這才發現原來 HNS 竟然自動幫我選了 Default Switch 作為 nat 網路的預設 vSwitch,為什麼會這樣呢?(抓頭)

    備註: Default Switch 預設會由 Hyper-V 服務進行管理,如果找不到也會自動被建立,此虛擬交換器無法刪除,若要刪除 Default Switch 虛擬交換器,則必須移除 Hyper-V 才行。

  7. 嘗試移除用不到的虛擬網路介面 (vNIC)

    由於無法理解為何 HNS 不會自動建立名為 nat 的虛擬交換器,反而改用 Default Switch 來進行連線。

    此時我開始往 vNIC 著手研究,從 裝置管理員 (devmgmt.msc) 中發現有好多張 Hyper-V Virtual Ethernet Adapter 虛擬網卡,我從 控制台網路連線 也可以看到一堆不明用途的 Hyper-V 網路介面。其實這個問題我之前就有發現,只是無法理解為什麼會有這麼多虛擬網路介面。這次查看後發現,有一張網路介面的命名很詭異,叫做【Hyper-V Broken Virtual Ethernet Adapter】,另外還有兩個【Hyper-V Virtual Ethernet Adapter (nat)】與【Hyper-V Virtual Ethernet Adapter (nat 2)】的網路介面。

    我直接將看不懂的 vNIC 全部移除,事實上我也有嘗試過把所有 Hyper-V Virtual Ethernet Adapter 虛擬網卡全數移除,反正 Hyper-V 會自動幫我重新建立。所以我連包含 Default Switch 字樣的 vNIC 也移除。結果,問題就修好了!因為當 HNS 看不到名為 nat 的 vSwitch,又找不到 Default Switch 的預設虛擬交換器,它就會自動幫我建立一組 nat 虛擬交換器(vSwitch) 與 vEthernet (nat) 虛擬網路介面(vNIC)。

    不過,重開機之後,問題依舊存在,真的快崩潰!我從 Windows container networking 文件得知,原來 NAT 網路從 Windows Server 2019 (1809) 開始,每次重開機都會重建新的。

    image

    現在我的電腦,每次重開機,都要執行以下命令,先把 Docker 服務停止,然後將所有透過 HNS 管理的網路移除,然後重新啟動 docker 服務,停三秒之後,再執行 Get-VMSwitch | Select Id,Name,SwitchType 命令,這個命令會讓 Hyper-V 自動建立缺少的 Default Switch 虛擬交換器。這個執行的過程順序不能有錯,一定要先刪除所有 HNS 管理的網路,然後重啟 docker 服務,此時 Docker 才能透過 HNS 建立 nat 虛擬交換器,之後 Hyper-V 隔離模式下的 NAT 網路才會正常!

    Stop-Service docker
    Get-Service docker
    Get-HnsNetwork | Remove-HnsNetwork
    Start-Service docker
    Start-sleep 3
    Get-Service docker
    Get-VMSwitch | Select Id,Name,SwitchType
    

    你可以透過以下命令查詢網路設定值:

    docker network ls
    hnsdiag list networks -dl
    Get-VMSwitch | Select Id,Name,SwitchType
    

    不過,這不叫 解決方案 (Solution),這叫 應變措施 (Workaround) 而已,但礙於網路對於 HNS 的資源太少,根本找不到任何蛛絲馬跡!

  8. 嘗試建立獨立的 vSwitch、vNIC 與 docker network

    New-VMSwitch -Name nat2 -SwitchType Internal
    docker network create -d nat -o com.docker.network.windowsshim.interface='vEthernet (nat2)' nat2
    docker run -d --rm -p 80:80 --network nat2 --isolation hyperv mcr.microsoft.com/windows/servercore/iis:windowsservercore-1903
    

    如果是用這種方式建立網路,所有問題都可以被解決,唯一的麻煩之處就是每次執行 docker run 都要額外加上 --network nat2 選項,有點不方便!而且我也查了一下,看能不能將 docker run 都加入預設參數 --network nat2 選項,結果是沒辦法! Orz

  9. 全新的 Docker 啟動命令

    由於我的筆電原本就設定 Docker 不會隨作業系統自動啟動服務,因此我大可另外寫一個 PowerShell 指令檔,用我自定的命令來啟動 Docker 服務,如此一來便可漂亮的解決我的問題。

    Write-Host '移除所有 HNS 網路'
    Get-HnsNetwork | Remove-HnsNetwork
    
    Write-Host '啟動 Docker 伺服器'
    &"C:\Program Files\Docker\Docker\Docker for Windows.exe"
    
    Sleep 5
    
    Write-Host '建立 Default Switch 虛擬交換器'
    Get-VMSwitch | Select Id,Name,SwitchType | Out-Host
    
    Write-Host '查詢 Docker 網路資訊'
    Write-Host ''
    
    docker network ls
    
    Write-Host ''
    Write-Host '查詢 HNS 網路資訊'
    Write-Host ''
    hnsdiag list networks
    
    Sleep 5
    

疑惑待解

我回報了這個問題到 Docker for Windows 的官方 Repo,希望未來可以得到解答!

相關連結

留言評論