让 Docker 在 Windows 上作为本地网络主机工作需要大量复杂的 hack 大杂烩才能工作,但这是可以做到的。

Docker Desktop是一种在 MacOS 或 Windows 上使用 Docker 的完美服务方式,但对于非平凡的用例,它还有很多不足之处。

我最近遇到了一个你可能认为比较常见的用例:我在 MacOS 上开发,但由于我的 MacBook Pro 只有 16GB 内存,我想使用另一台主机——在这种情况下,我的个人 Windows 游戏具有高达 32GB 内存的计算机——作为远程 Docker 主机。我们中有多少黑客、书呆子和极客在白天使用我们的(通常)MacBook 笔记本电脑,但下班后又转向我们定制的 Windows 盒子,以炸毁一些外星人的脸?

你会认为现在这将是一个非常受欢迎的领域,因此相对容易实现。但你大错特错。事实证明,让 Docker 在 Windows 上作为本地网络主机工作很简单。事实上,它需要大量杂乱无章的黑客大杂烩才能工作。

免责声明:无论如何,我都不是 Windows 管理员。我完全清楚,现在你可以用 Windows 做几乎任何事情,就配置、服务等而言,你可以用 *nix 操作系统做,但我完全承认我不知道当前的最佳实践。
此外,请勿在任何关键任务或生产情况下使用此设置。

总结

  1. 安装 WSL 2,然后在其中安装 Docker 作为守护进程。
  2. 配置所说的守护进程监听0.0.0.0:2375。
  3. 意识到无论您做什么,WSL 都会强制它绑定到, not localhost。0.0.0.0
  4. 连接在Windows 用户空间中运行的 TCP 代理以绑定0.0.0.0:2375
    并使用该代理将流量传送到localhost:2375.
  5. 测试来自外部主机的连接以验证它是否有效。

Docker 桌面:不该做什么

Docker Desktop 包含一种使您的 Windows 机器成为 Docker 主机(或者看起来如此)的简单方法:配置中的一个简单复选框。您可能认为这就是您所需要的,但这里有一个“陷阱”:它只会将 Docker 绑定到localhost:2375,而不是0.0.0.0:2375,这意味着使用此选项只会使 Docker 在本地机器上可用。使用此选项将无法从您网络上的其他任何地方访问。

旁注:Docker Desktop 包含此功能以使当前版本的守护程序可用于旧版 dockerCLI 客户端。尽管如此,就我们的目的而言,它几乎没有用。

起初,我没有意识到这一点。我使用了前面提到的复选框并尝试从本地网络上的另一台机器访问 Docker,但无济于事。想知道我做错了什么,我查看了 Windows 资源监视器。我发现 Docker 桌面应用程序正在侦听 port 2375,但它只绑定到localhost,因此它无法接收来自我网络上其他任何地方的流量。

接下来,我做了任何理智的人都会做的事情:我问“The Google”™。这篇关于 Docker 配置的文章 这篇关于 Docker 配置的文章暗示我可以通过修改 Docker 桌面应用程序中公开的 JSON 配置来控制这种行为。所以很自然,我在 Docker 配置中添加了以下内容:

JSON1{ “主机”:[ “tcp://0.0.0.0:2375” ]2}

然后,我在 Windows和我的防病毒软件中都禁用了我的防火墙,只是为了排除这种可能性,以防万一不起作用。当我重新启动 Docker 时,我再次检查了 Resource Monitor 的 Network 选项卡并寻找绑定到 port 的内容2375。是的,它是 Docker Desktop,好吧,但它仍然只绑定到localhost! 根据文档,即使配置正确,Windows 上的 Docker Desktop 也只是拒绝绑定到所有网络接口,这使得 Docker Desktop 对于除了最琐碎的用例之外的所有用例基本上都无用。

使用适用于 Linux 的 Windows 子系统

经过几次反复试验以使上述工作失败后,出于沮丧,我决定从 Windows 中删除整个 Docker 安装,并仅在 WSL 中重新安装守护程序。一旦我安装和配置了它,我推断,我可以将WSL中的守护进程暴露给网络,并最终能够完成一些工作。

我是多么的错误,多么无可救药的错误。

对于外行来说,“WSL”代表“Linux 的 Windows 子系统”,这可能是微软在其旗舰产品中内置的最令人惊奇的东西。WSL 的第一个版本直接向 Windows 内核添加了钩子/垫片,因此当编译的 Linux 二进制文件对它认为是 Linux 内核的系统调用时,Windows 内核接受这些系统调用并重定向或重新实现所需的逻辑“Windows 方式”,任何运行的 Linux 二进制文件都不知道它是在 Windows 上运行的。无需虚拟机管理程序或安装其他操作系统即可考虑虚拟化。
与任何事情一样,这并不完美。随着时间的推移,微软最终决定使用 WSL 版本 2 解决的问题开始在 Windows 中实现一个极其轻量级、完全透明的虚拟机,它允许相同的功能,只是实现方式不同。与以前的版本相比,这有一些权衡,但在大多数情况下,这是目前推荐的方式。
你需要 Windows 10,最好是最新的构建版本,才能使用 WSL。您可能还需要 Pro 版或更高版本(我不确定 Home 版是否允许运行 WSL)。您可以在此处阅读有关如何安装它的更多信息。

在 Linux 下安装守护进程非常简单。Docker 提供了有关安装过程的出色文档和出色的安装后指南,我在安装 Ubuntu 20.04 WSL 2 时遵循了这两个文档。这很简单。但是,由于 WSL 的启动方式与普通 Linux 发行版略有不同systemd,因此您没有运行的 .

dockerd安装和配置完所有内容后,您可以通过以 root 身份运行手动启动 docker 守护程序。

但是,如果您查看 Windows 资源监视器的网络选项卡,您将看不到dockerd任何地方。相反,这将在 process 下wslhost,它将仅绑定到local loopback。无论您做什么,WSL 都不允许您将其中的进程直接暴露给网络。我们仍然只停留在本地环回上。

因此,需要更多的魔法才能让事情顺利进行。TCP代理怎么样?

把代理放在前面:疯狂的练习

此时,我意识到我不可能让 Docker直接暴露给整个网络,所以我决定在它前面放一个 TCP 代理。“只要它只是盲目地来回穿梭,”我推断,“ dockerCLI 甚至不会知道其中的区别。”

那么,我们应该设置什么代理?我有使用HAproxy的经验,但看起来他们没有针对 Windows 的构建。我需要Windows用户空间中的一些东西,所以它被淘汰了。

我熟悉的另一个代理是Envoy,所以我查看了他们的网站,看看他们是否有可用的 Windows 版本。事实证明,在 Windows 上运行 Envoy 的“文档”(例如它)告诉你从源代码编译它(当时发生这种情况;希望将来会改变),我既没有时间也没有耐心, 安装海量的 Windows 开发环境只为构建 Envoy一次。那似乎有点疯狂。

再四处寻找之后,我找到了他们的FAQ,链接到这个文档,作为下载二进制文件的地方。只有一个问题:它们都是 Docker 镜像!所以我需要Docker 来运行Docker?!

那是零意义,所以我查看了该项目的 GitHub 版本——没有运气。因此,我开始查看该项目的“问题”,看看是否有其他人在获得预编译的 Windows 版本时寻求帮助,希望他们能得到有用的答案。

不幸的是,对于为 Envoy 提供预构建 Windows 二进制文件的问题,最有用的回应是来自一位维护者的回应:

请参阅特使代理。我们不提供二进制文件,但您可以将它们从 docker 容器中复制出来。

该线程确实继续,并且提供了文档链接和用于在 Linux机器上下载 Envoy 的工具。尽管如此,任何已链接的文档(截至撰写本文时)都完全缺少对 Windows 的任何提及,这使得该线程中的响应完全没有意义。

有了这个,我要去撕开一个 Docker 镜像。

用 Docker 镜像做不敬虔的事

在我看来,使用 Windows 进行开发和/或基于终端的工作/工作流仍然很痛苦。这不是一个流畅的体验,更不用说我必须重新安装关于 bajillion 不同的东西并配置更多只是为了获得一个体面的工作流程设置。

因此,我开始在我的 Mac 上从 Docker Hub 获取官方Envoy Docker Image for Windows(使用 Docker 桌面,当时它占用了我 80% 以上的内存,只是为了提取图像):

$ docker pull envoyproxy/envoy-windows Using default tag: latest Error response from daemon: manifest for envoyproxy/envoy-windows:latest not found: manifest unknown: manifest unknown

好吧,好吧,Docker,很难。让我们强制将最近发布的版本作为标签:

$ docker pull envoyproxy/envoy-windows:v1.18.2 v1.18.2: Pulling from envoyproxy/envoy-windows 4612f6d0b889: Downloading 5ff1512f88ec: Downloading ecaafce5c67e: Pulling fs layer d507bef22a1e: Waiting 9bdb36426121: Waiting e5b3f220cc68: Waiting e5410c30e7a9: Waiting 4b47829da3b1: Waiting 35eb096aecb4: Waiting 68a2a4ec685d: Waiting 9c1c360fc8b3: Waiting a08a596feca8: Waiting image operating system "windows" cannot be used on this platform

…aaaaand,不。如果主机和容器操作系统不匹配,Docker 甚至不会让你拉取镜像。

诅咒了众神之后,我决定不放弃。我记得Docker 映像只是大 tarball,所以我应该能够从 Docker Hub 下载平面 tarball 映像,理论上,将其解压到某个地方以浏览原本是 Windows 映像的文件系统。

事实证明,这并不容易。检索 Docker 映像涉及许多自定义标头和令牌,手动操作并不容易。幸运的是,我从 Moby 项目中偶然发现了这个脚本,它完全符合我的需要!我检索了脚本并运行它来获取 Windows Envoy 图像:

$ ./script.sh win envoyproxy/envoy-windows:v1.18.2 Downloading 'envoyproxy/envoy-windows:v1.18.2@v1.18.2' (12 layers)... Download of images into 'win' complete. Use something like the following to load the result into a Docker daemon: tar -cC 'win' . | docker load

注意:如果您收到一条错误消息,提示您找不到“mapfile”,您可能需要升级您的 Bash 版本。MacOS 附带一个旧版本,该版本落后了两个主要版本。或者,您可以在您的 WSL 机器上运行它,该机器可能已经有可用的 Bash 更新版本。

现在,我们可以浏览win/该脚本为我们创建的目录:

$ ls win Permissions Size User Date Modified Name drwxr-xr-x - jah 4 May 15:42 1a5606bb70dc20fb56456b2583d295bdd2e847c0617da3da68d152bdd6a10b78 drwxr-xr-x - jah 4 May 15:42 4e6cb5497aca4d83d2b91ef129fa823c225b0c76cefd88f5a96dd6c0fccdd6c7 drwxr-xr-x - jah 4 May 15:42 6bfb8784732bcc28ef5c20996dbe6f15d3a004bf241ba59575b8af65de0a0aaf drwxr-xr-x - jah 4 May 15:42 3712aa599c08d0fb31285318af13e44d988391806d2460167643340c4f3a7123 drwxr-xr-x - jah 4 May 15:42 698765937dc05ffcc458d8c2653563450bc169a724c62ed6a2c58f23c054b0ff drwxr-xr-x - jah 4 May 15:42 a4c3f3e7cef6cd7492338a26b7b307c0cd26e29379655f681d402c1eeaf595b6 drwxr-xr-x - jah 4 May 15:42 b93d56fb00e644574bb7c2df769bb383d7fa351730393d46239078026bbc8efc .rw-r--r-- 3.7k jah 4 May 15:42 b775d72f61762e116864ab49adc8de32045e001efd1565c7ed3afe984d6e07f0.json drwxr-xr-x - jah 4 May 15:42 c42480d1b057b159309c4e55553ba75d84c21dc6c870f7ed77b0744c72e755f5 .rw-r--r-- 3.7k jah 4 May 15:40 d00ba7ba582355550f5e42f453d99450754df890dec22fc86adb2520f3f81da2.json drwxr-xr-x - jah 4 May 15:42 d59df72a9d52b10ca049b2b6b1ce5b94f6ebb8a100ec71cea71ec7d8c0369383 drwxr-xr-x - jah 4 May 15:43 d8067d34f431844ea7a3068d31cdb9254f1fcb93bcaf1c182ceebdec17c8d1fc drwxr-xr-x - jah 4 May 15:42 ea8955ac8603cc8dbb34e70e0922b59271522839db7d626e0f79f45b954c0d12 drwxr-xr-x - jah 4 May 15:42 ec233e633fbbcbaf9d6f7ba3496ebc676f9b70ac4b95ba1127c466723976f55a .rw-r--r-- 1.2k jah 4 May 15:43 manifest.json .rw-r--r-- 51 jah 4 May 15:43 repositories

在逐个浏览每个目录并解压缩每个目录后layer.tar,我最终找到了我要找的东西:

$ cd 3712aa599c08d0fb31285318af13e44d988391806d2460167643340c4f3a7123 $ tar xf layer.tar && chmod -R 777 * && tree . tree . . ├── Files │ ├── Documents\ and\ Settings -> [Error\ reading\ symbolic\ link\ information] │ └── Program\ Files │ └── envoy │ └── envoy.exe ├── Hives │ ├── DefaultUser_Delta │ ├── Sam_Delta │ ├── Security_Delta │ ├── Software_Delta │ └── System_Delta ├── VERSION ├── json └── layer.tar 4 directories, 10 files

最后!我们找到了envoy.exe!

envoy.exe从容器 tarball 中复制出来后,我现在可以编写一个配置文件来代理 TCP 流量从0.0.0.0:2375同一端口上的本地环回。该配置文件最终看起来像这样:

static_resources: listeners: - name: docker-proxy address: socket_address: address: 0.0.0.0 port_value: 2375 protocol: TCP filter_chains: filters: - name: envoy.filters.network.tcp_proxy typed_config: "@type": type.googleapis.com/envoy.extensions.filters.network.tcp_proxy.v3.TcpProxy cluster: docker-actual stat_prefix: docker-proxy clusters: - name: docker-actual connect_timeout: 1s type: STATIC load_assignment: cluster_name: docker-actual endpoints: - lb_endpoints: - endpoint: address: socket_address: address: 172.25.65.236 port_value: 2375 protocol: TCP admin: address: socket_address: address: 0.0.0.0 port_value: 9901

重要提示:每次 WSL 启动时,它都会为自己分配一个不同的 IPv4 地址。我还没有弄清楚的一件事是如何让它始终使用相同的 IP 地址。您需要通过ifconfig eth0在 WSL shell 中运行来获取您的 WSL IP 地址,并将您的 IP 替换为我的示例配置中的那个。每次重新启动和/或每次停止和启动 WSL 时都需要执行此操作。

启动它

随着 Envoy 终于准备就绪,我们可以启动一切并对其进行测试。在我的 Windows 机器上,我在 WSL 中启动了一个 shell 并手动运行 Docker 守护程序:

$ sudo dockerd --tls=false &

然后,在另一个终端窗口中,我启动了一个 Powershell 会话并运行了 Envoy:

PS J:\envoy> .\envoy.exe -c .\envoy.yaml

如果一切正常,您应该能够看到它wslhost绑定到端口 2375 上的本地环回,并且envoy.exe也绑定到 2375 上的“IPv4 unspecified”:

打开网易新闻 查看更多图片

现在,我终于能够使用具有一些重要硬件的计算机作为我的 Docker 主机。剩下的就是导出环境变量并使用它!

$ export DOCKER_HOST= $ docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

成功!

结束的想法

如果不是很明显,我在这里所做的永远不应该在生产环境中完成。这纯粹是为了我自己在我自己的网络上使用,仅用于开发和测试目的。

此处描述的设置在很大程度上依赖于手动工作。每次重新启动主机时,您都需要挖掘 WSL 的内部 IPv4 地址,更改 Envoy 配置中的该条目,dockerd在 WSL 中启动,然后envoy.exe再次启动以连接所有内容。现在,我正在手动执行此操作,因为我不必经常重启那台 Windows 机器。也就是说,我计划在每次启动计算机时找到自动启动这些东西的方法。我相信这可以通过每次登录前启动计算机时通过 Windows“计划任务”(请参阅任务计划程序应用程序)调用的 BAT 文件来完成。

这个过程比它需要的要困难得多。从 Windows Docker 桌面应用程序拒绝遵守host's配置数组到 Envoy 项目声称他们有一个 Windows 版本的代理,但是很难检索到微软拒绝将绑定到 WSL 中端口的进程公开到网络的方式默认情况下,这是后路的主要痛苦。批评这里涉及的所有三家公司是公平的,因为他们为了做原本应该非常简单的事情而付出了多大的努力。公平地说,如果没有这一切都是不可能的上述这些地方的人们在构建出色的工具方面所做的出色工作,让像我这样的人能够完成这样的“疯狂科学”壮举。

但是,我承认没有必要从 Windows 中删除 Docker 桌面应用程序,
因为我们在 WSL 中安装的守护程序最终只能绑定到本地环回。至少在理论上,您应该能够在完全不使用 WSL 的情况下完成所有这些工作(减去检索 Envoy Docker 映像,您需要一个最新版本的 bash)。上面的重要词语是“理论上的”——我没有使用 Windows 的 Docker 桌面应用程序对此进行测试,并且不知道如果他们尝试使用它可能会遇到什么额外的麻烦。

但是,如果有以下任何一种情况,这些都不是必需的:

  1. Apple 出厂的默认 MacBook Pro 笔记本电脑具有超过 16GB 的 RAM 或;
  2. Docker 不需要具有专用资源的整个 VM 来“伪造”MacOS 上的容器概念。

但我们并不生活在一个完美的世界里。