Ubuntu 20.04 LTS on WSL2 環境構築メモ (3/4) - Docker 導入

前提

導入対象とするマシンは下記の通り。

  • Hardware : CPU = i10900, GPU = GeForce RTX 2080Ti, Mem = 32GB, SSD 1 TB + HDD 2TB
  • ベース OS : Windows 10 Pro バージョン 20H2 (ビルド 19042.685)
  • 仮想 OS : Ubuntu 20.04 LTS on WSL2
  • 環境構築メモ 2/4 を実施した状況

Docker 導入

Ubuntu 14.04 より apt-get, apt-chache ではなく apt コマンドの使用が推奨されているため、参考記事に旧コマンドが含まれている場合は注意。特に Docker Package の導入では apt 新コマンド利用を強く推奨されている。

Docker のインストールと設定

前提パッケージのインストール
 sudo apt update
 sudo apt install -y apt-transport-https ca-certificates curl software-properties-common
Docker 公式 GPG 公開鍵のインストール
 curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
apt レポジトリの設定

Intel x86-64 だけど arch=amd64 でいいの?」と思うが、x86-64 (= AMD64) とは AMD が開発した 32bit 下方互換 64bit アーキテクチャという意味なのでこれでよい。AMD ではない方と誤って armhf にしてはいけない。

 sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu $(lsb_release -cs) stable"
Docker Community Edition のインストール
 sudo apt update
 sudo apt install -y docker-ce

# 最後に更新
sudo apt update; sudo apt upgrade
インストールした Docker のバージョン確認
 docker version
一般ユーザで Docker を起動するための準備

インストールした状態では root しか docker コマンドを実行できないため、起動させたいユーザを docker グループに追加する。現状のユーザをグループ追加するには次のコマンドを実行する。

 sudo usermod -aG docker ${USER}

設定は再ログインまたは次のコマンドを実行した後に有効になる。

 su - ${USER}

次のコマンドを実行し、指定したユーザが docker グループに所属したか確認する。

 id -nG

これで Docker のインストールが完了し、デーモンが起動している、確認するため次のコマンドを実行する、と各解説サイトは説明する。

 sudo systemctl status docker

が、実行結果は下記の通り。デーモンプロセスは動いていない。

System has not been booted with systemd as init system (PID 1). Can't operate.
Failed to connect to bus: Host is down

systemd ≠ pid 1 問題とその解決

ここでデーモンが動かないのは、一般の Unix システムでカーネルから最初に起動され pid = 1 を割り振られる init プロセスが、仮想 OS である Ubuntu on WSL2 においてはカスタマイズされてしまっていることによる。Unix の場合 init = systemd (pid 1) だが、WSL2 の場合、init プロセスは systemd とは別にあり、systemd はインストールされているものの無効化されていて、有効化しても pid = 1 にはならない。という背景により systemctl によるサービス管理が動作しない。

解決方法はいくつかあるらしいが、ここでは Genie というものを導入する方法を採る。

Genie 導入の準備

Genie が依存するパッケージをあらかじめインストールする。

 sudo apt install -y daemonize dbus policykit-1

.NET Runtime が必要となるため、最新の .NET 5.0 for Ubuntu 20.04 LTS をインストールする。(参考記事は .NETLinux も古いため、手順は少しカスタマイズが必要。)

# レポジトリの追加
 wget https://packages.microsoft.com/config/ubuntu/20.04/packages-microsoft-prod.deb -O packages-microsoft-prod.deb

# 更新
 sudo dpkg -i packages-microsoft-prod.deb
 sudo apt update

# .NET Runtime 導入
 sudo apt install -y apt-transport-https && sudo apt update && sudo apt install -y aspnetcore-runtime-5.0

Genie の導入

Genie のインストール

まず、下記の内容を記述した /etc/apt/sources.list.d/wsl-translinux.list というファイルを新規作成する。書き込みには root 権限が必要。本家で説明されている導入手順であるため、解説サイトなどでは説明が端折られていることが多いが、このファイルを作成しないとパッケージが見つからず、インストールが途中で止まる。

 deb [trusted=yes] https://wsl-translinux.arkane-systems.net/apt/ /

Genie をインストールする。

# リポジトリの追加
 curl -s https://packagecloud.io/install/repositories/arkane-systems/wsl-translinux/script.deb.sh | sudo bash

# Genie のインストール
 sudo apt install systemd-genie
