🐳

Docker 環境を作るなら Ubuntu のクロスプラットフォームな仮想化ツール Multipass を使おう

2024/04/13に公開

はじめに

Web アプリケーションを開発する際、バックエンドの API サーバーやデータベースなどの複数のマシン環境を1台の PC で動かすことの可能なコンテナツールとして、Docker は広く利用されています。

この便利な Dockerですが、複数人で Web アプリケーションを開発しているチームで各メンバの開発用 PC の OS が異なる状態(例えば、A さんが Windows、B さんが macOS など)で、Docker をローカル環境に直接インストールしていた場合、下記のような不都合が発生します。

  1. Docker のバージョンがメンバ間で統一されていない 😱
  2. コンテナ外で使用必須のツールのインストール方法・バージョンがメンバ間で異なる 😱
  3. コンテナ外で使用必須のシェルスクリプトは OS 毎で(または OS の差異を考慮して)作成する必要がある 😱

以上の問題は、Docker ホスト環境が 各 OS へ依存してしまっていることが原因です。
特に、3.のケースは Unix系の OS と Windows の場合、 Bash と PowerShell の shell の違いやファイルパス表記方法の違いのギャップが大きく苦しむことになります。この問題は、Python や Node.js などでバッチを作成することで、処理やファイルパスの違いを吸収することができますが、一方、 2.の問題が発生するため良い解決策とは言えません。

この問題の解決策として、有効なのが Linux の仮想環境上に Docker をインストールする[1]ことで、複数の OS 依存性を特定の Linux OS にのみに限定する方法です。有名な構成としては、Vagrant + VirtualBox + Docker がよく知られています。Vagrant は、CLIで仮想環境の作成が可能で、環境の初期化時設定をコードとして管理可能な IaC (Infrastructure as Code)が可能な点が優れています。

しかしながら今現在、仮想化ツールの VirtualBox は Apple Silicone (いわゆる M1/M2/M3 Mac など)の PC では正式には動作しないという問題があります。

このため、下記の条件を満足する仮想化ツールとして白羽の矢が立ったのが Ubuntu 環境のクロスプラットフォームな仮想化ツールである Multipass です。

  • Apple Silicone の macOS 環境で動作可能
  • CLI のみで仮想環境構築が可能
  • Cloud-init による IaC が可能

本記事では、Multipass を使用したクロスプラットフォームな Docker 開発環境構築の方法について紹介します。

この記事を読んで欲しい人

  • 各メンバの開発用 PC の OS が異なるチームで、クロスプラットフォームな Docker のローカル開発環境を構築したい人
  • Multipass 初心者で基本的な使い方を学びたい人
  • 商用利用可能かつ無料の Docker 環境が欲しい人(Docker Desktop からの移行)

前提知識

本記事は下記についての基礎的な知識を前提として話を進めます。(Linux や Docker に全く触ったことがないという人にはハードルが高いと思います。)

  • CLI (シェル)の操作
  • ssh 接続
  • Docker
  • VSCode

Multipass とは?

Multipass は Debian 系 Linux ディストリビューションである Ubuntu 限定で仮想マシンを提供してくれるツールです。

Multipass の特徴としては、シンプルなコマンドを使用し CLI 上で仮想マシンを作成することが可能という点です。Windows・Mac・Linux 環境で動作することはもちろん Apple Silicone Mac(いわゆる M1/M2/M3 Mac)でも、Rosetta2 の利用なしに動作します。
また、yaml 形式の Cloud-init というファイルに仮想マシンの設定やパッケージのインストールを記述しておくことで、仮想マシン作成時のセットアップ作業を自動化可能です。

https://multipass.run/

Multipass のインストール

お使いの環境の手順を参照して、Multipass をインストールします。

macOS 環境向けインストール手順

macOS 向けの Multipass のインストール方法について、説明します。

brew によるインストール

Multipass のインストールは Mac向けのパッケージマネージャである Homebrew が利用できることを前提とします。もし、まだ導入されてなければ下記ページからインストールを実施してください。

https://brew.sh/ja/

zsh
brew install --cask multipass

下記のコマンドで multipass のインストール確認を実施します。
バージョン情報が表示されれば正しくインストールされています。

zsh
multipass --version

アクティビティモニターを起動して、multipassdが起動しているか確認をして下さい。
もし、起動が確認できない場合は PC の再起動を試してみて下さい。

multipassdの起動確認
アクティビティモニタ

brew によるバージョン更新

必要に応じて、Multipass のバージョン更新を実施します。作成した既存の仮想環境は新しいバージョンでも引き続き利用できます。

zsh
brew upgrade multipass

brew によるアンインストール

zsh
brew uninstall multipass

(参考)Mac 著者環境

カテゴリ 内容
OS macOS Sonoma 14.3.1
チップ Apple M1
メモリ 16GB
ストレージ 512GB
Multipass 1.13.1+mac

Windows 環境向けインストール手順

ドライバのインストール

