🤖

images.linuxcontainers.orgを利用して手軽に色々なWSL2ディストリビューションを作る

2024/11/21に公開

(追記 2024-11-22)cloud-initを使ったプロビジョニング方法について追記

WSL2ディストリビューションに関して、
Microsoft Storeからインストールできるものや、あるいは
wsl --list --onlineで表示されるリスト以外のディストリビューションをインストールしたくなったり、
あるいは同じ種類のディストリビューションを複数個インストールしたくなったりすることがある(目的別にUbuntu24.04を2つ並用する、とか)。

というときの選択肢を探ってみる。

方法

https://learn.microsoft.com/ja-jp/windows/wsl/use-custom-distro

に記載されているように、wsl --importコマンドでLinuxのコンテナの中身を含むtarファイルを取り込むことで任意のLinuxディストリビューションをWSL2に登録することができる。

上記のリンク先の解説ではDockerコンテナを使っているが、
linuxcontainers.orgで公開されているイメージ:

https://images.linuxcontainers.org/

をそのままwsl --importコマンドで取り込むことができ、即座に対応するWSL2ディストリビューションを作ることができる。

公開されているイメージは

  • ubuntu
  • debian
  • fedora
  • archlinux
  • alpine
  • almalinux
  • rockylinux
  • amazonlinux
  • (その他数え切れないくらい多数)

といった感じで、かなり多くの選択肢がある。
(もちろん無いものもあるにはあるが)

所望のイメージのページに行き、rootfs.tar.xzをダウンロードし、

powershell
wsl --import <登録するディストリビューション名> <仮想ディスクなどの保存場所のパス> <rootfs.tar.xzの保存パス>

のようにして、wsl --importの第3引数でダウンロードしたrootfs.tar.xzを指定して取り込むと、所望のWSL2ディストリビューションをインポート出来る。

手元ではとりあえずubuntu-24.10(oracular), fedora41, amazonlinux2023, archlinuxあたりをインポートしてみたが、どれも問題なく起動した。

(発展)cloud-initによるディストリビューションのセットアップ・プロビジョニング

全部のイメージがそうかまでは確認できていないが、見た範囲ではどのディストリビューションでもユーザーがrootしか存在しないため、
実用的にはユーザーの作成などを行う必要がある。
また、パッケージ・ライブラリの追加や設定ファイルの記入なども一般には必要になる。

ここで、WSL2ではcloud-init:

https://cloudinit.readthedocs.io/en/latest/reference/datasources/wsl.html

https://cloudinit.readthedocs.io/en/latest/tutorial/wsl.html

https://documentation.ubuntu.com/wsl/en/latest/tutorials/cloud-init/

が使えるため、cloud-initを使うことでWSL2の初期設定を自動化し、再現性の高いものにできる。

user-dataファイルの記入と配置

WSL2でcloud-initを実行するにあたり、事前に<WSL2ディストリビューション名>.user-dataという名前のyamlファイルを作成し、
Windows上の%USERPROFILE%\.cloud-init(%USERPROFILE%はWindowsにおけるホームディレクトリのようなもの)に格納しておく必要がある。

記入にあたり、詳細にはリファレンス:

https://cloudinit.readthedocs.io/en/latest/reference/modules.html#modules

および、設定例:

https://cloudinit.readthedocs.io/en/latest/reference/examples.html#yaml-examples

などを参考に作っていけば良い。

手元では↓のようなuser-dataを作成して動作確認した:

%USERPROFILE%/.cloud-init/<WSL2ディストリビューション名>.user-data
#cloud-config

# タイムゾーンとlocaleを設定↓
timezone: Asia/Tokyo
locale: en_US.utf8

# `wsl-usser`というUID=1000のユーザーを作成し、パスワード無しでsudoを使える設定にしておく
users:
- name: wsl-user
  groups: [adm, sudo, wheel]
  sudo:
    - ALL=(ALL) NOPASSWD:ALL
  uid: 1000
  # ↓深い意味は無いが、例としてデフォルトのシェルを後でインストールするfishに設定する
  shell: /usr/bin/fish

# ↓aptやdnf, pacmanなどでインストールするパッケージ名を指定
packages:
- tmux
- fish
- htop
- fzf
- ripgrep

# ↓自動でファイルを作成したり追記したりできる
write_files:
# /etc/wsl.confを記述することにより、systemdの有効化と、ログイン時のデフォルトユーザーをcloud-initで作成するものに設定している
- path: /etc/wsl.conf
  append: true
  content: |
    [boot]
    systemd=true
    [user]
    default=wsl-user

# ↓この例では意味のあることはしていないが、shellで実行できる操作を順次記述していける
runcmd:
- echo "cloud-init" >> /tmp/sample.txt

上記の内容で、執筆時点でのUbuntu-24.10, Fedora41, archlinuxで動作することを確認している。

