如何在Mac上使用Docker

千鋒IT教育發表於2022-11-24

一、目標任務

首先要明確的是, 作為了一個每天在 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 方案

Colima 號稱是專門為了解決 Mac 平臺容器化工具鏈的, 但是實際測試發現目前 Colima 還不算穩定, 有時可能會有一些小問題; 當然 Colima 最大的問題是: 可自定義化程度不高, 底層基於 Lima. Colima 具體的使用方式啥的這裡暫不詳細描述, 目前還不穩定不太推薦.

五、Lima 方案

Lima 目前是基於 QEMU 的自動化 VM 方案, 當前由於其出色設計, 藉助 Cloud Init 可以在很多階段幫助我們完成 hook; 所以不論是裝個 Docker 還是 k8s, 亦或是弄個其他的東西都很方便; 而且很多方案比如 docker 官方都有相關樣例, 我們可以直接照抄外加做點自定義.

5.1、Lima 安裝

Lima 在 Mac 下安裝相對簡單, 以下命令將安裝 master 分支版本.

brew install lima --HEAD

在正常情況下, 安裝 Lima 會附帶安裝 QEMU, 如果本機已經安裝 QEMU, 可能需要執行以下命令將 QEMU 升級到 7.0:

brew upgrade qemu

為了使用 docker, 還需要透過 brew 安裝一下 docker cli:

brew install docker

5.2、Lima 使用

預設情況下 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

5.3、Lima 配置檔案

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 | 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
  ------
Copy

5.4、啟動 VM

limactl 命令提供了一個 start 子命令用於啟動一個虛擬機器, 子命令接受一個引數, 這個引數形式不同會產生不同的行為:

  • 如果引數為一個檔案路徑, 則假定檔案為一個 lima 虛擬機器的 yaml 配置, 讀取並啟動
  • 如果引數是單純字串, 首先嚐試從已存在的虛擬機器中查詢名字相同的, 找到則立即啟動
  • 如果引數是單純字串, 且未找到已存在同名的虛擬機器, 則嘗試透過內建模版來建立一個新的虛擬機器

以上面我自己定義的 docker 配置檔案為例, 我們直接啟動這個配置既可以建立一個 docker 虛擬機器:

limactl start ./docker-amd64.yaml

啟動後會提示是否編輯然後再啟動, 這是為了使用同一個配置來啟動多個 vm 使用的, 所以不編輯直接啟動即可:




稍等片刻後虛擬機器將啟動成功:




啟動完成後, 執行最下面列印出的兩條命令, 即可在宿主機上完整的使用 docker. 其本質上利用 docker context 功能, 然後透過將虛擬機器中的 sock 檔案掛載到宿主機, 並配置 docker context 來實現無縫使用 docker 命令.

5.5、虛擬機器調整

某些情況下, 我們需要定製一些 VM 裡的配置, 在定製時主要需要調整配置檔案的 provision 部分; 在該部分中, 如果 mode 被定義為 system 則會以 root 使用者執行相關命令, 否則以普通使用者來執行命令. 需要注意的是, 我們定義的指令碼需要具有冪等性, 因為指令碼在每次都會執行一次, 所以一般對於可能造成資料擦除動作的命令都要寫好判斷邏輯, 避免重複執行.

關於檔案掛載, 這裡推薦使用 9p 型別, 未來 lima 將完全切換到該掛載方式; 同時經過測試目前僅有 9p 掛載模式下, 本地目錄 rw 對映到虛擬機器時不會出現許可權問題, sshfs 方式掛載如果遇到 chown 之類的命令會造成許可權錯誤, 可能導致容器啟動失敗(例如 mysql).

在測試虛擬機器配置過程中, 可以直接使用 limactl delete -f xxxx 來強制刪除目標虛擬機器, 然後重新啟動即可; 虛擬機器名稱預設與 yaml 檔名相同, 可使用 limactl ls 命令檢視.

5.6、多平臺相容

在上面我的 docker 配置樣例中, 每次虛擬機器啟動完成後會自動安裝 binfmt:

docker run --platform=linux/amd64 --privileged --rm tonistiigi/binfmt --install all

這樣能保證無論 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 等), 非常適合重度容器使用者。


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/70023145/viewspace-2925023/,如需轉載,請註明出處,否則將追究法律責任。

相關文章