はじめに
アドベントカレンダーに初参加してみました。
さて、6日目のWindows PCでDocker Desktopを利用せずにLinuxコンテナーとWindowsコンテナー環境を構築する方法でも紹介されている通り、一定規模以上の法人でのDocker Desktopの利用が有償化されました(2022年1月31日まで猶予期間あり)。
早速ですが、Docker Desktopを使用する場合、PC起動時にDocker DesktopがDockerデーモンを起動してくれるため、デーモンの起動状況を意識せずとも任意のコンテナを実行することができます。
一方で、WSL2上のLinuxのinitはカスタムされているためにサービスの自動起動ができないようです。すなわち、Docker Desktop無しにWSL2上のLinuxへDockerエンジンをインストールすると、PC起動後にターミナルでわざわざsudo service docker start
を実行する必要があります。
これだと、せかっくdocker run
の--restart
オプションやdocker-compose.ymlのrestart
にalways
を指定したコンテナも、PC起動時には再実行されません。
そこで、Docker Desktopを使わずにDockerエンジンをインストールした環境でも、デーモンを自動起動する方法をまとめてみたいと思います。
前提環境
- Windows 10以降
- WSL2上のUbuntuへDockerエンジンをインストール済み
- インストール手順は6日目のエントリを参照してください
方法
その1 .bashrcを使う
~/.bashrc
にservice docker start
を書く方法です。
一般ユーザーがパスワードなしで実行できるようvisudoコマンドなどでsudoersファイルを変更しておく必要があります。
DockerDesktopからWSL2上のみで動くDockerに移行するが参考になります。
なお、この方法はPC起動後に初めてターミナルへログインしたときにデーモンが実行されるため、PC起動時にコンテナを再実行するという目的は果たせません。
その2 Systemdを使う
WSL2上のLinuxでSystemdを動かしてデーモンを起動する方法です。
genieを利用するのが定番みたいですが、他にはDistrodを使ってもSystemdを動かせるようになります。
WSL2(Ubuntu18.04 LTS)をセットアップしてDocker(on Ubuntu)とGUIアプリを使うが参考になります。
その3 タスクスケジューラを使う
Windowsのタスクスケジューラを使う方法です。
wslコマンドの-d
オプションと-u
オプションを使うと、Windows側から任意のディストリビューションの任意のユーザーで任意のコマンドを実行することができます。
- タスクスケジューラの「基本タスクの作成」を選びます
- タスクの名前を決めます
- トリガーとして「コンピュータの起動時(H)」を選びます
- 操作として「プログラムの開始(T)」を選びます
- プログラムにはPowerShellのパスである
C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe
を、引数の追加(オプション)には-Command "wsl -d Ubuntu -u root -- service docker start
を指定します
これでほぼ完了です。
ただし、Windowsのプロセスの起動順によってはうまく効かない時があったので、作成したタスクのプロパティからトリガーを編集して、「遅延時間を指定する(K)」で「30秒間」を選択してタスクの実行を遅らせると安定します。
この方法は、Dockerエンジンのインストール以外に、Ubuntu側の設定ファイルを書き換えやツールのインストールが不要なので結構気に入っています。
wslコマンドでLinux側のコマンドを実行するオプションとして-eオプションと--オプションの二つがあります。
それぞれの違いについては深く調べられていませんが、-uオプションで指定したユーザーのデフォルトシェルが使われる--を使ってます。
その4 wsl.confのbootオプションを使う
Windows 11から使えるオプションのようなので試せていませんが、Linux側の/etc/wsl.conf
のboot
オプションを使うと、ディストリビューション起動時にroot権限で実行するコマンドを指定できるようです。
すなわち、以下のように書いておけば、タスクスケジューラを使わずともデーモンを立ち上げることができます。
[boot]
command = service docker start
Microsoft DocsのWSL での詳細設定の構成でも、Dockerデーモンを起動する設定例が記載されているので、まさにDockerデーモンの自動起動を想定した機能なのでしょう。多分。
bootオプションはInsider Previewに参加しているWindows 10でもおそらく使えます。
2021/12/25時点で、Microsoft Docsの日本語が機械翻訳されたもののためにかなり読みづらいので、英語版を読むことをお勧めします。
課題
これまで4つの方法を紹介しましたが、いずれもほんの少しだけ課題がありました。
それは、コンテナのエントリポイントで指定したスクリプト内で、trapコマンドでシグナルを捕捉して終了処理を書いているときです。
ディストリビューションのシャットダウンに伴ってデーモンが正常に停止されれば、終了シグナルが発せらるので終了処理が呼び出されたうえでコンテナが停止します。
しかしながら、WSL2上で動くLinuxのプロセスたちは、PCシャットダウン時にマルっとKillされているような動きをしていました。(間違ってるかもしれません。)
そのため、シグナルが捕捉できないために終了処理が呼ばれないまま、コンテナが停止される状況に陥ります。
これに気づいたきっかけは、Docker でセルフホステッド エージェントを実行するを参考に、Azure PipelinesのセルフホステッドエージェントをDockerコンテナで構築しようと試した時でした。
終了処理が呼ばれないままコンテナが再実行されると、エージェントプログラムが実行できないようになっていました。
Docker Desktopが動いている環境では、Docker Desktopの常駐プロセスがPCシャットダウン時にデーモンを正常停止してくれてるっぽいので、この問題は起きません。
解決案
PCシャットダウン時にwsl -d Ubuntu -u root -- service docker stop
を実行する。
調べてみると、グループポリシーを変更すればシャットダウン時に任意のプログラムを実行できるようになるみたいですが、できるだけWindowsの設定を変えずに何とかできないかと考えました。
そこで、タスクスケジューラでPC起動時に以下のようなスクリプトを実行すれば、シャットダウン時にfinallyに入ってくれると考えましたがダメでした。
wsl -d Ubuntu -u root -- service docker start
try
{
while (1)
{
Start-Sleep 5
}
}
finally
{
wsl -d Ubuntu -u root -- service docker stop
}
その理由はPowershell finally block not executed in windows task schedulerにありました。
タスクスケジューラで実行しているプロセスは、PCシャットダウン時にWindowsによってKillされているためにfinallyで捕捉できないようです。
ただし、タスクスケジューラで実行したプロセスから派生したプロセスはKillされないそうです。
この仕様を使い、このスレッドの回答にある感じで、親プロセスの終了をWaitForExit()
待ってからwsl -d Ubuntu -u root -- service docker stop
を呼び出す派生プロセスを実行すればうまくいく気がしています。
以下が、タスクスケジューラで起動するスクリプトのイメージですが、私はまだこの方法を試せていないので、真偽は不明です…。
もし結果が分かったら追記します。
wsl -d Ubuntu -u root -- service docker start
# ここでWaitForExit()で親プロセスの終了を待ってwsl -d Ubuntu -u root -- service docker stopを実行するスクリプトを立ち上げる
while (1)
{
Start-Sleep 5
}
最後に
有償化といっても1人当たり高々数百円ですし、有償化の対象になる法人もお金払って素直にDocker Desktop使ったほうがいい気がしますね。