Dockerを駆使して任意のWSL2 Distroを作成する
前にAmazonLinux2023をWSL2上で動かすを書いたときの流れで他のDistroも作ってみたくなったのでやってみたという感じ。
概要
基本的にはMicrosoftドキュメントのWSLで使用するLinuxディストリビューションをインポートする、特にCentOS 用の tar ファイルを取得する例を参考にすればほとんど自由に自分でカスタマイズしたWSL2ディストリビューションが作れそうなことが分かる。
すなわち、Dockerfileでいい感じにカスタマイズしたDockerコンテナを作成し、tarファイルにエクスポートする方法を試してみた。
今回の手法のメリット・デメリットなど
メリット:
- 採用するLinuxディストリビューションからインストールするパッケージ、
設定ファイルの内容といったところまでかなり自由度の高い調整が可能- Windowsストアから入手出来ないdistro(例えばFedoraなど)も使える
- Dockerfileによって生成を行うため、作成するWSLディストリビューション内容をgitなどで管理しやすく、
環境の複製や他人との共有が容易- いわゆるゴールデンイメージに近い概念のものが作れる
デメリット:
- それなりに知識やスキルが必要
- distroの生成に使う大元のDockerイメージはDockerコンテナ用のものであり、
入っているパッケージが非常に少なかったりコンテナ固有の設定がなされていたりと、
サーバーやデスクトップ用途で動かすものとは割と差異が大きい - WSL2と使用したいLinuxディストリビューションの知識、Dockerfileをそれなりに書ける技術あたりが求められる
- distroの生成に使う大元のDockerイメージはDockerコンテナ用のものであり、
実行
今回はとりあえずUbuntu, Fedora, Arch Linuxを試してみた。
0. 前提や共通事項
検証環境
- Fedora37@WSL2(Windows11)
- ちょうどこの記事で書いた手法で作ったWSLディストリビューション
- Docker version 23.0.1, build a5ee5b1
- WSL バージョン: 1.1.3.0
といった感じ。
各Distro生成時の共通事項
まず、
- 以下のようなデフォルトユーザーを設定:
- ユーザー名:
wsl-user
- ログインパスワードの初期値は
password
- UID: 1000
- 起動時のシェルは
/bin/bash
- sudoが使え、rootへの権限昇格が可能
- ユーザー名:
-
systemd
を有効にしておく
といった感じ。
また、各distroの生成は以下のようなディレクトリ構成で行った:
$ tree
.
├── Dockerfile
└── wsl.conf
上記の構成で、
docker build -t <適当なタグ名> .
とすることでイメージを作成し、そのイメージから立ち上げたコンテナを元にtarファイルのエクスポートとwslディストリビューションのインポートを行う。
Dockerfile
の中身は各Distro毎にそれぞれ別途記載する。
wsl.conf
は今回は以下のようにした:
[user]
default = wsl-user
[boot]
systemd = true
詳細な設定内容はMicrosoftドキュメント - WSLでの詳細設定の構成を参照のこと。
今回は最低限として、
- wsl起動時のデフォルトユーザーを今回設定するユーザー名:
wsl-user
にしている - systemdの有効化
を設定している。
これ以外にも実際には、例えば.bashrc
などの設定ファイルを追加してCOPY
によってコンテナに追加する、といったカスタマイズが考えられる。
なお、コンテナイメージのtarファイルへのエクスポートおよび、
tarファイルをインポートしてWSLディストリビューションを生成する方法は公式ドキュメントに詳しく記載されているため、ここでは省略する。
1. Ubuntu22.10(kinetic)
Ubuntu自体はWindowsストアでも配布されており、22.10を使うにも22.04からアップデートするなどのやり方はある。
が、カスタマイズすることや再生成しやすい形で作っておくこと自体にも意味があると思うので一応やってみた。
Dockerfileは次のような感じ:
FROM docker.io/library/ubuntu:kinetic
USER root
ARG TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
# コンテナで動かすのに特有?の設定が追加されているので消しておく
RUN rm /etc/apt/apt.conf.d/docker-*
RUN apt-get -y update && \
yes | unminimize && \
apt-get install -y --no-install-recommends \
command-not-found \
man-db man \
nano vim \
git \
curl wget \
sudo && \
# Cleanup \
apt-get -y autoremove && \
apt-get clean && \
rm -rf /var/lib/apt/lists/*
# create user
ARG USER_NAME=wsl-user
ARG USER_UID=1000
ARG PASSWD=password
RUN useradd -m -s /bin/zsh -u ${USER_UID} ${USER_NAME} && \
echo "${USER_NAME}:${PASSWD}" | chpasswd && \
echo "${USER_NAME} ALL=(ALL) ALL" >> /etc/sudoers
# WSL2 setting
COPY wsl.conf /etc/wsl.conf
# (補足)例えば作成済みの設定ファイルを追加する場合は以下のような感じ
# COPY --chown=${USER_NAME}:${USER_NAME} .bashrc /home/${USER_NAME}/.bashrc
USER ${USER_NAME}
WORKDIR /home/${USER_NAME}
以下補足:
インストールしているパッケージ: command-not-found
は存在しないコマンドを打ったときに、インストールする候補や、typoが疑われる場合は正しいコマンドをサジェストしてくれるもの。
後で足りないコマンドを追加していくときに便利なので入れておいている。
次に、
ARG TZ=UTC
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
部分について、この設定をいれておかないと
のような感じで固まってしまうことがあるため追加している。
yes | unminimize
部分について、Ubuntuはunminimize
というコマンドがあり、サーバーとして動かすのに最低限?のパッケージ追加や設定変更などを行ってくれる様子
あと、これをやらないとコンテナ仕様のためにman
, man-db
をインストールしても各コマンドのマニュアルデータがダウンロードされず
man <コマンド>
をしても何も表示されなくなってしまう。
参考:
また、
RUN useradd -m -s /bin/bash -u ${USER_UID} ${USER_NAME} && \
echo "${USER_NAME}:${PASSWD}" | chpasswd && \
echo "${USER_NAME} ALL=(ALL) ALL" >> /etc/sudoers
部分について、useradd
コマンドでユーザーの作成、
chpasswd
コマンドでログインパスワードの設定を行っている。
また、/etc/sudoers
への追記でsudoを使えるように設定している。
WSLで使う分にはパスワードの入力は面倒なのでパスしたい、とう場合はALL=(ALL)
部分をALL=NOPASSWD:
に置き換えると、sudoを使うときにパスワード入力が不要になる。
実際に作り込んでいくとこれら以外にも色々あったりするが、用途次第な部分が大きいので目的に合わせてDockerfileに色々追記したりCOPYで追加するファイルを増やしていってカスタマイズしていく。
2. Fedora37
Windowsストアで普通に入れることは出来ず、かつ開発用途には結構使いやすい点で今回の手法が向いているような気がする。
なるべくミニマルなものを作るとして、Dockerfileは例えば以下のような感じ:
FROM docker.io/library/fedora:37
USER root
# for man
RUN sed -i -e 's/tsflags/# tsflags/g' /etc/dnf/dnf.conf
# install packages
RUN dnf update -y && \
# add packages \
dnf install -y nano vim \
sudo passwd \
less which wget findutils rsync tar unzip zip xz bzip2 \
bc gawk binutils \
util-linux-user \
cracklib-dicts \
man-db man-pages \
git \
dnf-plugins-core \
&& \
# cache clear \
dnf clean all && \
rm -rf /var/cache/yum && \
rm -rf /var/cache/dnf
# create user
ARG USER_NAME=wsl-user
ARG USER_UID=1000
ARG PASSWD=password
RUN useradd -m -s /bin/bash -u ${USER_UID} ${USER_NAME} && \
echo "${USER_NAME}:${PASSWD}" | chpasswd && \
echo "${USER_NAME} ALL=(ALL) ALL" >> /etc/sudoers
# WSL2 setting
COPY wsl.conf /etc/wsl.conf
USER ${USER_NAME}
WORKDIR /home/${USER_NAME}
Ubuntuとちがってunminimize
のようなものはないので、もう少し色々自分で入れている。
また、Ubuntu同様にデフォルトではmanをインストールしてもマニュアルデータがダウンロードされない設定になっているため、
RUN sed -i -e 's/tsflags/# tsflags/g' /etc/dnf/dnf.conf
部分で/etc/dnf/dnf.conf
の該当部分の設定を編集して対処している。
(tsflag
の設定が悪さをしているため、コメントアウトしている)
あとはUbuntu同様に今回記載のものをベースに、目的・用途に応じてパッケージや設定項目を追加していく。
3. Arch Linux
Dockerfileは例えば以下のような感じ:
FROM docker.io/library/archlinux:latest
USER root
# manが正しく動くために必要
RUN sed -i -e 's|^\(NoExtract *= *usr/share/man/\)|#\1|' /etc/pacman.conf
RUN pacman-key --init
RUN pacman -Syyu --noconfirm && \
pacman -Sy --noconfirm \
bash bash-completion \
sudo \
man-db man-pages \
coreutils \
which less wget binutils \
vi nano vim \
git \
systemd \
&& \
# cacheのクリア
pacman -Scc && \
rm -rf /var/cache/pacman/*
# create user
ARG USER_NAME=wsl-user
ARG USER_UID=1000
ARG PASSWD=password
RUN useradd -m -s /bin/bash -u ${USER_UID} ${USER_NAME} && \
echo "${USER_NAME}:${PASSWD}" | chpasswd && \
echo "${USER_NAME} ALL=(ALL) ALL" >> /etc/sudoers
# WSL2 setting
COPY wsl.conf /etc/wsl.conf
USER ${USER_NAME}
WORKDIR /home/${USER_NAME}
基本的にfedoraのときと同じコンセプトでやっていて、
- 設定ファイル
/etc/pacman.conf
を書き換えて、man
コマンドでコマンドのマニュアルを見れるように設定している - とりあえず最低限の動作に困らなさそうなコマンドを追加している
- 実際は目的・用途に合わせて追加していく
といった感じ。ユーザーの作成・設定もUbuntu, fedoraのときと同様になっている。
4. その他
これまでの例は結構ミニマルな感じだったので、
もう少し色々詰め込んだ例を補足としておいてみる
FROM docker.io/library/fedora:37
USER root
# for man
RUN sed -i -e 's/tsflags/# tsflags/g' /etc/dnf/dnf.conf
# install packages
RUN dnf update -y && \
# add packages \
dnf install -y nano vim \
sudo passwd \
openssh openssh-clients autossh \
net-tools iproute iputils hostname gnutls bind-utils nmap nc \
less which wget findutils rsync tar unzip zip xz bzip2 \
whois \
bc gawk binutils \
util-linux-user \
cracklib-dicts \
bash zsh tcsh fish tmux \
bash-completion \
ShellCheck shfmt \
man-db man-pages \
python3 python3-pip \
cloud-init \
git tig \
tree jq multitail expect \
fzf exa hexyl fd-find bat ripgrep duf procs htop ncdu tldr zoxide \
hadolint \
fuse \
xclip xsel xeyes \
wl-clipboard \
dnf-plugins-core \
&& \
dnf reinstall -y shadow-utils \
&& \
# docker-ce \
dnf config-manager --add-repo https://download.docker.com/linux/fedora/docker-ce.repo && \
dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin && \
# cache clear \
dnf clean all && \
rm -rf /var/cache/yum && \
rm -rf /var/cache/dnf
# enable service
RUN systemctl enable docker
# create user
ARG USER_NAME=wsl-user
ARG USER_UID=1000
ARG PASSWD=password
RUN useradd -m -s /bin/zsh -u ${USER_UID} ${USER_NAME} && \
echo "${USER_NAME}:${PASSWD}" | chpasswd && \
echo "${USER_NAME} ALL=(ALL) ALL" >> /etc/sudoers
# echo "${USER_NAME} ALL=NOPASSWD: ALL" >> /etc/sudoers
# for docker
RUN gpasswd -a ${USER_NAME} docker
# WSL2 setting
COPY wsl.conf /etc/wsl.conf
# gitのエイリアスといった設定を入れている
COPY gitconfig /home/${USER_NAME}/.gitconfig
USER ${USER_NAME}
WORKDIR /home/${USER_NAME}
上記の例ではdocker-ceをインストールしているが、
- ユーザーをdockerグループに追加し、
sudo
なしで実行出来るようにしている:
RUN gpasswd -a ${USER_NAME} docker
- dockerサービスがWSL起動時に立ち上がるように設定している
# dockerサービスを有効化しておく
RUN systemctl enable docker
# (...)
# 冒頭らへんで書いたように、wsl.confにはsystemd有効化の設定を記入している
COPY wsl.conf /etc/wsl.conf
これらは一例だが、こういった地味に面倒な設定作業もスクリプト化して自動化させることが出来る。
まとめ
本格的に色々やろうとすると、コンテナ特有の設定を解除したり、普通だったら入っているはずの機能を探してインストールしたり、というのが結構大変だったり、という感想。
とは言え、ある程度慣れればWSL上で色々なディストリビューションを試してみたり、開発環境の構築や設定をスクリプト化出来るのは便利そう。
Discussion
参考になりました。ありがとうございました。
CBL-Marinerのイメージを作りたいのですが、同様の方法で作成すると、ホストドライブがマウントできなかったり、他のWSL VMを含めてすべてがネットに接続できなくなってしまいました。もしご興味あればと思いコメントしました。