さて、筆者はワクチン売人であるビル・ゲイツが大嫌いでアンチM$ユーザなわけで、自宅では一切Windowsを触ることはないですが、
仕事では仕方なく非常に残念ながらWindowsを触る機会もあります。

そこで今回はWSL2(WindowsSubSystem for Linux)で開発環境を構築してみたのでしが、その際に実施したことなどをまとめてみました。

今回の記事はかなりボリュームがあります。

性能面の違い

今回の比較環境(Lenovo ThinkPad X1Carbon 4thを使用)

1)Windows10Pro(20H2)上のWSL2(UbuntuLinux20.04)
2)elementaryOS5.0(Ubuntuベースディストリビューション)

上記のGeekBench5のベンチマーク結果
1)Single:727、Multi:1303
2)Single:769、Multi:1719

なんとWSL2でもそこそこSingleコアではパフォーマンスが確保できていました。Multiで低いのはその分ベースのOSに食われているからかと推測します。

環境設定面の違い

WSL2上のUbuntuではネイティブLinuxとは環境構築において勝手が違い手間がかかった。主に以下が相違していた。
・systemctlコマンドが使えない。
・外部アクセスができない(内部IPアドレスとなるため)

例によってモノ好きな筆者は上記の相違していた点をなんとかしてみた。

1)WSL2では「systemctl」コマンドが使えない

WSL2上のUbuntuへ必要な各種ソフトウェアなどを導入し、設定、いざsystemctlコマンドでサービスを起動しようとすると以下のメッセージが。。。

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

なんとも、systemdがPID1で起動してないから使えないと。。。とほほ。

ググってみると同様の事象で解決策がいくつかありました。
そんなわけで

WSL2で「systemctl」コマンドをなんとか使えるようにする

ということで今回はこちらのサイトを参考にさせていただいた。

簡単にまとめると以下コマンドを実行することで一時的にsystemdのPIDを1にすることが可能。

まずはUbuntuのインストール後にdaemonizeをインストール。

$ sudo apt update && sudo apt install daemonize

次に以下を実行。

$ sudo daemonize /usr/bin/unshare --fork --pid --mount-proc /lib/systemd/systemd --system-unit=basic.target 
$ exec sudo nsenter --target $(pidof systemd) --all su - $LOGNAME

上記でsystemdのPIDが1になりsystemctlコマンドが使えるようになる。
ただし上記は起動時に毎回実行する必要ある。

そのため毎回実行しないで済む方法としてこちらのフォーラムを参考に以下を実施した。

ここでは今後の筆者が再度環境構築する際の自分用メモおよびフォーラムの記載では一部パスが異なり動作しなかったために、その修正版としてここへ掲載しておく。

「/usr/sbin/start-systemd-namespace」というファイルを作成する。

$ sudo vi /usr/sbin/start-systemd-namespace

中身は以下。これはフォーラムのものと同一。

#!/bin/bash

SYSTEMD_PID=$(ps -ef | grep '/lib/systemd/systemd --system-unit=basic.target$' | grep -v unshare | awk '{print $2}')
if [ -z "$SYSTEMD_PID" ] || [ "$SYSTEMD_PID" != "1" ]; then
    export PRE_NAMESPACE_PATH="$PATH"
    (set -o posix; set) | \
        grep -v "^BASH" | \
        grep -v "^DIRSTACK=" | \
        grep -v "^EUID=" | \
        grep -v "^GROUPS=" | \
        grep -v "^HOME=" | \
        grep -v "^HOSTNAME=" | \
        grep -v "^HOSTTYPE=" | \
        grep -v "^IFS='.*"$'\n'"'" | \
        grep -v "^LANG=" | \
        grep -v "^LOGNAME=" | \
        grep -v "^MACHTYPE=" | \
        grep -v "^NAME=" | \
        grep -v "^OPTERR=" | \
        grep -v "^OPTIND=" | \
        grep -v "^OSTYPE=" | \
        grep -v "^PIPESTATUS=" | \
        grep -v "^POSIXLY_CORRECT=" | \
        grep -v "^PPID=" | \
        grep -v "^PS1=" | \
        grep -v "^PS4=" | \
        grep -v "^SHELL=" | \
        grep -v "^SHELLOPTS=" | \
        grep -v "^SHLVL=" | \
        grep -v "^SYSTEMD_PID=" | \
        grep -v "^UID=" | \
        grep -v "^USER=" | \
        grep -v "^_=" | \
        cat - > "$HOME/.systemd-env"
    echo "PATH='$PATH'" >> "$HOME/.systemd-env"
    exec sudo /usr/sbin/enter-systemd-namespace "$BASH_EXECUTION_STRING"