Windows 環境で Multipass を動作させるには Hyper-V または VirtualBox が必要ですので、下記記事を参考にインストールを実施して下さい。デフォルトのドライバは Hyper-V で設定されており、 Hyper-V は Microsoft 製であることから Windows 環境との相性を考えると VirtualBox よりも Hyper-V の導入を個人的に推奨します。

https://multipass.run/docs/installing-on-windows

Windows11 Home 向けユーザーの Hyper-V インストール手順は下記を参照をして下さい。

https://qiita.com/masatonasou/items/7c5ca7be6f6b519f3d4b

winget のインストール

winget は Windows 標準のパッケージマネージャです。

winget が環境にインストールされているかどうかは下記のコマンドで確認します。
バージョン情報が表示されればすでにインストールされています。

pwsh/powershell
winget --version

コマンドが見つからなかった場合は下記ページの「winget をインストールする」に従って wingetのインストールを実施して下さい。

https://learn.microsoft.com/ja-jp/windows/package-manager/winget/

PowerShell7 のインストール

システム内蔵の Windows PowerShell を使用している場合は、新しい PowerShell7.x を利用するよう推奨してくるため、PowerShell のインストールを行います。
バージョン情報が7.xで正しく表示されれば、すでに新しい PowerShell を利用しています。

pwsh/powershell
pwsh --version

コマンドが見つからなった場合は下記のコマンドでインストールします。

powershell
winget install --id Microsoft.Powershell --source winget

インストール確認は下記のコマンドで実施します。

以降の PowerShell の操作は PowerShell7 を使うことを推奨します。

https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows?view=powershell-7.4#install-powershell-using-winget-recommended

winget によるインストール

pwsh
winget install -e --id Canonical.Multipass

Multipass のインストール確認は下記コマンドで実施します。
正しくインストールされている場合は、Multipass のバージョン情報が表示されます。

pwsh
multipass --version

winget によるアップデート

必要に応じて、Multipass のバージョン更新を実施します。

pwsh
winget upgrade -e --id Canonical.Multipass

マウント機能の有効化

On Windows mounts are disabled by default, as anyone with TCP access to localhost (127.0.0.1) can use Multipass, and by extension, gets access to the whole file system.

マウントとは、仮想環境のディレクトリに、ローカル環境のディレクトリを紐付けることで、仮想環境からローカル環境上のファイルをアクセス可能にし、ファイルの変更を両環境で同期する機能です。

公式記事によると Windows 環境で mount 機能の有効化は localhost からローカル環境のファイルシステム全体にアクセスできてしまうリスクがあります。
この点について注意をしてマウント機能の有効化を実施して下さい。有効化後は Multipass のサービスを再起動して下さい。
下記コマンドは PowerShell を管理者として実行して下さい。

pwsh(Administrator)
# 確認
multipass get local.privileged-mounts

# 変更
multipass set local.privileged-mounts=Yes

# 変更確認
multipass get local.privileged-mounts

# サービス再起動
Restart-Service -Name multipass

https://multipass.run/docs/privileged-mounts#heading--key

VirtualBox をドライバとする場合の設定変更

VirtualBox を使う場合は Hyper-V がデフォルトのドライバとなっているので変更が必要です。

pwsh
# 確認
multipass get local.driver

# 変更
multipass set local.driver=virtualbox

# 変更確認
multipass get local.driver

Hyper-V に戻す場合は下記のコマンドを実施して下さい。

pwsh
# 確認
multipass get local.driver

# 変更
multipass set local.driver=hyperv

# 変更確認
multipass get local.driver

https://multipass.run/docs/installing-on-windows#heading--run

winget によるアンインストール

pwsh
winget uninstall multipass

(参考)Windows 著者環境

カテゴリ 内容
OS Windows11 Pro
チップ Intel i5-12600KF
メモリ 32GB
ストレージ 1TB
Multipass 1.13.1+win

Linux 環境向けインストール手順

基本は下記の公式ドキュメントを参照して下さい。ハマりポイントなどがあれば後から追記しようかと思います。

https://multipass.run/docs/installing-on-linux

ワンライナーで仮想環境作成

デフォルト仮想環境 primary の作成

とにかく細かい設定は良いので、すぐに Ubuntu の仮想環境が欲しい場合は、下記のコマンドで Multipass で予約されているデフォルト仮想環境 primary を作成可能です。
このコマンドで仮想環境の作成終了後すぐに、デフォルトユーザーubuntuとしてshell ログインできます。

zsh/bash/pwsh
multipass shell

shell ログインできたら、lsコマンドを実行してみるとディレクトリHomeが作成されています。
cd Homeをすると、 なんとローカル環境のホームディレクトリに移動でき、ローカル環境のファイルにアクセスできる状態になっています。

bash
ubuntu@primary:~$ ls
Home  snap
ubuntu@primary:~$ cd Home
ubuntu@primary:~/Home$ ls
AWSCLIV2.pkg  Desktop    Downloads  Movies  Pictures  config  python3
Applications  Documents  Library    Music   Public    python

仮想環境を出るにはexitを実行します。

bash
ubuntu@primary:~$ exit
logout
multipass shell  0.10s user 0.07s system 0% cpu 10:08.33 total

仮想環境の情報について確認

