在Apple Silicon上构建x86_64 Docker容器

2020-12-05 08:05:27

我最近购买了新的M1 Macbook Pro。我的旧车是2012年中的Retina,最近呼吸了最后一口气,因此没有时间等待。对于开发人员来说,M1肯定还没有准备好,但是我决定忍受几个月的困难过渡,而不是购买很快就会过时的东西。

Docker在撰写本文时不起作用的主要组件之一是Docker,尽管它似乎很接近。对于我们许多人来说,拥有Docker工作对我们的工作流程至关重要,我想看看能否在Docker正式就绪之前实现这一目标。另外,仅因为Docker是为M1发布的,并不意味着我们可以使用可以在x86_64上无缝运行的容器。本文的最后一部分可能会涉及很长一段时间,因此了解仿真如何在将来支持我的工作将很有用。

我还要指出,我的方法绝对是纯粹的。我们并没有购买具有新颖架构的笔记本电脑,以免被我们遗忘的世界中的二进制文件污染。除了容器中运行的内容以及Docker客户端之外,其他所有内容都在没有Rosetta 2的M1上运行。

安装Mac的Docker。如果您启动它,它将失败,但这很好。现在,您有了必要的命令行工具,并且它们可以工作。就是这样,您完成了。

Docker在其他基于ARM的系统(例如Ubuntu Focal)上运行良好。这就是我在这里使用的。

有几个小项目在Big Sur中包装了新的虚拟化框架。我登陆了vftool。为什么?因为事实证明,避免完全安装XCode来编译和运行它很容易,所以我想避免这种开销。

首先,我们需要一个文件夹来保存所有内容。确保您具有可用的XCode命令行工具,然后下载并编译vftool。

$ mkdir ubuntu-docker-m1 $ cd ubuntu-docker-m1 $ xcode-select --install<按照屏幕上的说明> $ git clone https://github.com/evansm7/vftool.git克隆到' vftool' ...< ...> $ clang -framework基础-framework虚拟化vftool / vftool / main.m -o vftool / vftool.bin $文件vftool / vftool.bin vftool / vftool.bin:Mach-O 64位可执行文件arm64

快要到了,但是不完全是。我们已经安装了vftool,但是如果此时使用内核运行它,则会出现错误:

配置验证失败!错误域= VZErrorDomain代码= 2“虚拟化需要“ com.apple.security.virtualization”授权” UserInfo = {NSDebugDescription =虚拟化需要“ com.apple.security.virtualization”授权}

为了赋予它权利,我们需要对编译后的二进制文件进行签名。为此,您需要一个自签名证书。打开钥匙串访问并使用证书助手创建一个:

单击将打开一个对话框。将证书类型设置为“代码签名”,复制“名称”字段中的内容,然后创建它。

您可以运行所需的任何Linux发行版,但是我选择运行Ubuntu Focal的云映像(感谢droidix)。我们需要一个内核,一个initrd和磁盘映像本身。我们还需要解压缩内核和磁盘映像。

我们还希望调整磁盘映像的大小,否则空间将用完。使用qemu-img可以很容易地做到这一点,但是它不能在带有Homebrew的M1上本地编译。如我先前所写,这是一种纯粹的方法,因此我们将仅使用dd。 hacky,但是可以用。让我们给它大约20个演出。 (我们稍后将调整实际分区的大小。)

$ ls -lh vm / focal-server-cloudimg-arm64.img -rw-r--r-- 1 chris人员1.3G 4 Dec 12:17 vm / focal-server-cloudimg-arm64.img $ dd if = / dev / zero of = vm / focal-server-cloudimg-arm64.img seek = 20000000 obs = 1024 count = 0 0 + 0记录在0 + 0记录中输出0个字节,以0.000011秒(0字节/秒)传输$ ls- lh vm / focal-server-cloudimg-arm64.img -rw-r--r-- 1 chris员工19G 4 Dec 17:03 vm / focal-server-cloudimg-arm64.img

完善。现在,让我们运行该程序,但现在无需指定根文件系统,因此我们可以进行一些更改。

运行该命令,并注意它所连接的TTY,在本例中为/ dev / ttys009。打开第二个终端窗口并连接到它。

复制并粘贴以下内容(再次感谢droidix)。这会将root密码更改为root,并设置SSH和网络。

mkdir / mnt挂载/ dev / vda / mnt chroot / mnt touch /etc/cloud/cloud-init.disabled echo' root:root' | chpasswd ssh-keygen -f / etc / ssh / ssh_host_rsa_key -N'' -t rsa ssh-keygen -f / etc / ssh / ssh_host_dsa_key -N'' -t dsa ssh-keygen -f / etc / ssh / ssh_host_ed25519_key -N'' -t ed25519 cat<<< EOF> /etc/netplan/01-dhcp.yaml网络:渲染器:联网以太网:enp0s1:dhcp4:真实版本:2 EOF

(可选)在使用时添加您的SSH密钥,以便以后访问。

我们已经完成了,所以让虚拟机知道这一点。

这次,使用root用户运行VM。我还暂时将其分配的内存提高到了两个演出,这对于我到目前为止的目的来说似乎还不错。将-m标志更改为适合您的内容。

再次,注意该设备,然后像上面一样使用屏幕连接到该设备。应该会遇到一个Ubuntu登录提示符,其用户名和密码均为root。如果在上一步中设置了SSH密钥,则可以键入hostname -I来获取虚拟机的IP地址,然后以root用户身份将其SSH到其中。如果您发现要处理的烦人操作,这使您可以分离屏幕(先按Ctrl + A再按D)– VM将在不连接的情况下愉快地运行。无论哪种方式,我们现在都可以在M1的虚拟机上运行Ubuntu ARM。

#uname -a Linux ubuntu 5.4.0-56-generic#62-Ubuntu SMP Mon Nov 23 19:17:58 UTC 2020 aarch64 aarch64 aarch64 GNU / Linux

还记得我提到过我们需要调整文件系统的大小吗?让我们立即完成。

#resize2fs / dev / vda resize2fs 1.45.5(2020年1月7日)/ dev / vda的文件系统安装在/;在线调整大小所需的old_desc_blocks = 1,new_desc_blocks = 3现在,/ dev / vda上的文件系统的长度为5000000(4k)个块。

现在,我们要在VM内部设置Docker守护程序。 Docker有相当不错的安装说明,因此您应该阅读这些说明。如果您不想这样做,请执行以下操作:

#apt-get更新#apt-get安装apt-transport-https ca证书curl gnupg-agent software-properties-common#curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add-#添加-apt-repository" deb [arch = arm64] https://download.docker.com/linux/ubuntu $(lsb_release -cs)stable" #apt-get更新#apt-get安装docker-ce docker-ce-cli containerd.io

注意:在这一点上,我的VM和VPN(Mullvad)对彼此的存在不满意,并且除非断开VPN的连接,否则VM无法访问Internet。让我知道您是否找到解决方案。

至此,Docker已启动并运行。安装建议您运行hello-world映像,您现在可以根据需要执行此操作。

现在,让我们设置Docker守护程序以接受来自外部世界的连接。说明在这里,但是如果您懒惰,也可以按照我的说明进行操作。

#mkdir -p /etc/systemd/system/docker.service.d/#cat<< EOF> /etc/systemd/system/docker.service.d/override.conf [Service] ExecStart = ExecStart = / usr / bin / dockerd EOF#cat<<<" EOF> /etc/docker/daemon.json {" hosts&#34 ;: [" unix:///var/run/docker.sock" ;," tcp://0.0.0.0 :2375"]} EOF#systemctl守护进程重新加载#systemctl重新启动docker.service

#lsof -i | grep dockerd dockerd 5794根7u IPv6 38573 0t0 TCP *:2375(LISTEN)

Docker守护程序已设置并运行。确保获得主机名为-I的VM的IP地址,然后转到客户端。

