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 从鸡肋到现在基本满足使用的过程,虽然还不是很完美,但它一直在不断完善,而我也会持续关注并更新下去。