fi
if [ -n "$PRE_NAMESPACE_PATH" ]; then
    export PATH="$PRE_NAMESPACE_PATH"
fi

次に「/usr/sbin/enter-systemd-namespace」というファイルを作成。

$ sudo vi /usr/sbin/enter-systemd-namespace

中身は以下。daemonizeのパスが異なっていたので、正しいパスへ修正してある。


#!/bin/bash

if [ "$UID" != 0 ]; then
    echo "You need to run $0 through sudo"
    exit 1
fi

SYSTEMD_PID="$(ps -ef | grep '/lib/systemd/systemd --system-unit=basic.target$' | grep -v unshare | awk '{print $2}')"
if [ -z "$SYSTEMD_PID" ]; then
    /usr/bin/daemonize /usr/bin/unshare --fork --pid --mount-proc /lib/systemd/systemd --system-unit=basic.target
    while [ -z "$SYSTEMD_PID" ]; do
        SYSTEMD_PID="$(ps -ef | grep '/lib/systemd/systemd --system-unit=basic.target$' | grep -v unshare | awk '{print $2}')"
    done
fi

if [ -n "$SYSTEMD_PID" ] && [ "$SYSTEMD_PID" != "1" ]; then
    if [ -n "$1" ] && [ "$1" != "bash --login" ] && [ "$1" != "/bin/bash --login" ]; then
        exec /usr/bin/nsenter -t "$SYSTEMD_PID" -a \
            /usr/bin/sudo -H -u "$SUDO_USER" \
            /bin/bash -c 'set -a; source "$HOME/.systemd-env"; set +a; exec bash -c '"$(printf "%q" "$@")"
    else
        exec /usr/bin/nsenter -t "$SYSTEMD_PID" -a \
            /bin/login -p -f "$SUDO_USER" \
            $(/bin/cat "$HOME/.systemd-env" | grep -v "^PATH=")
    fi
    echo "Existential crisis"
fi

上記の2ファイルに実行権限を付与する。

