WSL 中 Docker 使用总结

0.77字数 3804阅读 716

前言


最近看一篇文章中提到 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-ce-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 )

    # 加载 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 命令了,如果没有的话自行安装。

  • [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