multipass infoで仮想環境の情報を確認できます。複数の環境が作成されている場合は、その環境情報についても表示されます。

zsh/bash/pwsh
multipass info
Name:           primary
State:          Running
Snapshots:      0
IPv4:           192.168.64.230
Release:        Ubuntu 22.04.4 LTS
Image hash:     edba2f9aad6f (Ubuntu 22.04 LTS)
CPU(s):         1
Load:           0.00 0.00 0.00
Disk usage:     1.6GiB out of 4.8GiB
Memory usage:   149.0MiB out of 962.4MiB
Mounts:         /Users/<user name> => Home
                    UID map: 501:default
                    GID map: 20:default

デフォルト仮想環境 primary は、CPU: 1、ディスクサイズ: 5GB、メモリ: 1GB の設定でリソースが割り当てられたことが確認できます。
Mounts:を見ると、ローカル環境のホームディレクトリが仮想環境上のHomeディレクトリに対応づいていることが確認できます。このために、仮想環境上からローカル環境のファイルにアクセスが可能になっていました。

環境名を指定して情報を取得する場合は下記のコマンドで実施します。

zsh/bash/pwsh
multipass info primary
# multipass info <仮想環境名>

仮想環境の起動・停止・再起動

起動

zsh/bash/pwsh
multipass start primary
# multipass start <仮想環境名>

停止

zsh/bash/pwsh
multipass stop primary
# multipass stop <仮想環境名>
bash
ubuntu@primary:~$ sudo shutdown now

再起動

zsh/bash/pwsh
multipass restart primary
# multipass restart <仮想環境名>
bash
ubuntu@primary:~$ sudo reboot

仮想環境の削除

削除

zsh/bash/pwsh
multipass delete primary
# multipass delete <仮想環境名>

起動中の環境に対して、deleteコマンド実行した場合は、自動で仮想環境を停止してから削除が実施されます。

復旧

deleteコマンドは完全に環境を削除しないため、下記コマンドから復旧することも可能です。

zsh/bash/pwsh
multipass recover primary
# multipass recover <仮想環境名>

破棄

完全に削除を実施するには、delete後にpurgeを実施してください。

zsh/bash/pwsh
multipass purge

対象の環境について一発で完全に削除を実施するにはdeleteコマンドに-pまたは--purgeオプションを付与して実施してください。[2]

zsh/bash/pwsh
multipass delete primary -p
# multipass delete <仮想環境名> -p

オプションを設定して仮想環境作成

前節では、最もシンプルな方法で仮想環境の作成を実施しました。
本節では、OSイメージやリソース設定などをした上で、仮想環境を作成する方法を紹介します。

利用可能な Ubuntu ベースイメージを確認

前節で デフォルト環境 primary を作成した時は、自動的に 22.04 LTS のイメージを使用していましたが、findコマンドで使用可能なイメージ一覧を確認できます。

通常のイメージ以外にも、jellyfindockerminikubeがすでに導入済みのイメージを選択することも可能です。[3]

zsh/bash/pwsh
multipass find
Image                       Aliases           Version          Description
20.04                       focal             20240220         Ubuntu 20.04 LTS
22.04                       jammy,lts         20240221         Ubuntu 22.04 LTS
23.10                       mantic            20240209         Ubuntu 23.10

Blueprint                   Aliases           Version          Description
anbox-cloud-appliance                         latest           Anbox Cloud Appliance
charm-dev                                     latest           A development and testing environment for charmers
docker                                        0.4              A Docker environment with Portainer and related tools
jellyfin                                      latest           Jellyfin is a Free Software Media System that puts you in control of managing and streaming your media.
minikube                                      latest           minikube is local Kubernetes
ros-noetic                                    0.1              A development and testing environment for ROS Noetic.
ros2-humble                                   0.1              A development and testing environment for ROS 2 Humble.

リソースの割り当てを設定して仮想環境の立ち上げ

仮想環境へのcpuやメモリ、ディスクのリソース割り当てを設定して仮想環境を作成します。
下記のコマンドでは、CPU コアが2つ、メモリが 2[GByte]、ディスクサイズが 10[GByte] を割り当てた 22.04 イメージをベースとする仮想環境を作成します。
仮想環境名(インスタンス名)は--nameオプションで hoge としており、この名前がホスト名にもなります。

zsh/bash/pwsh
multipass launch --cpus 2 --memory 2G --disk 10G --name hoge 22.04
# multipass launch --cpus <割り当てるCPU数> --memory <割り当てるメモリ数[GByte]>G --disk <割り当てるディスクサイズ>G --name <仮想環境名> <イメージタグ>

作成が完了したら、下記コマンドでshellログインできます。

zsh/bash/pwsh
multipass shell hoge
# multipass shell <仮想環境名>

ローカル環境ディレクトリをマウント

前説のデフォルト環境 primary では、ローカル環境のホームディレクトリが仮想環境上のHomeに紐付けられており、ローカル環境のファイルにアクセスできる状態になっていました。

しかし、launchコマンドにより独自で作成した環境では特別な設定がされていないため、環境作成とは別途でマウント作業を実施します。