sudo chmod +x /usr/sbin/*-systemd-namespace

visudoコマンドで以下を最終行に貼り付け

Defaults        env_keep += WSLPATH
Defaults        env_keep += WSLENV
Defaults        env_keep += WSL_INTEROP
Defaults        env_keep += WSL_DISTRO_NAME
Defaults        env_keep += PRE_NAMESPACE_PATH
%sudo ALL=(ALL) NOPASSWD: /usr/sbin/enter-systemd-namespace

「/etc/bash.bashrc」の最終行に追記

sudo vi /etc/bash.bashrc

以下の内容を最終行に貼り付け

sudo sed -i 2a"# Start or enter a PID namespace in WSL2\nsource /usr/sbin/start-systemd-namespace\n" /etc/bash.bashrc

Windows上の環境変数の設定

Powershell上で以下を実行

cmd.exe /C setx BASH_ENV /etc/bash.bashrc
cmd.exe /C setx WSLENV BASH_ENV/u

これでWSL2のUbuntuの起動時にユーザのパスワードを聞かれ、ログインするとsystemdのPIDが1となり、systemctlコマンドが使えるようになる。

このログインパスを聞かれないようにする方法として以前の記事で紹介した以下手順を実施した。

尚、上記以外にもう一点、root直下に、「.system-env」が無いとメッセージが出るのでこれを回避するために以下を実施。

rootで「vi .system-env」を実行し、以下を貼り付けておいた。
(※ユーザ環境の.system-envをrootのパスに合わせた修正版である)

COLUMNS=120
HISTFILE=/root/.bash_history
LINES=30
MAILCHECK=60
PWD=/root
TERM=xterm-256color
WSLENV=WT_SESSION:BASH_ENV/u:WT_PROFILE_ID
WSL_DISTRO_NAME=Ubuntu-20.04
WSL_INTEROP=/run/WSL/7_interop
WT_SESSION=896b3e3a-5f2d-474c-893d-cffbe0c32a95
PATH='/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/games:/usr/local/games:/mnt/c/Windows/system32:/mnt/c/Windows:/mnt/c/Windows/System32/Wbem:/mnt/c/Windows/System32/WindowsPowerShell/v1.0/:/mnt/c/Windows/System32/OpenSSH/:/mnt/c/Users/OSManiaX/AppData/Local/Microsoft/WindowsApps:/mnt/c/Users/OSManiaX/AppData/Local/Programs/Microsoft VS Code/bin'

2)外部からアクセスできるようにする

これにはPortProxyで対応した。
まずはWSL2のUbuntuLinuxの内部IPアドレスを確認のため以下コマンドを実行。

$ ifconfig

(※上記コマンドが存在しない場合、sudo apt install net-toolsでインストールを行う)

次にPowershellの管理者モードで以下のコマンドを実行。
(※ポートやIPアドレスは筆者環境のものです、適宜環境に合わせて実施)

netsh.exe interface portproxy add v4tov4 listenaddress=* listenpor=22 connectaddress=172.26.231.58 connectport=22
netsh.exe interface portproxy add v4tov4 listenaddress=* listenpor=80 connectaddress=172.26.231.58 connectport=80
netsh.exe interface portproxy add v4tov4 listenaddress=* listenpor=443 connectaddress=172.26.231.58 connectport=443
netsh.exe interface portproxy add v4tov4 listenaddress=* listenpor=7080 connectaddress=172.26.231.58 connectport=7080

上記で実施した設定状況の確認は以下コマンドで行う。

netsh.exe interface portproxy show all

誤って削除したい場合は以下のコマンドで行う。

netsh.exe interface portproxy delete v4tov4 listenport=80

尚、内部IPアドレスは再起動時に変わってしまうため、上記コマンドを自動実行させる方法として以下を行う。

まず、WSLが起動しないとIPアドレスが判明しないため、Powershellから叩くシェルを作成し、配置する。

$ sudo vi /opt/bin/port_forwarding.sh
#!/bin/bash

IP=$(ifconfig eth0 | grep 'inet ' | awk '{print $2}')

netsh.exe interface portproxy delete v4tov4 listenport=22
netsh.exe interface portproxy add    v4tov4 listenport=22 connectaddress=$IP
netsh.exe interface portproxy delete v4tov4 listenport=80
netsh.exe interface portproxy add    v4tov4 listenport=80 connectaddress=$IP
netsh.exe interface portproxy delete v4tov4 listenport=443
netsh.exe interface portproxy add    v4tov4 listenport=443 connectaddress=$IP
netsh.exe interface portproxy delete v4tov4 listenport=7080
netsh.exe interface portproxy add    v4tov4 listenport=7080 connectaddress=$IP

sc.exe config iphlpsvc start=auto
sc.exe start  iphlpsvc

次に上記で配置したシェルをPowershellから叩くバッチを以下に作成する。

c:\bat\port_forwarding.bat

内容は以下

wsl --exec bash /opt/bin/port_forwarding.sh

上記で作成したバッチファイルをタスクスケジューラで起動時に実行の項目に含める。ここで「最上位の特権で実行する」のチェックを含めること。

尚、Windowsの場合は上記の他として、WindowsFirewallの設定が必要です。

コマンドで行う場合は以下

New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Inbound -LocalPort 80 -Action Allow -Protocol TCP
New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Inbound -LocalPort 443 -Action Allow -Protocol TCP
New-NetFireWallRule -DisplayName 'WSL 2 Firewall Unlock' -Direction Inbound -LocalPort 7080 -Action Allow -Protocol TCP

ファイやウォールについてはネイティブなLinuxの場合はufwですので環境設定方法の違いによるものなので比較対象からは除外する。

まとめ

今回はWSL2のUbuntuで外部アクセス可能なサーバ環境構築をしてみました。
ネイティブなLinuxでの環境設定手順と比較するとかなり手間がかかることは確実ですし、知らないと解決に時間がかかると思います。

セキュリティアップデートも母艦であるWindowsのものも実施しないとなりません。

サーバとしてではなく、ネットが使えない環境でクライアントへデモを行う際や可搬性のある開発環境としては良いのではないでしょうか。

まぁ同じことはmacOSXでもできますけどもね。

いやぁ疲れました。。。