WSL 中 Docker 使用總結

前言


最近看一篇文章中提到 WSL 中已經支持 Docker 運行了,最初不以爲意以爲還是千篇一律的標題黨 ( Docker Client + Docker Desktop for Windows ) ,後來嘗試之後發現確實可行,本文在此記錄一些遇到的問題。

關於版本


  • 系統最低版本要求: 1803 ( 17134 )

  • 1803 下可用 Docker 版本: 17.03.0 ~ 17.09.0

    使用高版本的 Docker 拉取鏡像時會報下面的錯誤:

    # docker pull hello-world
    Using default tag: latest
    latest: Pulling from library/hello-world
    1b930d010525: Extracting [==================================================>]     977B/977B
    failed to register layer: Error processing tar file(exit status 1): invalid argument
    

    原因見 這裏issue 中說這個問題應該在 17666 版本已經修復了。

  • 1809 下可用 Docker 版本: 17.03.0 ~ 18.06.1

    使用高版本的 Docker 創建容器時會報下面的錯誤:

    # docker run -it hello-world
    docker: Error response from daemon: OCI runtime create failed: container_linux.go:345: starting container process caused "process_linux.go:303: getting the final child's pid from pipe caused \"EOF\"": unknown.
    

    對應的 dockerd 日誌:

    ERRO[2019-07-09T02:00:58.717968000+08:00] stream copy error: reading from a closed fifo
    ERRO[2019-07-09T02:01:00.342200600+08:00] f91be3566b127aa49acc2021701035b2fadfa709a313d3f255999471ae309924 cleanup: failed to delete container from containerd: no such container
    ERRO[2019-07-09T02:01:00.451686200+08:00] Handler for POST /v1.39/containers/f91be3566b127aa49acc2021701035b2fadfa709a313d3f255999471ae309924/start returned error: OCI runtime create failed: container_linux.go:345: starting container process caused "process_linux.go:303: getting the final child's pid from pipe caused \"EOF\"": unknown
    

    安裝低版本的就好了

    # 安裝指定版本
    # 參考 https://docs.docker.com/install/linux/docker-ce/ubuntu/#install-docker-engine---community-1
    apt-get install -y docker-ce=18.06.1~ce~3-0~ubuntu
    

最終方案 ( 參考 )


  • 安裝 Docker

    # 安裝依賴
    sudo apt -y install cgroupfs-mount libltdl7
    # 下載安裝包
    wget -O /tmp/docker-ce.deb https://download.docker.com/linux/ubuntu/dists/xenial/pool/stable/amd64/docker-ce_17.09.0~ce-0~ubuntu_amd64.deb
    # 安裝
    sudo dpkg -i /tmp/docker-ce.deb
    # 卸載
    # apt remove -y docker-ce
    # 新建 docker 用戶組 ( 安裝 docker 的時候默認應該會添加這個用戶組 )
    # sudo groupadd docker
    # 將當前用戶加入docker組
    sudo usermod -aG docker ${USER}
    # 刷新 docker 組成員 ( 免 sudo 執行 docker 命令 )
    newgrp - docker
    
  • 修改配置文件

    # 修改 /etc/default/docker
    echo 'DOCKER_OPTS="-H=unix:///var/run/docker.sock -H=0.0.0.0:2375 --iptables=false"' >> /etc/default/docker
    # 修改 docker.service
    sed -i 's#^ExecStart=.*#EnvironmentFile=-/etc/default/docker\nExecStart=/usr/bin/dockerd -H fd:// $DOCKER_OPTS#' /lib/systemd/system/docker.service
    
  • 啓動 Docker ( 使用 管理員權限 打開 CMD 或者 PowerShell 來運行 WSL )

    # 加載 cgroupfs
    sudo cgroupfs-mount
    # 啓動服務
    sudo service docker start
    # 配合計劃任務,自行設置開機啓動
    

    查看狀態:

    # docker version
    Client:
     Version:      17.09.0-ce
     API version:  1.32
     Go version:   go1.8.3
     Git commit:   afdb6d4
     Built:        Tue Sep 26 22:42:18 2017
     OS/Arch:      linux/amd64
    
    Server:
     Version:      17.09.0-ce
     API version:  1.32 (minimum version 1.12)
     Go version:   go1.8.3
     Git commit:   afdb6d4
     Built:        Tue Sep 26 22:40:56 2017
     OS/Arch:      linux/amd64
     Experimental: false
     
    # docker info
    Containers: 0
     Running: 0
     Paused: 0
     Stopped: 0
    Images: 0
    Server Version: 17.09.0-ce
    Storage Driver: overlay2
     Backing Filesystem: <unknown>
     Supports d_type: true
     Native Overlay Diff: true
    Logging Driver: json-file
    Cgroup Driver: cgroupfs
    Plugins:
     Volume: local
     Network: bridge host macvlan null overlay
     Log: awslogs fluentd gcplogs gelf journald json-file logentries splunk syslog
    Swarm: inactive
    Runtimes: runc
    Default Runtime: runc
    Init Binary: docker-init
    containerd version: 06b9cb35161009dcb7123345749fef02f7cea8e0
    runc version: 3f2f8b84a77f73d38244dd690525642a72156c64
    init version: 949e6fa
    Kernel Version: 4.4.0-17134-Microsoft
    Operating System: Ubuntu 16.04.2 LTS
    OSType: linux
    Architecture: x86_64
    CPUs: 8
    Total Memory: 15.89GiB
    Name: DESKTOP-31U4I5S
    ID: 35XV:BUEF:HFQE:DFHI:5HVO:Y40P:2E2V:DC3L:YBAK:JGKR:WD34:OYPZ
    Docker Root Dir: /var/lib/docker
    Debug Mode (client): false
    Debug Mode (server): false
    Registry: https://index.docker.io/v1/
    Experimental: false
    Insecure Registries:
     127.0.0.0/8
    Live Restore Enabled: false
    
    WARNING: No memory limit support
    WARNING: No swap limit support
    WARNING: No kernel memory limit support
    WARNING: No oom kill disable support
    WARNING: No cpu cfs quota support
    WARNING: No cpu cfs period support
    WARNING: No cpu shares support
    WARNING: No cpuset support
    

    啓動容器看下效果:

    docker run -it --rm hello-world
    docker run -it --rm --name nginx --network host nginx
    curl 127.0.0.1
    