仮想環境上で、マウント先のディレクトリを作成します。ディレクトリは任意の名前で良いですが今回はsync_hogeという名前にします。

ディレクトリの作成後、sync_hogeまでのフルパスを控えておきます。

bash
ubuntu@hoge:~$ mkdir sync_hoge
ubuntu@hoge:~$ cd sync_hoge
ubuntu@hoge:~/sync_hoge$ pwd
/home/ubuntu/sync_hoge
ubuntu@hoge:~$ exit

仮想環境上で、マウント先のディレクトリを作成後下記のmountコマンドで、任意のローカル環境のディレクトリをマウントできます。

zsh/bash/pwsh
multipass mount /Users/<user_name>/Desktop/workspace hoge:/home/ubuntu/sync_hoge
# multipass mount <ローカル環境の任意ディレクトリまでのフルパス> <仮想環境名>:<マウント先ディレクトrまでのフルパス>
## windows はローカル環境のパスの区切りを\で表記するように注意

ローカル環境上の現在のディレクトリをマウントしたいのであれば下記が便利です。

zsh/bash
multipass mount $(pwd) hoge:/home/ubuntu/sync_hoge
pwsh
multipass mount (Get-Location).Path hoge:/home/ubuntu/sync_hoge
macOS でマウントに失敗する場合のトラブルシュート

マウント機能を利用するためには、仮想環境から Mac 上のディスク(ディレクトリ)にアクセスを許可する必要がある場合があります。(OSバージョンの問題なのか、フルディスクアクセスの一覧にmultipassdが表示されないケースもあるようです。)
下記手順の実施してください。

  1. ディスクトップ画面左上🍎アイコンをクリック
  2. 「システム設定」をクリック
  3. 「プライバシーとセキュリティ」の一覧から「フルディスクアクセス」を選択
  4. 「フルディスクアクセス」の一覧から multipassd を有効化

mac-privacy-security
3. 「プライバシーとセキュリティ」の一覧から「フルディスクアクセス」を選択

mac-full-disk-access
4. 「フルディスクアクセス」の一覧から multipassd を有効化

マウントの状況はmultipass info hogeから確認できます。

zsh/bash/pwsh
multipass info hoge
Name:           hoge
State:          Running
Snapshots:      0
IPv4:           192.168.64.231
Release:        Ubuntu 22.04.4 LTS
Image hash:     edba2f9aad6f (Ubuntu 22.04 LTS)
CPU(s):         2
Load:           0.00 0.00 0.00
Disk usage:     1.6GiB out of 9.6GiB
Memory usage:   162.4MiB out of 1.9GiB
Mounts:         /Users/<user_name>/Desktop/workspace => /home/ubuntu/sync_hoge
                    UID map: 501:default
                    GID map: 20:default

対象環境についてマウントを解除するには下記のコマンドを実施します。

zsh/bash/pwsh
multipass umount hoge
# multipass umount <仮想環境名>

multipass の紹介できなかったコマンドについては公式ドキュメントを参照してください。

https://multipass.run/docs

Docker インストール

Docker のインストールは公式ぺージの手順を参考に導入をすれば良いのですが、しばしばこの手順が変わるため、コマンドを直接実施する方法ではなく、convenience scriptを利用した方法によるインストールを行います。[4] versionオプションでインストールしたいdockerのバージョンを指定できます。
Docker バージョンは下記リンク先のリリースタグから確認できます。

https://github.com/moby/moby/releases

bash
ubuntu@hoge:~$ curl -fsSL https://get.docker.com -o install-docker.sh
ubuntu@hoge:~$ bash install-docker.sh --version 26.0 --dry-run
ubuntu@hoge:~$ bash install-docker.sh --version 26.0

https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository

sudoなしでdockerを実行するために、ubuntuユーザーをdockerグループに追加します。

bash
ubuntu@hoge:~$ sudo groupadd docker
ubuntu@hoge:~$ sudo gpasswd -a $USER docker
ubuntu@hoge:~$ exit

再度仮想環境にログインし、下記コマンドで実行確認ができます。

bash
ubuntu@hoge:~$ docker run hello-world

ssh 接続設定

multipass shell hogeで仮想環境へのログインはできるようになりましたが、仮想環境のファイル操作をすべて CLI で実施するのは大変なため、GUI を用いる方が作業効率が良いです。
このため、仮想環境上のファイル操作を GUI で行うために VSCode 拡張の Remote-SSH を利用します。

https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-ssh

また、特別な設定をしない限り、仮想環境へのパスワード認証による ssh ログインはできないように設定されているため、鍵情報を使った ssh 接続の必要があります。

基本設定

VSCode の Remote-SSH は ~/.ssh/configを参照して仮想環境への接続を行うためconfigファイルに設定を記載しておく必要があります。この際、~/.ssh下に鍵を配置してconfigに設定をすべて記述しても良いのですが、仮想環境毎に鍵と設定ファイルを一元的に管理できた方が便利です。[5] このため、仮想環境名のディレクトリを作成し、そのディレクトリ下に設定ファイルと鍵を配置します。~/ssh/configへはIncludeを利用して設定ファイルを参照するようにします。