现在到应许之地。确保在VM(以#表示)和macOS(以$表示)中都有方便的终端窗口。首先,确保您的Docker客户端知道在哪里可以找到您的服务器,将下面的IP地址替换为从VM获得的任何IP地址。

您可以通过检查上面使用的hello-world图像弹出来验证连接。

如果您只想为ARM构建映像,则可以在此处停止。恭喜,您已经完成了。如果Docker for Mac在两天内更新了M1,您可能会觉得自己浪费了很多时间。如果您想要更多,那就冒险吧。即使在更新Docker之后,它也应该很有用。

Buildx是Docker中的一项实验性功能。要在客户端中启用实验性功能,我们需要在Docker的配置中将实验性标志设置为enabled。如果您尚未对Docker配置进行任何更改,请运行以下行。如果有的话,请手动进行,或者至少免除我承担任何将其分解的责任。

$ mkdir容器$ cd容器$ cat<< EOF>来自python:3.9 WORKDIR / app COPY app.py的Dockerfile。运行pip install fastapi uvicorn EXPOSE 8000 CMD uvicorn --host 0.0.0.0 app:app EOF $ cat<<<<" EOF>从fastapi导入app.py FastAPI app = FastAPI()@ app.get(" /")def root():返回{" message&#34 ;:" Hello World&# 34;} EOF

让我们来构建并运行该程序,以供其查看。您可以使用上面获得的IP地址在端口8000的浏览器中打开它。

$ docker buildx build --platform = linux / arm64 -t testimage:arm。 $ docker run -p 8000:8000 testimage:arm信息:服务器进程已启动[6]信息:等待应用程序启动。信息:应用程序启动完成。信息:在http://0.0.0.0:8000上运行的Uvicorn(按CTRL + C退出)

注意:有时容器不能正常退出。如果发生这种情况,请转到打开的VM终端窗口,然后键入docker kill $(docker ps -q),这将杀死所有正在运行的容器。

如果您尝试将其构建为x86_64,则效果会有所不同:

$ docker buildx build --platform = linux / amd64 -t testimage:x86_64。 < ...>解决失败:rpc错误:代码=未知desc =使用前端dockerfile.v0无法解决:加载LLB失败:不支持平台linux / amd64上的运行时执行

我们需要模拟x86_64指令,以便我们可以构建一个容器。 Artur Klaser上有一篇文章,介绍了如何在为手臂构建的z计算机上启动并运行它,但我们希望相反。我们需要安装一些东西才能使它工作。让我们回到虚拟机上运行它。这可能需要一段时间,因此请在喝时喝一杯。

我们还需要重新创建该生成器,以便buildx知道它具有要定位的新平台,然后将其用作我们的默认平台:

$ docker buildx创建--name生成器$ docker buildx检查生成器--bootstrap< ...>平台:linux / arm64,linux / amd64,linux / riscv64,linux / ppc64le,linux / s390x,linux / 386 $ docker buildx use builder

看起来不错!让我们尝试一下。您可能会注意到,由于仿真,这比第一次运行要慢一些。下面的--load选项可确保图像不会被丢弃。

我们完成了!这仅起作用,因为基本映像可用于多种体系结构,但许多流行的Docker映像均可用。现在,您已经获得了两个分别针对x86_64和arm构建的映像,并且它们都在虚拟机中运行-尽管其中一个具有仿真功能。如果您正常运行最新图像并在浏览器中访问该应用程序,则可以正常运行。

我对实现这一目标的技术深表敬畏。我们在M1中采用了全新的芯片,包括容器化,仿真和虚拟化,而花费在开发人员身上的时间却令人震惊。

在此设置中仍然会有一些怪癖。 qemu不能很好地工作,它在Docker中的大部分用途似乎来自为ARM构建的x86架构上的人们,因此,由于正在分发M1,因此可能会弹出其他错误。但是,如果您要构建使用解释性代码的图像,而编译后的依赖性很少甚至没有,那么这应该可以正常工作! Adrian Mouat在Docker Blog上有一篇博客文章,对它进行了进一步说明,并提供了一些有用的替代方法,包括交叉编译。

PS:一个更简单的解决方案是在您选择的云服务的免费层上运行x86虚拟机。为此,请安装Docker,将守护程序配置为从外部接收连接,并在您的环境中适当设置DOCKER_HOST。无需其他选择,因为您所构建的机器不会更好。