※package名は同じものでもディストリビューションによって名称が違う、あるいはパッケージによって存在したりしなかったりする、ということがありうるので、常に異なるディストリビューションで使い回せるとは限らない。(が、差分を小さくできるのも事実だと思われる)

wsl2でのcloud-initの実行

記事の方法で記載したようなやり方でwsl.exe --importにより、適当なWSL2ディストリビューションを作成する。
ここで、インポートするイメージは"cloud"とついているものだと(全てではないようだが)多くのものが初めからcloud-initがインストール済みの状態になっており、オススメ。
もし入っていなければ、後で(詳細は未検証だが)おそらくはaptとかdnfなど適当なパッケージマネージャーでインストールしておけば良いと思われる。

次に作成したディストリビューションを起動する。例えば、

wsl.exe -d <WSL2ディストリビューション名> --cd ~

などとすると作成したディストリビューションに入ることができる。

その後、以下のようなスクリプトを作成し、実行する:

cloud-init.sh
#!/bin/bash

cloud-init init --local
cloud-init init
cloud-init modules --mode=config
cloud-init modules --mode=final
# wsl内のrootユーザーで実行
chmod +x /path/to/cloud-init.sh
/path/to/cloud-init.sh

こうすると、Windows側の%USERPROFILE%\.cloud-init\<ディストリビューション名>.user-dataの内容を元にプロビジョニングが実行される。
(ターミナル上でcloud-initの処理が実行されるのが確認できる)

最後に、wsl2をシャットダウン・再起動すればOK。

(なお、wsl --importで作成したディストリビューションではデフォルトではsystemdが有効でないため、systemctl enable cloud-init-***などをせずに手動でスクリプト実行をすることにしている。)

(WSL公式のUbuntu-24.04の場合)

https://cloudinit.readthedocs.io/en/latest/tutorial/wsl.html

にWSL公式版のUbuntu-24.04版での実行例が記述されているが、ここではwsl2でのcloud-initの実行で記載されている手順は書かれておらず、実際、%USERPROFILE%\.cloud-init\<ディストリビューション名>.user-dataを置いておきさえすれば、初回起動時に勝手にcloud-initが実行される。
すなわち、手順が減ってより楽にプロビジョニングの実行ができて便利である。

WSL公式版のUbuntu-24.04、というのはMicrosoft Storeからインストールしたもの(おそらく、cliでwsl --installでいれたUbuntuでも良いはず)、
および、Ubuntu image server:

https://cloud-images.ubuntu.com/wsl/

noble以下に置いてあるubuntu-noble-wsl-amd64-ubuntu24.04lts.rootfs.tar.gzを使って、

wsl.exe --import <wsl2ディストリビューション名> <インストールパス> <↑でダウンロードしたrootfs.tar.gzファイル>

のようにして作成したもの、のいずれかが該当する。

そのため、24.04以降のUbuntu LTS版を用途ごとに複数立ち上げたい、といった場合であれば、↑のUbuntu image serverで公開されているイメージを使ってwsl --importする方が便利かもしれない。

(補足)deprecatedなコマンドの移行

https://cloudinit.readthedocs.io/en/latest/reference/cli.html#single

によると、cloud-init initおよびcloud-init modulesの両コマンドはdeprecated扱いとなっており、将来的に動かなくなる可能性がある。
そのため、将来的にはこの記事で紹介した内容はアップデートを行う必要がでてくると思われる。

cloud-init singleコマンド:

https://cloudinit.readthedocs.io/en/latest/reference/cli.html#single

を使うことで、cloud-init initコマンドおよびcloud-init modulesコマンドを置き換えることができる。

使用例は

cloud-init single --file /path/to/user-data --name <モジュール名>

のような感じである。
手元で試した感じ、cloud-init singleを使う場合、Windows側の%USERPROFILE%\.cloud-init\<WSL2ディストリビューション名>.user-dataを自動認識するとは限らないようなので、user-dataのyamlファイルは適当な場所に置いて--fileオプションで明示的に指定する必要がある。(逆に、%USERPROFILE%\.cloud-init下に置かなくても良い)

<モジュール名>と書いた部分に具体的に何を入れるのかが分からず難しかったが、

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

の各項目のうち、Internal name: cc_***となっている部分の***が相当する。

すなわち、cc_runcmdであればruncmdcc_package_update_upgrade_installであればpackage_update_upgrade_installとなる。

なお、各モジュールはinit, config, finalのいずれかのフェーズで実行されるのが本来正しいため、今回のように手動で実行する場合に間違ったタイミングで行わないように注意する必要がある。

(参考)

https://cloudinit.readthedocs.io/en/latest/explanation/boot.html#boot-stages

https://docs.redhat.com/ja/documentation/red_hat_enterprise_linux/8/html/configuring_and_managing_cloud-init_for_rhel_8/cloud-init-modules-execute-in-phases_introduction-to-cloud-init#cloud-init-modules-execute-in-phases_introduction-to-cloud-init

