😽

Dockerを駆使して任意のWSL2 Distroを作成する

2023/03/26に公開1

前に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をそれなりに書ける技術あたりが求められる

実行

今回はとりあえず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は今回は以下のようにした:

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は次のような感じ:

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

部分について、この設定をいれておかないと

https://northshorequantum.com/archives/dockerbuild_tz_hang.html

のような感じで固まってしまうことがあるため追加している。

yes | unminimize

部分について、Ubuntuはunminimizeというコマンドがあり、サーバーとして動かすのに最低限?のパッケージ追加や設定変更などを行ってくれる様子

あと、これをやらないとコンテナ仕様のためにman, man-dbをインストールしても各コマンドのマニュアルデータがダウンロードされず

man <コマンド>

をしても何も表示されなくなってしまう。

参考:

https://askubuntu.com/questions/1431707/where-to-find-the-man-page-for-command-unminimize

https://qiita.com/Fulltea/items/1c62c43133451cf5aa45

https://scrapbox.io/nwtgck/Dockerのubuntu:18.04のunminimizeで入るパッケージ一覧

また、

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は例えば以下のような感じ:

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は例えば以下のような感じ:

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. その他

これまでの例は結構ミニマルな感じだったので、
もう少し色々詰め込んだ例を補足としておいてみる

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 \
    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 --chown=${USER_NAME}:${USER_NAME} gitconfig /home/${USER_NAME}/.gitconfig

USER ${USER_NAME}
WORKDIR /home/${USER_NAME}

上記の例ではdocker-ceをインストールしているが、

  • ユーザーをdockerグループに追加し、sudoなしで実行出来るようにしている:
RUN gpasswd -a ${USER_NAME} docker
# dockerサービスを有効化しておく
RUN systemctl enable docker

# (...)

# 冒頭らへんで書いたように、wsl.confにはsystemd有効化の設定を記入している
COPY wsl.conf /etc/wsl.conf

これらは一例だが、こういった地味に面倒な設定作業もスクリプト化して自動化させることが出来る。

まとめ

本格的に色々やろうとすると、コンテナ特有の設定を解除したり、普通だったら入っているはずの機能を探してインストールしたり、というのが結構大変だったり、という感想。

とは言え、ある程度慣れればWSL上で色々なディストリビューションを試してみたり、開発環境の構築や設定をスクリプト化出来るのは便利そう。

GitHubで編集を提案

Discussion

あおんとあおんと

参考になりました。ありがとうございました。
CBL-Marinerのイメージを作りたいのですが、同様の方法で作成すると、ホストドライブがマウントできなかったり、他のWSL VMを含めてすべてがネットに接続できなくなってしまいました。もしご興味あればと思いコメントしました。