getty@tty サービスの停止と systemd への差し替え
 sudo systemd stop getty@tty
Genie の起動確認
# Genie の動作確認 (エラーが出なければ OK)
 genie -s
systemctl による Docker の状況再確認

あらためて systemctl で Docker の状態を再確認する。.NET 入れたり Genie 入れたり、これらも一筋縄ではいかず、苦労したが、やりたかったのはこれ。ふぅ。

 sudo systemctl status docker
Genie の自動起動設定

WSL2 起動時に Genie を自動起動させるために /etc/profile.d/genie.sh に管理者権限で下記の設定をする。(参考記事と少し異なる理由は後述)

# systemd の PID を調べるコマンドにエイリアスをつける
alias getsdpid='ps -eo pid,lstart,cmd | grep systemd | grep -v -e grep -e systemd- | sort -n -k2 | awk '\''NR==1 { print $1 }'\'''

if [ -z "`getsdpid`" ]; then
  echo 'genie : initializing ...'
  genie -i &> /dev/null &
  echo 'genie : waiting for initialization ...'
  sleep 2s  
fi

if [ "`getsdpid`" != "1" ]; then
  echo 'genie : launching bash ...'
  genie -s
fi
Ubuntu 仮想環境初回起動時に systemd を待ち続ける問題

WSL2 起動時に Genie を自動起動させるために .bashrc/etc/profile.d/genie.sh に下記を追記しておけとよく解説されているが、これでは PC 起動直後の Ubuntu 仮想環境初回起動時には Waiting for systemd ... !!!!!!!! (! が次第に増えていく) と出続け、しばらく待っていても機能しない。

if [ "`ps -eo pid,lstart,cmd | grep systemd | grep -v -e grep -e systemd- | sort -n -k2 | awk 'NR==1 { print $1 }'`" != "1" ]; then
  genie -s
fi

この問題にかなり悩まされたがいろいろと調査した結果、わかったことを列挙する。原因は初期化プロセスでかなり待たされるようになっている Genie の作りにあるようだ。

  • genie -s は、①初期化 (systemd 関連プロセス群の起動) を担う genie -i の部分と、②bash シェルを (Ubuntu 仮想環境直上の bash に重ねて) 起動する部分、から成る
  • genie -s は、初期化されていれば①をスキップする
  • genie -i は、systemd 関連プロセス起動をキックした後に Waiting for systemd ... !!!!!!!! と出力してコンソールを占有しつつ、数分間稼働し続ける
  • systemd 関連プロセス群が利用可能になるまでの時間は数秒だが、②の bash シェル起動は①のプロセス完了を待っている
  • /init から systemd への pid = 1 移し替えは genie -s が担っている (genie -i ではない)
  • systemd を pid = 1 にしているのはエミュレーションであり、genie -s が起動した bash シェル上でのみ有効である (Ubuntu 仮想環境直上で実際に systemd が pid = 1 となっているわけではない)
  • genie -i が起動した systemd 関連プロセス群は genie -u でクリーンアウトできる


したがって、回避策としては下記のようになる。

  • genie -s の担う役割を、①初期化、と、②bash シェル起動 + pid エミュレート、とに分離する (初期化を genie -s に頼らない)
  • systemd がなければ genie -i を起動して初期化する
  • genie -i は、出力を捨てつつバックグラウンドで立ち上げる
  • 初期化開始後に genie -s を起動して bash シェルを起動する (genie -i の完了を待たない)


genie.shの冒頭に記述した systemd の pid を返すエイリアス getsdpid を使うと下記のように環境を調べることができる。

  • 1 が返る → genie -s が起動した bash にいる
  • 1 以外が返る → genie -i が起動した systemd は生きているが、Ubuntu 仮想環境直上の bash にいて systemd の pid は 1 ではない
  • 何も返らない → Ubuntu 仮想環境直上の bash にいて systemd も生きていない


.bashrc もレイヤー違いで重ねて走ってしまうため、重ねると害のある部分 (hwclock など) は getsdpid の返り値によってスキップさせるとよい。

Docker Compose のインストール

Docker Compose も使うことになるため、インストールしておく。

# update package list
sudo apt update
sudo apt upgrade

# install
sudo apt install docker-compose

Docker 利用の省力化

テキストタイプ省力化のためによく使う docker コマンドのエイリアス.bashrc に記述しておく。

alias dc='docker'
alias dcc='docker-compose'
alias dce='docker exec -it $(docker ps -lq) /bin/bash'