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
-
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 從雞肋到現在基本滿足使用的過程,雖然還不是很完美,但它一直在不斷完善,而我也會持續關注並更新下去。