zsh/bash
# 仮想環境の名前を設定
INSTANCE_NAME="hoge"

# ssh 接続設定用に仮想環境名ディレクトリを切る
WORKSPACE="${HOME}/.ssh/multipass/${INSTANCE_NAME}"
mkdir -p "${WORKSPACE}"

# 仮想環境名の鍵生成
## パスフレーズは空
ssh-keygen -t ecdsa -b 521 -N "" -f "${WORKSPACE}/${INSTANCE_NAME}"

# config ファイル作成
touch "${WORKSPACE}/config"

# 公開鍵を仮想環境の authorized_keys に追加
## 下記コマンドでなくても multipass shell <仮想環境名> でログインして、 authorized_key に直接公開鍵を追加するでも OK
multipass exec hoge --working-directory "/home/ubuntu/.ssh" -- bash -c "echo '$(cat ${WORKSPACE}/${INSTANCE_NAME}.pub)' | tee -a authorized_keys"
# multipass exec <仮想環境名> --working-directory <コマンド実行ディレクトリ> -- <実行コマンド>
pwsh
# 仮想環境の名前を設定
$INSTANCE_NAME="hoge"

# ssh 接続設定用に仮想環境名ディレクトリを切る
$WORKSPACE="$HOME\.ssh\multipass\$INSTANCE_NAME"
New-Item -ItemType Directory -Force -Path "$WORKSPACE"

# 仮想環境名の鍵生成
## パスフレーズは空
ssh-keygen -t ecdsa -b 521 -N "" -f "$WORKSPACE\$INSTANCE_NAME"

# config ファイル作成
New-Item -ItemType File -Path "$WORKSPACE\config"

# 公開鍵を仮想環境の authorized_keys に追加
## 下記コマンドでなくても multipass shell <仮想環境名> でログインして、 authorized_key に直接公開鍵を追加するでも OK
multipass exec hoge --working-directory "/home/ubuntu/.ssh" -- bash -c "echo '$(Get-Content $WORKSPACE\$INSTANCE_NAME.pub)' | tee -a authorized_keys"

~/.ssh/multipass/hoge/configおよび~/.ssh/configファイルについて適当なテキストエディタで下記の設定を追加して下さい。

~/.ssh/multipass/hoge/config
Host hoge
    HostName <仮想環境のIPアドレス>
    User ubuntu
    IdentityFile ~/.ssh/multipass/hoge/hoge
    IdentitiesOnly yes
    ServerAliveInterval 60
~/.ssh/config
Include ~/.ssh/multipass/hoge/config

HostName の<仮想環境のIPアドレス>部分はmultipass info hogeIPv4:のアドレスを記載して下さい。

ssh ログインで仮想環境に入れることが確認できたら、ssh 接続の設定は完了です。

zsh/bash/pwsh
ssh hoge
The authenticity of host '192.168.64.231 (192.168.64.231)' can't be established.
ED25519 key fingerprint is SHA256:Vt7KevcPFvk4QzGXMge+Fmae16nLmjGk5U40iWYlBYQ.
This key is not known by any other names.
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes
Warning: Permanently added '192.168.64.231' (ED25519) to the list of known hosts.

Avahi を使って仮想環境の接続先を固定化

Multipass は、仮想環境を立てるたびに、IP アドレスが自動で割り当てられるため、同じホスト名のインスタンスを再度立て直した時に、仮想環境の ssh 接続設定のHostNameを編集し直す必要があります。特に、Windows 環境では PC の再起動のたびに IP アドレスが変わってしまいます。

https://hnakamur.github.io/blog/2019/10/29/static-ip-address-with-hyper-v-nat/

Multipass の公式ドキュメントでは、IPの固定化をするためにブリッジを手動で作成する方法が紹介されていますが、手順が各々のホスト環境依存になってしまう点が気に入りません。

https://multipass.run/docs/configure-static-ips

これらの問題を解決してくれるのが、Avahi です。

https://qiita.com/fecker/items/2c602a9913e23bf67dac
https://wiki.archlinux.jp/index.php/Avahi

仮想環境上でavahi-deamonを動かしておくだけで、<仮想環境のホスト名>.localの形式でローカル環境におけるホスト名を解決してくれます。[6]

avahi-deamonのインストール方法は下記で実施します。
systemctl statusでステータスがactiveになっていれば、デーモンが起動しています。

bash
ubuntu@hoge:~$ sudo apt-get update
ubuntu@hoge:~$ sudo apt-get install avahi-daemon
ubuntu@hoge:~$ sudo systemctl status avahi-daemon

Avahiが有効化されたので、HostNameを修正します。

~/.ssh/multipass/hoge/config
Host hoge
-   HostName <仮想環境のIPアドレス>
+   HostName hoge.local
    User ubuntu
    IdentityFile ~/.ssh/multipass/hoge/hoge
    IdentitiesOnly yes
    ServerAliveInterval 60

以上で、接続先を擬似的に固定化できました。

VSCode Remote-SSH で接続

