WindowsでDockerデーモンを自動起動させる方法をまとめてみる without Docker Desktop

## はじめに

アドベントカレンダーに初参加してみました。

さて、6日目の[Windows PCでDocker Desktopを利用せずにLinuxコンテナーとWindowsコンテナー環境を構築する方法](https://qiita.com/hatasaki/items/1b945f940c23e0ee36f3)でも紹介されている通り、一定規模以上の法人での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日目のエントリ](https://qiita.com/hatasaki/items/1b945f940c23e0ee36f3)を参照してください

## 方法

### その1 .bashrcを使う

`~/.bashrc`に`service docker start`を書く方法です。
一般ユーザーがパスワードなしで実行できるようvisudoコマンドなどでsudoersファイルを変更しておく必要があります。

[DockerDesktopからWSL2上のみで動くDockerに移行する](https://zenn.dev/taiga533/articles/11f1b21ef4a5ff)が参考になります。

なお、この方法はPC起動後に初めてターミナルへログインしたときにデーモンが実行されるため、PC起動時にコンテナを再実行するという目的は果たせません。

### その2 Systemdを使う

WSL2上のLinuxでSystemdを動かしてデーモンを起動する方法です。
[genie](https://github.com/arkane-systems/genie)を利用するのが定番みたいですが、他には[Distrod](https://github.com/nullpo-head/wsl-distrod)を使ってもSystemdを動かせるようになります。

[WSL2(Ubuntu18.04 LTS)をセットアップしてDocker(on Ubuntu)とGUIアプリを使う](https://qiita.com/Keiwa/items/390d89c20228edf0b16c)が参考になります。

### その3 タスクスケジューラを使う

Windowsのタスクスケジューラを使う方法です。
wslコマンドの`-d`オプションと`-u`オプションを使うと、Windows側から任意のディストリビューションの任意のユーザーで任意のコマンドを実行することができます。

1. タスクスケジューラの「基本タスクの作成」を選びます
1. タスクの名前を決めます
1. トリガーとして「コンピュータの起動時(H)」を選びます 
1. 操作として「プログラムの開始(T)」を選びます
1. プログラムにはPowerShellのパスである`C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe`を、引数の追加(オプション)には`-Command "wsl -d Ubuntu -u root -- service docker start`を指定します

これでほぼ完了です。
ただし、Windowsのプロセスの起動順によってはうまく効かない時があったので、作成したタスクのプロパティからトリガーを編集して、「遅延時間を指定する(K)」で「30秒間」を選択してタスクの実行を遅らせると安定します。

この方法は、Dockerエンジンのインストール以外に、Ubuntu側の設定ファイルを書き換えやツールのインストールが不要なので結構気に入っています。

:::note info
wslコマンドでLinux側のコマンドを実行するオプションとして-eオプションと--オプションの二つがあります。
それぞれの違いについては深く調べられていませんが、-uオプションで指定したユーザーのデフォルトシェルが使われる--を使ってます。
:::

### その4 wsl.confのbootオプションを使う

Windows 11から使えるオプションのようなので試せていませんが、Linux側の`/etc/wsl.conf`の`boot`オプションを使うと、ディストリビューション起動時に**root権限**で実行するコマンドを指定できるようです。
すなわち、以下のように書いておけば、タスクスケジューラを使わずともデーモンを立ち上げることができます。

```ini:/etc/wsl.conf
[boot]
command = service docker start
```

Microsoft Docsの[WSL での詳細設定の構成](https://docs.microsoft.com/windows/wsl/wsl-config#boot-settings)でも、Dockerデーモンを起動する設定例が記載されているので、まさにDockerデーモンの自動起動を想定した機能なのでしょう。多分。

:::note info
bootオプションはInsider Previewに参加しているWindows 10でもおそらく使えます。
:::

:::note info
2021/12/25時点で、Microsoft Docsの日本語が機械翻訳されたもののためにかなり読みづらいので、英語版を読むことをお勧めします。
:::

## 課題

これまで4つの方法を紹介しましたが、いずれもほんの少しだけ課題がありました。
それは、コンテナのエントリポイントで指定したスクリプト内で、trapコマンドでシグナルを捕捉して終了処理を書いているときです。

ディストリビューションのシャットダウンに伴ってデーモンが正常に停止されれば、終了シグナルが発せらるので終了処理が呼び出されたうえでコンテナが停止します。
しかしながら、WSL2上で動くLinuxのプロセスたちは、PCシャットダウン時にマルっとKillされているような動きをしていました。(間違ってるかもしれません。)
そのため、シグナルが捕捉できないために終了処理が呼ばれないまま、コンテナが停止される状況に陥ります。

これに気づいたきっかけは、[Docker でセルフホステッド エージェントを実行する](https://docs.microsoft.com/azure/devops/pipelines/agents/docker?view=azure-devops#create-and-build-the-dockerfile-1)を参考に、Azure PipelinesのセルフホステッドエージェントをDockerコンテナで構築しようと試した時でした。
終了処理が呼ばれないままコンテナが再実行されると、エージェントプログラムが実行できないようになっていました。

:::note info
Docker Desktopが動いている環境では、Docker Desktopの常駐プロセスがPCシャットダウン時にデーモンを正常停止してくれてるっぽいので、この問題は起きません。
:::

### 解決案

PCシャットダウン時に`wsl -d Ubuntu -u root -- service docker stop`を実行する。
調べてみると、グループポリシーを変更すればシャットダウン時に任意のプログラムを実行できるようになるみたいですが、できるだけWindowsの設定を変えずに何とかできないかと考えました。

そこで、タスクスケジューラでPC起動時に以下のようなスクリプトを実行すれば、シャットダウン時にfinallyに入ってくれると考えましたがダメでした。

```powershell:ダメなパターン
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](https://superuser.com/questions/1302681/powershell-finally-block-not-executed-in-windows-task-scheduler)にありました。
タスクスケジューラで実行しているプロセスは、PCシャットダウン時にWindowsによってKillされているためにfinallyで捕捉できないようです。
ただし、タスクスケジューラで実行したプロセスから**派生したプロセスはKillされない**そうです。
この仕様を使い、このスレッドの回答にある感じで、親プロセスの終了を`WaitForExit()`待ってから`wsl -d Ubuntu -u root -- service docker stop`を呼び出す派生プロセスを実行すればうまくいく気がしています。

以下が、タスクスケジューラで起動するスクリプトのイメージですが、私はまだこの方法を試せていないので、真偽は不明です…。
もし結果が分かったら追記します。

```powershell
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使ったほうがいい気がしますね。