全ディストリビューションで共通かは不明だが、/etc/cloud/cloud.cfgを参照するとどのモジュールが存在するか、どのタイミングなのかが分かるので参考にすると良い:

/etc/cloud/cloud.cfg(抜粋)
/etc/cloud/cloud.cfg(抜粋)
# The modules that run in the 'init' stage
cloud_init_modules:
  - seed_random
  - bootcmd
  - write_files
  - growpart
  - resizefs
  - disk_setup
  - mounts
  - set_hostname
  - update_hostname
  - update_etc_hosts
  - ca_certs
  - rsyslog
  - users_groups
  - ssh
  - set_passwords

# The modules that run in the 'config' stage
cloud_config_modules:
  - ssh_import_id
  - keyboard
  - locale
  - spacewalk
  - yum_add_repo
  - ntp
  - timezone
  - disable_ec2_metadata
  - runcmd

# The modules that run in the 'final' stage
cloud_final_modules:
  - package_update_upgrade_install
  - write_files_deferred
  - puppet
  - chef
  - ansible
  - mcollective
  - salt_minion
  - reset_rmc
  - scripts_vendor
  - scripts_per_once
  - scripts_per_boot
  - scripts_per_instance
  - scripts_user
  - ssh_authkey_fingerprints
  - keys_to_console
  - install_hotplug
  - phone_home
  - final_message
  - power_state_change

上記の各モジュールの中から、必要そうなものを選んで適切な順序で実行すれば良い。

今回の例だと、

cloud-init.sh
#!/bin/bash

# ↓user-dataのyamlファイルのパス。適宜編集するか、引数などで与えるようにする
USER_DATA_PATH=/path/to/user-data

# 必要なモジュールを順次実行(足りていないものがあれば適宜追記すること)

# init phase
cloud-init single --file "$USER_DATA_PATH" --name users_groups
cloud-init single --file "$USER_DATA_PATH" --name write_files

# config phase
cloud-init single --file "$USER_DATA_PATH" --name locale
cloud-init single --file "$USER_DATA_PATH" --name timezone

# final phase
cloud-init single --file "$USER_DATA_PATH" --name package_update_upgrade_install
# cloud-init single --file "$USER_DATA_PATH" --name runcmd

のような感じのスクリプト: cloud-init.shを作成し、bash /path/to/cloud-init.shのようにして実行すれば良い。
ただし、手元で試している感じだと何故かruncmdだけはうまく動いていないようだった。
(全モジュールについて試したわけでもないので、他にもこのままでは動かないものがあるかもしれない)

という感じでまだ動作面で課題が残っている手法になるので、こちらの方法は補足としておき、
deprecatedになっているcloud-init initおよびcloud-init modulesを使った方法は掲載したままにしておく。

参考

(検証環境)

  • Windows11(x86_64)
    • バージョン23H2 (OSビルド22631.4460)
  • wslバージョン:
wsl --version
WSL バージョン: 2.3.26.0
カーネル バージョン: 5.15.167.4-1
WSLg バージョン: 1.0.65
MSRDC バージョン: 1.2.5620
Direct3D バージョン: 1.611.1-81528511
DXCore バージョン: 10.0.26100.1-240331-1435.ge-release
Windows バージョン: 10.0.22631.4460

参考1 昔書いた記事(Dockerfileを使うやり方)

https://zenn.dev/junkor/articles/e2b8d7815bd44a

Dockerfileを使ってイメージ内容を自由にカスタマイズできる、という記事。
極めて高い自由度がメリットだが、一方で、Dockerで使われるベースイメージは一般にごく最低限のパッケージしか入っていないため、WSL2のような開発や普段使い?するためには色々と不足や差分があり、カスタマイズ(Dockerfileの記入)が大変なところがあった。
(あと、docker exportなどを行うのが面倒)

images.linuxcontainers.org で公開されているイメージはそのままwsl --importすることができ、
dockerイメージよりは最低限使いそうなパッケージが入っていることが多そう?なので、より手軽に色々なディストリビューションを導入できると思われる。

参考2 distod

https://github.com/nullpo-head/wsl-distrod

WSL2でsystemdを使えるようにする他、
WSL2にlinuxcontainers.orgで入手できるあらゆるイメージをWSL2にインストールできるツールらしい。
(まだwsl2で満足にsystemdが使えなかったときに、systemdを使う手段として使われていた気がする。)

前者の機能のためにこのツールを存在を知ったが、今回の記事の内容は後者の方の機能に着想を得ている。

(その他参考リンク)

https://zenn.dev/s_ryuuki/articles/46c6d9d8d34404

https://qiita.com/yo-yamada/items/ed44bc72260ad5787d84

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

GitHubで編集を提案

Discussion