ssh 接続設定が完了したので、Remote-SSH を利用して仮想環境への接続を行います。
以降は VSCode 上の操作となり、Remote-SSH がすでに導入されていることを前提とします。
もし、導入がまだの方は VSCode の Extensions タブから検索してインストールを実施して下さい。

  1. 緑の矢印の先のアイコン><をクリック
    vscode-remote-ssh-start
    Remote-SSH を起動

  2. コマンドパレットで、仮想環境のホスト名を選択して、仮想環境に接続します。
    vscode-remote-ssh-select
    接続先の選択

  3. 「Open Folder」ボタンから VSCode で開きたい仮想環境上のディレクトリ名を指定します。
    vscode-remote-ssh-open
    仮想環境にアタッチされた VSCode

以上で、作成した仮想環境に Remote-SSH でアクセスできました。

ポートフォワーディング

仮想環境上で立ち上げた Web サーバーにローカル環境のブラウザからアクセスするためには、仮想環境上のポートにローカル環境のポートで繋ぐ、ポートフォワーディングを行う必要があります。

ターミナルから ssh 接続する場合

ssh 接続時にポートフォワーディングするためには、下記のように ssh 接続の設定ファイルを書き換えます。

~/.ssh/multipass/hoge/config
Host hoge
    HostName hoge.local
    User ubuntu
    IdentityFile ~/.ssh/multipass/hoge/hoge
    IdentitiesOnly yes
    ServerAliveInterval 60
+   LocalForward 8080 127.0.0.1:8080

この設定は、LocalForward 直後の8080が仮想環境の Web サーバーが使用する予定のポートであり、そのポートをクライアントであるローカル環境127.0.0.18080ポートへ繋ぎ込むことを表します。

設定が完了したら、仮想環境に ssh ログインします。ssh 接続中はポートフォワーディングが有効になります。

zsh/bash/pwsh
ssh hoge

仮想環境の Ubuntu 22.04 にはデフォルトで python 3.10.12 が導入されていますので、こちらを利用して接続テスト用の Webサーバーを立ち上げます。Web サーバーのポートは設定ファイルと同じ8080にします。

bash
ubuntu@hoge:~$ python3 --version
Python 3.10.12
ubuntu@hoge:~$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

ページを確認するためローカル環境で、Chrome などのブラウザを立ち上げて検索バーに127.0.0.1:8080またはlocalhost:8080と入力します。

下記のように仮想環境のホームディレクトリの一覧が表示されていれば正しく接続できています。

ポートフォワーディングの接続テスト
ポートフォワーディングによる仮想環境の Web サーバーへの接続テスト

ssh 接続を終了すると、ポートフォワーディングが解除されるため、アクセスできなくなります。

VSCode Remote-SSH を使う場合

VSCode Remote-SSH で接続の手順から、仮想環境へ接続を行い。python から 接続テスト用の Web サーバーを下記のように起動します。

bash
ubuntu@hoge:~$ python3 -m http.server 8080
Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

VSCode では、ポート指定のアプリケーションを起動すると、自動的にローカル環境へポートの繋ぎこみをやってくれます。ローカル環境のどのポートに繋ぎ込まれたかは、VSCode の画面の Ports(差し込みプラグのアイコン) のタブから確認できます。

ssh 接続設定で LocalForward を設定していない場合
ssh 接続設定で LocalForward を設定していない場合

また、すでにローカル環境側で使用しているポート、または ssh 接続設定で、常にバインドされるポートが指定されている)がある場合には、利用されていないポート(今回であれば8081)を VSCode が自動で指定してくれます。今回のケースでは、localhost:8080またはlocalhost:8081で ページにアクセスできます。

ssh 接続設定で LocalForward 8080 127.0.0.1:8080 で設定されていた場合
ssh 接続設定で LocalForward 8080 127.0.0.1:8080 で設定されていた場合

https://code.visualstudio.com/docs/remote/ssh#_always-forwarding-a-port

ポートの欄をクリックすると、下記のように Forwarded Address のページをすぐ確認するための便利なボタンが配置されています。とても便利なので活用しましょう。

Forwarded Ad...の 左: クリップボードへコピー、中央: ブラウザ起動、右: VSCode 内で対象ページ起動
Forwarded Ad...の 左: クリップボードへコピー、中央: ブラウザ起動、右: VSCode 内で対象ページ起動

以上で仮想環境上の Web サーバーへアクセスするためのポートフォワーディングの設定について紹介しました。

Cloud-init による環境設定の自動化

ここまでで、Vscode Remote-SSH 可能な Docker 環境を構築できることは分かりましたが、新しく環境を作成する毎に DockerAvahi のインストール、公開鍵の追加作業を実施するのは大変です。

Multipass では Cloud-init という yaml 形式の複数 Linux ディストリビューションに対応したクロスプラットフォームなインスタンスの初期化方法を利用することができます。これにより下記のような作業を環境初期化時に実施可能です。

  • 仮想環境のホスト名の設定
  • ロケールの設定
  • タイムゾーンの設定
  • ssh 接続におけるパスワード認証の禁止
  • パッケージのアップデート & アップグレード
  • snap パッケージのインストール
  • ユーザーの作成
  • ファイルの作成 & 追記
  • コマンドの実施

下記に Multipass の仮想環境作成で有用と思われる Cloud-init のテンプレートを示します。

前提としてマウント先のsync_hogeディレクトリには任意の Monorepository がマウントされることを想定しています。つまり、 Polyrepo のために複数のマウントポイント(マウント先ディレクトリ)を事前に用意していません。

hoge 環境の hoge ユーザーで作成する場合であれば、<YOUR SSH PUBLIC KEY>部分を~/.ssh/multipass/hoge/hoge.pubの公開鍵の内容で書き換えるだけで hoge.yml は動作します。

hoge.yml
#cloud-config
hostname: hoge # この仮想環境のホスト名を "hoge" に設定
locale: en_US.UTF-8 # システムロケールを英語(us)に設定
timezone: Asia/Tokyo # システム時刻を日本時間に設定 UTC+9:00
ssh_pwauth: no # パスワード認証による ssh ログインを禁止
package_update: true # パッケージインストール前にパッケージリポジトリを更新
package_upgrade: true # パッケージインストール前に既存パッケージを更新
package_reboot_if_required: true # 再起動が必要なパッケージがある場合に再起動を実施

# apt パッケージをインストール
packages:
  - sshfs # ssh 接続でディレクトリマウントするためのパッケージ
  - curl # http リクエスト飛ばせる定番コマンド
  - ca-certificates # 基本的なCA認証を取得するパッケージ
  - vim # Let me out !!! のミームでお馴染みの CLI テキストエディタ
  - nano # vim が苦手な人向けのシンプルなテキストエディタ
  - jq # json ファイルのパーサー
  - tree # ディレクトリやファイルをツリー表示するコマンド
  - git # ソースコードのバージョン管理
  - zstd # zstandard 形式の圧縮コマンド
  - avahi-daemon # ローカル環境のホスト名解決

# snap パッケージのインストール
snap:
  commands:
    - snap install yq --classic # yaml ファイルのパーサー

# ユーザー作成
users:
  - name: hoge # 新規作成するユーザー名の設定
    lock_passwd: true # パスワードによるログインを禁止
    sudo: ALL=(ALL) NOPASSWD:ALL # sudo 実行の際にパスワード入力を不要化
    shell: /bin/bash # デフォルトで使用する shell を設定
    ssh-authorized-keys: # ssh 接続時に使用する、公開鍵を設定
      - <YOUR SSH PUBLIC KEY> # ~/.ssh/multipass/hoge/hoge.pub を記載
    groups: # docker コマンド実施の際に sudo を不要にするため docker グループへ追加
      - docker

# ユーザーのパスワード設定
chpasswd:
  list: | # ユーザー "hoge" にパスワードを "huga" を設定
    hoge:huga
  expire: false # パスワードに期限を設定しない

# ファイルの作成 & 追記
write_files:
  # マウント先 の git リポジトリが安全であることを追加
  - path: /home/hoge/.gitconfig # ファイルのパス
    owner: hoge:hoge # ファイルの権限設定 ユーザー:グループ
    append: true # 追記可能
    defer: true # ユーザー作成が終わるまでこのファイル作成を遅延
    content: | # ファイル内容
      [safe]
        directory = /home/hoge/sync_hoge
  # インタラクティブモードで bash を開いた際の処理を追加
  - path: /home/hoge/.bashrc
    owner: hoge:hoge
    append: true
    defer: true
    content: |
      # マウントディレクトリの環境変数を設定
      readonly SYNC_DIR=/home/hoge/sync_hoge

      # docker compose のエイリアスを設定(SYNC_DIR下に compose.yml があることを想定)
      alias cdsync='cd ${SYNC_DIR}'
      alias dcu='pushd ${SYNC_DIR}; docker compose up -d; popd'
      alias dcd='pushd ${SYNC_DIR}; docker compose down; popd'
      alias dcs='pushd ${SYNC_DIR}; docker compose stop; popd'
      alias dcr='pushd ${SYNC_DIR}; docker compose restart; popd'
      alias dcrmia='pushd ${SYNC_DIR}; docker compose down --rmi all --volumes --remove-orphans; popd'

      # Remote-SSH ログイン時に vscode 拡張を自動インストール
      if [[ -e "$(which code)" ]] && [[ -d "/home/hoge/.enable_install_vscode_extensions" ]]; then
        xargs -I {} code --install-extension {} --force < /home/hoge/vscode_extensions.txt
        rm -rf /home/hoge/.enable_install_vscode_extensions
      fi
      [[ "${TERM_PROGRAM}" != "vscode" ]] && rm -rf /home/hoge/.enable_install_vscode_extensions
  # ssh ログイン時の処理を追加
  - path: /home/hoge/.profile
    owner: hoge:hoge
    append: true
    defer: true
    content: | # vscode 拡張のインストールを有効化するディレクトリを作成
      mkdir -p /home/hoge/.enable_install_vscode_extensions
  # 自動インストールする vscode 拡張の ID 一覧
  - path: /home/hoge/vscode_extensions.txt
    owner: hoge:hoge
    defer: true
    content: |
      mosapride.zenkaku
      ms-azuretools.vscode-docker
      oderwat.indent-rainbow
      shardulm94.trailing-spaces
      streetsidesoftware.code-spell-checker

# コマンド実行(すべての初期化処理の最後に実行)
runcmd:
  # マウント先のディレクトリを "hoge" ユーザーの権限で作成
  - su -c "mkdir /home/hoge/sync_hoge" - hoge
  # docker インストール
  - curl --proto '=https' --tlsv1.2 -sSfL https://get.docker.com | bash -s -- --version 26.0
  # act インストール
  - curl --proto '=https' --tlsv1.2 -sSf https://raw.githubusercontent.com/nektos/act/master/install.sh | bash -s -- -b /usr/local/bin v0.2.60

この Cloud-init のテンプレートは、メタ文字列hogehugaを任意の文字列に適宜置換頂き、apt や snap パッケージおよび VSCode の拡張を各自お好みで削除・追加してご利用ください。

Cloud-init の詳細は公式ドキュメントを参照して下さい。

https://cloudinit.readthedocs.io/en/latest/reference/index.html

Cloud-init を利用した、仮想環境の作成は下記コマンドで実施します。

zsh/bash/pwsh
# 既存の hoge 環境が残っている場合は削除してから作成を実施して下さい
# multipass delete hoge -p
multipass launch --cpus 2 --disk 16G --memory 2G --cloud-init hoge.yml --name hoge --timeout 1800 22.04

また、ユーザー作成でユーザー名をhogeに指定した場合は、ssh 接続のログインユーザーを変更して下さい。

~/.ssh/multipass/hoge/config
Host hoge
    HostName hoge.local
-   User ubuntu
+   User hoge
    IdentityFile ~/.ssh/multipass/hoge/hoge
    IdentitiesOnly yes
    ServerAliveInterval 60

以上で Cloud-init による環境設定の自動化を行い、仮想環境の作成ができました。

Cloud-init では自動化できないこと

Multipass は Cloud-init を使用することで、環境作成に必要な多くの作業を自動化を達成しましたが、例えば下記の作業が手作業として残ってしまっています。

  • ssh 接続のための鍵の作成および設定ファイルの作成
  • 公開鍵の Cloud-init ファイルへの記載
  • 作成ユーザー名に応じた、Cloud-init ファイルの各種パスの変更
  • Docker や act の動的なインストールバージョン変更
  • ディレクトリマウント
  • (VSCodeを使わないケースは必須)ssh 設定へ LocalForward の設定追加・変更

このような Cloud-init でカバーできない作業(特に、Cloud-init 自身の内容変更)の自動化については、shell スクリプトなどを活用して自動化するべきですが、より進んだ内容になるため別の記事としてご紹介できればと思います。

おわりに

本記事では、Multipass を利用して Ubuntu の仮想環境上に Docker 環境を構築する方法について紹介しました。

Multipass については、導入から基本的な使い方・Dockerの導入・作業の自動化までとかなり網羅的に解説しました。また、Multipass を Docker ホスト環境としてより便利に活用していくためのトピックとして、Include 句を使った ssh 設定・VSCode Remote-SSH・Avahi によるアドレスの固定化、ポートフォワーディングについて紹介しました。

かなり内容の多い記事になってしまいましたが、皆様がより良いローカル仮想環境構築する際の一資料としてご活用頂けますと幸いです。

謝辞

本記事について、公開前にレビュー・アドバイスしてくれた3名の友人に感謝!

  • takanari
  • lenhaba
  • akimotty
脚注
  1. Dockerは Linux のカーネルを使用して動いているので、Linux 環境上に導入してしまうのが都合が良いためです。 ↩︎

  2. 個人的には、後述する Cloud-init を利用して仮想環境作成の自動化ができていれば、環境の再作成は容易に可能なため、docker compose downぐらいの気持ちで、delete -pを使用しても問題ないと思います。ただし、誤削除には気をつけましょう。 ↩︎

  3. このような便利なイメージを利用することも可能ですが、OSバージョンを指定できない問題があります。個人的には、OSバージョンのイメージを選択して、Cloud-init に使用したいツールのインストール手順を記載しておくのが良い方法だと考えます。この方法は「Cloud-init を使った自動化」の章で紹介します。 ↩︎

  4. 公式ページにも記載がありますが、convenience script によるインストールは本番環境では使用してはいけません。 ↩︎

  5. ~/.ssh/configに設定を直接書かずにIncludeで参照することによって、対象の環境のIncludeの行を記載するか・削除するかだけで設定の ON/OFF ができるようになるので非常に便利です。仮想環境は他のサーバーへの接続設定と違ってライフサイクルが短く、頻繁に ssh の設定・削除が必要になるのでので、Includeを使うと作業が捗ります。 ↩︎

  6. 仮想環境のホスト名はmultipass launchコマンドの--nameオプションで与えた名前がホスト名になります。後述する Cloud-init を使えば自由にホスト名を設定可能です。 ↩︎

ぬまごたつ

Discussion