啓動服務遇到的問題


  • 最初,直接啓動 dockerd 會報下面的錯誤:

    # dockerd
    INFO[2019-07-05T16:46:00.707322400+08:00] libcontainerd: new containerd process, pid: 1573
    WARN[0000] containerd: low RLIMIT_NOFILE changing to max  current=1024 max=65536
    INFO[2019-07-05T16:46:01.739948500+08:00] [graphdriver] using prior storage driver: overlay2
    INFO[2019-07-05T16:46:01.782012800+08:00] Graph migration to content-addressability took 0.00 seconds
    WARN[2019-07-05T16:46:01.782616800+08:00] Your kernel does not support cgroup memory limit
    WARN[2019-07-05T16:46:01.782894700+08:00] Unable to find cpu cgroup in mounts
    WARN[2019-07-05T16:46:01.783166700+08:00] Unable to find blkio cgroup in mounts
    WARN[2019-07-05T16:46:01.783375800+08:00] Unable to find cpuset cgroup in mounts
    WARN[2019-07-05T16:46:01.783676600+08:00] mountpoint for pids not found
    Error starting daemon: Devices cgroup isn't mounted
    

    解決辦法:

    # 安裝並掛載 cgroup
    sudo apt -y install cgroupfs-mount
    sudo cgroupfs-mount
    
  • 再啓動還會報錯:

    # 使用非管理員權限運行
    Error starting daemon: Error initializing network controller: error obtaining controller instance: failed to create NAT chain: iptables failed: iptables -t nat -N DOCKER: iptables v1.6.0: can't initialize iptables table `nat': Table does not exist (do you need to insmod?)
    Perhaps iptables or your kernel needs to be upgraded.
     (exit status 3)
     
    # 使用管理員權限運行
    Error starting daemon: Error initializing network controller: Error creating default "bridge" network: Failed to Setup IP tables: Unable to enable NAT rule:  (iptables failed: iptables --wait -t nat -I POSTROUTING -s 172.19.0.0/16 ! -o docker0 -j MASQUERADE: iptables: Invalid argument. Run `dmesg' for more information.
     (exit status 1))
    

    原因是 iptables 功能缺失,禁用就好了 ( 參考 ) 。

    dockerd --iptables=false
    

    其實,只有針對 172.17.0.0/16 網段執行時不會報錯的,而且 MASQUERADE 規則是可以生效的 ( 容器可以訪問外網 ) 。

    # iptables --wait -t nat -I POSTROUTING -s 172.17.0.0/16 ! -o docker0 -j MASQUERADE
    # iptables --wait -t nat -I POSTROUTING -s 172.19.0.0/16 ! -o docker0 -j MASQUERADE
    iptables: No chain/target/match by that name.
    

    所以,安裝完 docker 後先不禁用 iptables 來啓動一遍 dockerd ,讓它自動生成 docker0 網絡並自動配置 SNAT ,之後就禁用 iptables 啓動 dockerd ,這樣用到 docker-compose 或者創建其他網橋網絡時就不會報錯了,只不過其他網絡無法訪問外網 ( 這個問題後面來解決 ) 。

  • 然後還會報錯:

    Error starting daemon: Error initializing network controller: Error creating default "bridge" network: permission denied
    

    原因是創建網橋的命令權限不足,比如第一次創建的 docker0 和 之後使用 docker network create 命令創建的自定義網絡都需要 管理員權限

    解決辦法: 以 管理員權限 打開 CMD 來運行 dockerd

    順帶提一點,WSL 下有些缺失的功能可能已經實現了部分實驗功能 ( 比如 ) ,在 管理員權限 下可以試試看。

網絡配置


  • ping 容器

    Windows 防火牆添加入站規則 - ICMPv4 類型的協議 ( 參考 )

  • 端口映射的問題

    比如啓動一個 Nginx 服務,做端口映射,在 Win10 1803 上會發現無法訪問 127.0.0.1:3000

    docker run -it --rm -p 3000:80 nginx
    

    接着在 Win10 1809 上試了是可以訪問的,然而換成 Tomcat 容器後就又不行了,而且 宿主機 也無法通過 容器內網 IP + 端口 來訪問,懷疑是網橋或者路由表的配置有缺失。於是定製了一個安裝各種網絡工具包的鏡像進行各種測試,發現把 Tomcat 的監聽端口改爲 80 就可以了。通過這個現象想起來可能是防火牆的原因,而 WSLiptables 功能有缺失應該是不起作用的,那麼問題應該是出在 Win10 的防火牆上。果然,在防火牆中添加 入站規則 放行容器中的監聽端口 ( 比如 8080 ) 就解決了,我猜應該是容器中使用了 Windows 下的防火牆做攔截,而 宿主機 卻被當成了外來者。

    注意:

    • 可以簡單粗暴的把 Windows 下的防火牆先關掉測試。
    • 如果不生效,可以考慮重啓 容器Docker 服務 或者電腦。
  • 容器訪問外網

    上面也提到了 iptables 功能缺失,就做不了 源網絡地址轉換 ( SNAT / MASQUERADE ) ,這就導致了容器不能訪問外網 ( 容器之間也無法跨網路訪問 ) 。

    # 新建一個自定義網絡
    docker network create --subnet 172.18.0.0/16 test_net
    

    目前有兩種不是很完美的辦法來臨時解決:

    • 在宿主機搭建代理服務器,在容器中使用代理連接:

      # 注意: 使用 host 網絡
      # 另外,防火牆需要按上文方法設置,否則其他容器無法訪問宿主機的 8888 端口
      docker run -d --name gost --restart always --network host ginuerzh/gost -L=:8888
      

      啓動容器的時候配置環境變量 http_proxyhttps_proxy

      docker run -it --rm --network test_net --entrypoint sh -e http_proxy=http://172.18.0.1:8888 -e https_proxy=http://172.18.0.1:8888 appropriate/curl
      curl https://baidu.com
      

      當然,也可以修改配置文件,對之後啓動的所有容器生效 ( 參考 )

      cat > ~/.docker/config.json <<EOF
      {
       "proxies":
       {
         "default":
         {
           "httpProxy": "http://172.18.0.1:8888",
           "httpsProxy": "http://172.18.0.1:8888"
         }
       }
      }
      EOF
      

      這種方式僅限於 HTTP 請求 ( 而且只能使用當前網絡的網關 IP 來訪問代理 ) ,換成低層次的 TCP 或者 UDP 通訊可能就不行了。

    • 類似於 Windows 上開 WiFi 共享 的操作。

      Docker 創建網絡時對應會在 Windows 下創建網卡 ( 比如 IP172.18.0.1 ) ,只要把無線網卡或者有線網卡的網絡共享給這個新建的網卡,容器就可以通過本地網卡來訪問外網了。

      具體步驟:

      1. 在指定網絡下啓動一個容器 ( 先啓動容器再共享網絡很重要,否則後面可能不會起作用 )
         docker run -it --rm --network test_net --entrypoint sh -v /etc/resolv.conf:/etc/resolv.conf appropriate/curl
      2. Windows 下進入"控制面板\網絡和 Internet\網絡連接"
      3. 查找網橋 ( 172.18.0.1 ) 對應的網卡,比如 {357fbf18-4a4d-4e22-bf01-43b601b650bd}
      4. 選中可用的本地網卡 ( 有線或者無線 ) 右鍵屬性
      5. 點擊"共享"選項卡
      6. 勾選"允許其他網絡用戶通過此計算機的 Internet 連接來連接",並在下拉框選擇上面找到的那個網卡
      7. 測試 curl baidu.com
      

      這種方式比起前一種方式支持的網絡更完善,缺點就是隻能共享網絡給一個網卡,而且無法訪問其他網絡的容器。

      如果使用域名無法訪問,可能是容器內 DNS 解析失敗,換個 DNS 服務器 ( /etc/resolv.conf ) 。

另外,還遇到過一個不是必現的問題,網橋有時候會變成 169.254.158.185/16 這種很神奇的 IP ,暫時還沒找到原因。如果遇到這個問題,可以關掉 Docker 後手動刪除網橋,讓它重新創建。

其實,網絡問題的排查無非就是幾個點:端口監聽,IP 分配、路由表、防火牆、DNS、NAT 。

其他問題


  • [ ] 鏡像加速器 可能會不能正常使用 ( 1803 + 17.09.1+ )

    表現形式爲 pull 鏡像的時候先從 鏡像站 下載一遍,再回 官方源站 下載一遍。

    # docker pull hello-world
    Using default tag: latest
    latest: Pulling from library/hello-world
    1b930d010525: Extracting [==================================================>]     977B/977B
    latest: Pulling from library/hello-world
    1b930d010525: Extracting [==================================================>]     977B/977B
    failed to register layer: Error processing tar file(exit status 1): invalid argument
    

    暫無解決辦法,如果 源站 下載過慢可以使用 HTTP 代理 或者 VPN

  • [x] 關於 WSLdocker-compose 的用法和問題參考我的另一篇 文章

  • [x] 不支持 docker exec 命令

    # docker exec -it nginx sh
    oci runtime error: exec failed: container_linux.go:265: starting container process caused "could not create session key: function not implemented"
    

    解決辦法: 使用 nsenter 命令進入容器 ( 參考 )

    # 設置容器名或者id
    NAME=nginx
    # 進入容器
    sudo nsenter -p -i -u -m -n -t `docker inspect -f {{.State.Pid}} ${NAME}` sh
    

    應該已經內置 nsenter 命令了,如果沒有的話自行安裝。

    可以寫一個函數來簡化調用:

    # 添加函數
    cat >> ~/.bashrc << "EOF"
    
    function docker-exec {
        name=$1
        shift
        nsenter -p -i -u -m -n -t `docker inspect -f {{.State.Pid}} ${name}` "$@"
    }
    EOF
    
    # 重新加載配置
    . ~/.bashrc
    

    再次調用就簡單多了:

    docker-exec nginx sh
    
  • [x] 容器內文件讀寫權限有問題

    可能是文件系統的問題也可能是容器用戶的權限問題

    比如運行數據庫之類的容器會提示權限不足的錯誤,比如:

    # docker run -it --rm --network host neo4j
    Active database: graph.db
    /var/lib/neo4j/bin/neo4j: line 283: cannot create temp file for here-document: Permission denied
    

    從錯誤信息看出是 tmp 目錄沒有權限,可以在啓動容器的時候使用 掛載數據卷 的方式來解決:

    docker run -it --rm --network host -v /tmp:/tmp -v ~/.neo4j/certificates:/var/lib/neo4j/certificates neo4j
    

    當然,權限不足的目錄可能不止這麼一個,需要自己一個個去排查,還是比較麻煩的。

  • [ ] 非 docker0 網絡下 --link 失效 ( 不會寫入配置到 /etc/hosts 中 )

其他玩法


查資料的過程中發現了另一篇文章—— 用 WSL 運行 Docker 鏡像 ,雖然沒有跑通文章中的例子,但是思路還是很有啓發性的。從文章中用法來看,WSL 的架構和 Docker 還是比較類似的,WSL 提供基本的 內核 ,商店中的各種 發行版 等價於 鏡像 用來提供系統目錄和軟件包,而每個 WSL 實例則等價於 容器

如果 WSL 後續能夠原生支持從 Docker Hub 下載鏡像,同時支持類似於 Docker 一樣的命令來管理 WSL 實例,豈不是一件很酷的事?

參考文章


寫在最後


WSL 上成功運行 Docker 其實就幾分鐘的事,不過爲了解決上面提到的一些問題又斷斷續續花了幾天時間,重裝了幾十遍 WSL,也不斷測試並修正了文中的例子,希望沒有紕漏吧。

自從寫完 Windows10內置Linux子系統初體驗 一文已是兩年過去了,見證了 WSL 從雞肋到現在基本滿足使用的過程,雖然還不是很完美,但它一直在不斷完善,而我也會持續關注並更新下去。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章