# 如何修復 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 之後才有的功能)
    
    ```bash
    docker run -d --rm -p 80:80 --isolation process mcr.microsoft.com/windows/servercore/iis:windowsservercore-1903
    ```
    
2.  執行 Hyper-V 隔離模式時，容器網路是正常的，但是卻無法透過本地迴路進行連線！
    
    ```bash
    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](https://docs.docker.com/desktop/uninstall/)
    
    [Docker Desktop for Windows - Docker Hub](https://hub.docker.com/editions/community/docker-ce-desktop-windows)
    
    **無效！**
    
4.  完整移除並重新安裝 Hyper-V
    
    ```powershell
    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](https://github.com/MicrosoftDocs/Virtualization-Documentation/tree/master/windows-server-container-tools/CleanupContainerHostNetworking) 指令檔來清除網路狀態，不過一樣**無效**！
    
5.  比對兩台不同電腦的 Docker 設定
    
    我直接到 Azure 建立一台 D2S\_v3 的虛擬機器 (支援 Nested Virtualization 功能)，並將 Docker 安裝好，我發現新電腦會自動在 Hyper-V 的 VSwitch 建立一組名為 `nat` 的虛擬交換器。如果有切換到 Linux container 的話，則會自動建立起一個名為 `DockerNAT` 的虛擬交換器。
    
    接著我便手動在我的 Hyper-V 中手動新增一個 Internal 的 VSwitch，並且設定名稱為 `nat`，結果還是完全沒有效果，本地迴路還是無法連線！
    
6.  認真看完、看懂 [Windows 容器網路功能](https://learn.microsoft.com/zh-tw/virtualization/windowscontainers/container-networking/architecture) 的所有重要知識
    
    之前我只有看完這份文件，但卻沒有真正理解全部。這次遇到問題重看一遍，發現之前不太理解的地方突然懂了，因此也看的比較深入些。
    
    Windows 支援可透過 Docker 來建立的五種不同網路驅動程式或模式：`nat`、`overlay`、`transparent`、`l2bridge` 和 `l2tunnel`。
    
    底下這當圖片對我幫助很大，因為無論是 **Windows Server Container** 或 **Hyper-V Container**，每個容器都會使用一張虛擬網卡 (vNIC) 取得連線，而且該 vNIC 會連接到 Hyper-V 的虛擬交換器 (vSwitch) 來進行連線。這些虛擬網卡會由 Docker 自動建立與管理，照理來說你都不需要自己建立才對。
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/7f5ebcd85b3485213304-59151299-4e34f680-8a63-11e9-9188-5f30cf2cf690.png)
    
    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](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/bb14d1b0093beedae120-59151456-0a8fbc00-8a66-11e9-9f83-841b1ad09da1.png)
    
    但是在我的筆電上，執行 `docker network ls` 會看見 `nat` 網路，但是從 `Get-VMSwitch` 就是看不到，非常詭異！
    
    但因為這次問題，讓我徹底的認識了 **主機網路服務 (HNS)**，原來這是 Windows Container 一個相當重要的服務，用來自動對 Hyper-V 建立虛擬交換器 (vSwitch)，還有建立所需的 NAT 及 IP 集區！
    
    我從完全乾淨的 VM 中，透過 `hnsdiag list networks` 命令，可以找出所有透過 HNS 管理的網路：
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/098855aa22453dce2a0e-59151493-bc2eed00-8a66-11e9-840b-25b922ea1c7a.png)
    
    從我的筆電上，也可以透過 `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 虛擬交換器](http://vasudevg.blogspot.com/2018/04/tip-how-vethernet-default-switch-gets.html)，則必須移除 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](https://learn.microsoft.com/en-us/virtualization/windowscontainers/container-networking/network-drivers-topologies) 文件得知，原來 NAT 網路從 Windows Server 2019 (1809) 開始，每次重開機都會重建新的。
    
    ![image](https://stwillblogassets.blob.core.windows.net/files/images/external/user-images.githubusercontent.com/28d6aeadb9d2609e154e-59155061-ba8b1680-8ab2-11e9-82df-9c4f7d05b53f.png)
    
    現在我的電腦，每次重開機，都要執行以下命令，先把 Docker 服務停止，然後將所有透過 HNS 管理的網路移除，然後重新啟動 docker 服務，停三秒之後，再執行 `Get-VMSwitch | Select Id,Name,SwitchType` 命令，這個命令會讓 Hyper-V 自動建立缺少的 **Default Switch** 虛擬交換器。這個執行的過程順序不能有錯，一定要先刪除所有 HNS 管理的網路，然後重啟 docker 服務，此時 Docker 才能透過 HNS 建立 `nat` 虛擬交換器，之後 Hyper-V 隔離模式下的 NAT 網路才會正常！
    
    ```powershell
    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
    ```
    
    你可以透過以下命令查詢網路設定值：
    
    ```powershell
    docker network ls
    hnsdiag list networks -dl
    Get-VMSwitch | Select Id,Name,SwitchType
    ```
    
    不過，這不叫 **解決方案** (Solution)，這叫 **應變措施** (Workaround) 而已，但礙於網路對於 HNS 的資源太少，根本找不到任何蛛絲馬跡！
    
8.  嘗試建立獨立的 vSwitch、vNIC 與 docker network
    
    ```powershell
    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 服務，如此一來便可漂亮的解決我的問題。
    
    ```powershell
    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
    ```
    

### 疑惑待解

我回報了[這個問題](https://github.com/docker/for-win/issues/4058)到 Docker for Windows 的官方 Repo，希望未來可以得到解答！

-   [Docker for Windows use Hyper-V vEthernet (Default Switch) by default · Issue #4058 · docker/for-win](https://github.com/docker/for-win/issues/4058)

### 相關連結

-   [dockerd | Docker Documentation](https://docs.docker.com/engine/reference/commandline/dockerd/)
-   Windows 上的容器網路功能
    -   [容器網路功能概觀](https://learn.microsoft.com/zh-tw/virtualization/windowscontainers/container-networking/architecture) ([Container networking overview](https://learn.microsoft.com/en-us/virtualization/windowscontainers/container-networking/architecture))
    -   [網路拓樸和驅動程式](https://learn.microsoft.com/zh-tw/virtualization/windowscontainers/container-networking/network-drivers-topologies) ([Network topologies and drivers](https://learn.microsoft.com/en-us/virtualization/windowscontainers/container-networking/network-drivers-topologies))
    -   [網路隔離和安全性](https://learn.microsoft.com/zh-tw/virtualization/windowscontainers/container-networking/network-isolation-security) ([Network isolation and security](https://learn.microsoft.com/en-us/virtualization/windowscontainers/container-networking/network-isolation-security))
    -   [進階網路選項](https://learn.microsoft.com/zh-tw/virtualization/windowscontainers/container-networking/advanced) ([Advanced network options](https://learn.microsoft.com/en-us/virtualization/windowscontainers/container-networking/advanced))
-   [在 Windows 中設定 Docker | Microsoft Docs](https://learn.microsoft.com/zh-tw/virtualization/windowscontainers/manage-docker/configure-docker-daemon)
-   [Docker for Windows use Hyper-V vEthernet (Default Switch) by default · Issue #4058 · docker/for-win](https://github.com/docker/for-win/issues/4058)
-   [Diagnose problems, send feedack, and create GitHub issues](https://docs.docker.com/docker-for-windows/troubleshoot/)
