WSL 中 Docker 使用總結


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 就可以了。通過這個現象想起來可能是防火牆的原因,而 WSL 中 iptables 功能有缺失應該是不起作用的,那么問題應該是出在 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_proxy 和 https_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 下創建網卡 ( 比如 IP 為 172.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] 關於 WSL 下 docker-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 從雞肋到現在基本滿足使用的過程,雖然還不是很完美,但它一直在不斷完善,而我也會持續關注並更新下去。


轉載請注明出處:https://www.jianshu.com/p/20ebdbf68744


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM