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

posted @ 2020-01-02 15:07  中国人醒来了  阅读(5273)  评论(1)    收藏  举报