共计 6585 个字符,预计需要花费 17 分钟才能阅读完成。
导读 | 首先要明确的是, 作为一个每天在 Linux Server 上 rm -rf 的人来说, 如果想在 Mac 上使用 Docker, 最舒服的也是兼容所有 docker cli 命令行操作即可。 |
至于图形化的界面完全不需要, 我们并不指望图形化界面能比敲命令快到哪里去, 也不指望图形化界面变为主力; 所以本篇文章的核心目标:
- 在 Mac 上使用完整的 docker cli 命令, 包括对基本的 -v 挂载支持
- 可以支持 x86 的模拟, 可以为 x86 build 或者运行相关镜像
- 在尽可能的情况下可以进行 CPU 架构切换, arm64 与 x86 最好都可以支持
首先是我们最熟悉的 Docker Desktop, 安装包奇大无比, UI 卡成翔, 启动速度更不用提而且还时不时的卡死, 所以 Docker Desktop 是完全不考虑的; 那么剩下几种方案类型如下:
- VM 虚拟机方案
- Colima 方案
- Lima 方案
先说结论: Lima YES! VM 虚拟机方案要花钱且难受, Colima 暂且不稳定. Lima 方案直接看第五节.
目前在 M1 上, 唯一可用或者说堪用的虚拟机当属 Parallels Desktop, 至于其他的 VBox、VMware 目前还不成熟; 如果纯 qemu 有点过于硬核 (愿意自己封装脚本的当我没说); 对于 Parallels Desktop 来说, 我们需要购买开发版本的 License, 因为我们需要借助 prlctl 来实现一些自动化 , 一年好几百… 经过测试这种方案也有一定可行性:
- 1、首先通过 PD 创建 Ubuntu 之类的虚拟机
- 2、在虚拟机里安装好 Docker
- 3、通过 cli 程序启动虚拟机, 并且将 ~ rw 挂载到虚拟机里
基于这个方案我个人尝试过, 曾经写过一个 PD 的小工具来辅助完成挂载动作. 但是这种工具有一些明显的缺点:
目前不支持 x86 的模拟, 可通过 binfmt 缓解, 但是不完善。虚拟机要花钱且需要虚拟机 cli 支持完善
Colima 号称是专门为了解决 Mac 平台容器化工具链的, 但是实际测试发现目前 Colima 还不算稳定, 有时可能会有一些小问题; 当然 Colima 最大的问题是: 可自定义化程度不高, 底层基于 Lima. Colima 具体的使用方式啥的这里暂不详细描述, 目前还不稳定不太推荐.
Lima 目前是基于 QEMU 的自动化 VM 方案, 当前由于其出色设计, 借助 Cloud Init 可以在很多阶段帮助我们完成 hook; 所以不论是装个 Docker 还是 k8s, 亦或是弄个其他的东西都很方便; 而且很多方案比如 docker 官方都有相关样例, 我们可以直接照抄外加做点自定义.
Lima 在 Mac 下安装相对简单, 以下命令将安装 master 分支版本.
brew install lima --HEADCopy
在正常情况下, 安装 Lima 会附带安装 QEMU, 如果本机已经安装 QEMU, 可能需要执行以下命令将 QEMU 升级到 7.0:
brew upgrade qemuCopy
为了使用 docker, 还需要通过 brew 安装一下 docker cli:
brew install dockerCopy
默认情况下 Lima 安装完成后会生成一个 lima 的快捷命令, 目前不太推荐使用, 原因是看起来方便一点但是没法控制太多参数, 所以仍然建议使用标准的 limactl 命令进行操作. limactl 使用方式如下:
Lima: Linux virtual machines | |
Usage: | |
limactl [command] | |
Examples: | |
Start the default instance: | |
$ limactl start | |
Open a shell: | |
$ lima | |
Run a container: | |
$ lima nerdctl run -d --name nginx -p 8080:80 nginx:alpine | |
Stop the default instance: | |
$ limactl stop | |
See also example YAMLs: /opt/homebrew/share/doc/lima/examples | |
Available Commands: | |
completion Generate the autocompletion script for the specified shell | |
copy Copy files between host and guest | |
delete Delete an instance of Lima. | |
edit Edit an instance of Lima | |
factory-reset Factory reset an instance of Lima | |
help Help about any command | |
info Show diagnostic information | |
list List instances of Lima. | |
prune Prune garbage objects | |
shell Execute shell in Lima | |
show-ssh Show the ssh command line | |
start Start an instance of Lima | |
stop Stop an instance | |
sudoers Generate /etc/sudoers.d/lima file for enabling vmnet.framework support | |
validate Validate YAML files | |
Flags: | |
--debug debug mode | |
-h, --help help for limactl | |
-v, --version version for limactl | |
Use "limactl [command] --help" for more information about a command.Copy |
Lima 通过读取一个 yaml 配置描述文件来决定如何创建一个虚拟机, 该文件基本结构如下:
# 定义每个平台架构需要使用的启动镜像 | |
images: | |
- location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img" | |
arch: "x86_64" | |
- location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-arm64.img" | |
arch: "aarch64" | |
# 定义虚拟机需要使用哪个架构启动 (对应上面的镜像) | |
arch: "x86_64" | |
# CPU 数量 | |
cpus: 4 | |
# 内存大小 | |
memory: "16G" | |
# 磁盘大小 | |
disk: "100G" | |
# 虚拟机与 macOS 宿主机挂载时使用的挂载技术 | |
# 目前推荐 9p, 可换成 sshfs, 但是 sshfs 会有权限问题 | |
mountType: 9p | |
# 定义虚拟机和 macOS 宿主机有哪些目录可以共享 | |
mounts: | |
- location: "~" | |
# 定义虚拟机对这个目录是否可写 | |
writable: true | |
9p: | |
# 对于可写的共享目录, cache 推荐类型为 mmap, 不写好像默认 fscache | |
cache: "mmap" | |
- location: "/tmp/lima" | |
writable: true | |
9p: | |
cache: "mmap" | |
# containerd is managed by Docker, not by Lima, so the values are set to false here. | |
containerd: | |
system: false | |
user: false | |
# cloud-init hook 定义 | |
provision: | |
# 定义以什么权限在虚拟机内执行脚本 | |
- mode: system | |
# This script defines the host.docker.internal hostname when hostResolver is disabled. | |
# It is also needed for lima 0.8.2 and earlier, which does not support hostResolver.hosts. | |
# Names defined in /etc/hosts inside the VM are not resolved inside containers when | |
# using the hostResolver; use hostResolver.hosts instead (requires lima 0.8.3 or later). | |
script: | | |
#!/bin/sh | |
sed -i 's/host.lima.internal.*/host.lima.internal host.docker.internal/' /etc/hosts | |
- mode: system | |
script: | | |
#!/bin/bash | |
set -eux -o pipefail | |
if command -v docker >/dev/null 2>&1; then | |
docker run --platform=linux/amd64 --privileged --rm tonistiigi/binfmt --install all | |
exit 0 | |
else | |
export DEBIAN_FRONTEND=noninteractive | |
curl -fsSL https://get.docker.com | sh | |
docker run --platform=linux/amd64 --privileged --rm tonistiigi/binfmt --install all | |
# NOTE: you may remove the lines below, if you prefer to use rootful docker, not rootless | |
systemctl disable --now docker | |
apt-get install -y uidmap dbus-user-session | |
fi | |
- mode: user | |
script: | | |
#!/bin/bash | |
set -eux -o pipefail | |
systemctl --user start dbus | |
dockerd-rootless-setuptool.sh install | |
docker context use rootless | |
probes: | |
- script: | | |
#!/bin/bash | |
set -eux -o pipefail | |
if ! timeout 30s bash -c "until command -v docker >/dev/null 2>&1; do sleep 3; done"; then | |
echo >&2 "docker is not installed yet" | |
exit 1 | |
fi | |
if ! timeout 30s bash -c "until pgrep rootlesskit; do sleep 3; done"; then | |
echo >&2 "rootlesskit (used by rootless docker) is not running" | |
exit 1 | |
fi | |
hint: See "/var/log/cloud-init-output.log". in the guest | |
hostResolver: | |
# hostResolver.hosts requires lima 0.8.3 or later. Names defined here will also | |
# resolve inside containers, and not just inside the VM itself. | |
hosts: | |
host.docker.internal: host.lima.internal | |
portForwards: | |
- guestSocket: "/run/user/{{.UID}}/docker.sock" | |
hostSocket: "{{.Dir}}/sock/docker.sock" | |
# 自己定义的启动后消息输出 | |
message: | | |
To run `docker` on the host (assumes docker-cli is installed), run the following commands: | |
------ | |
docker context create amd64 --docker "host=unix://{{.Dir}}/sock/docker.sock" | |
docker context use amd64 | |
------ |
limactl 命令提供了一个 start 子命令用于启动一个虚拟机, 子命令接受一个参数, 这个参数形式不同会产生不同的行为:
- 如果参数为一个文件路径, 则假定文件为一个 lima 虚拟机的 yaml 配置, 读取并启动
- 如果参数是单纯字符串, 首先尝试从已存在的虚拟机中查找名字相同的, 找到则立即启动
- 如果参数是单纯字符串, 且未找到已存在同名的虚拟机, 则尝试通过内置模版来创建一个新的虚拟机
以上面我自己定义的 docker 配置文件为例, 我们直接启动这个配置既可以创建一个 docker 虚拟机:
limactl start ./docker-amd64.yamlCopy
启动后会提示是否编辑然后再启动, 这是为了使用同一个配置来启动多个 vm 使用的, 所以不编辑直接启动即可:
稍等片刻后虚拟机将启动成功:
启动完成后, 执行最下面打印出的两条命令, 即可在宿主机上完整的使用 docker. 其本质上利用 docker context 功能, 然后通过将虚拟机中的 sock 文件挂载到宿主机, 并配置 docker context 来实现无缝使用 docker 命令.
某些情况下, 我们需要定制一些 VM 里的配置, 在定制时主要需要调整配置文件的 provision 部分; 在该部分中, 如果 mode 被定义为 system 则会以 root 用户执行相关命令, 否则以普通用户来执行命令. 需要注意的是, 我们定义的脚本需要具有幂等性, 因为脚本在每次都会执行一次, 所以一般对于可能造成数据擦除动作的命令都要写好判断逻辑, 避免重复执行.
关于文件挂载, 这里推荐使用 9p 类型, 未来 lima 将完全切换到该挂载方式; 同时经过测试目前仅有 9p 挂载模式下, 本地目录 rw 映射到虚拟机时不会出现权限问题, sshfs 方式挂载如果遇到 chown 之类的命令会造成权限错误, 可能导致容器启动失败 (例如 mysql).
在测试虚拟机配置过程中, 可以直接使用 limactl delete -f xxxx 来强制删除目标虚拟机, 然后重新启动即可; 虚拟机名称默认与 yaml 文件名相同, 可使用 limactl ls 命令查看.
在上面我的 docker 配置样例中, 每次虚拟机启动完成后会自动安装 binfmt:
docker run --platform=linux/amd64 --privileged --rm tonistiigi/binfmt --install allCopy
这样能保证无论 Lima 虚拟机原始架构是什么, 都能运行其他平台的 docker 镜像; 典型的例如某些 openjdk8 镜像只有 amd64 的版本, 但是在 lima 虚拟机为 aarch64 的情况下仍然可以使用.
除了这种“速度较快”的跨架构运行方式, lima 还支持直接在 VM 中定义架构, 这样在 qemu 启动时则会直接从 VM 系统层模拟目标架构; 这种方式的好处是对目标架构兼容性很好, 但是运行速度会更慢. 调整 VM 架构只需要修改 arch 配置即可 (注意, 目标架构的镜像一定要配置好):
# 定义每个平台架构需要使用的启动镜像 | |
images: | |
- location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-amd64.img" | |
arch: "x86_64" | |
- location: "https://cloud-images.ubuntu.com/releases/22.04/release/ubuntu-22.04-server-cloudimg-arm64.img" | |
arch: "aarch64" | |
# 定义本虚拟机需要使用哪个架构启动 (对应会使用上面目标架构的镜像) | |
arch: "aarch64"Copy |
目前整体来看, Docker Desktop 在 mac 上基本上是很难用的, Colima 现在还不太成熟, 适合轻度使用 docker 的用户; 而重度使用 docker 并且有定制化需求的用户还是推荐 Lima 虚拟机; 同时 Lima 也支持很多操作系统, 官方有大量的样例模版 (包括 k8s、k3s、podman 等), 非常适合重度容器使用者。
