Docker DesktopなしのDocker環境をWSL2とcloud-initで構築する

株式会社 日立製作所 OSSソリューションセンタ の山田陽介です。
Docker DesktopなしでWSL2環境のUbuntu20.04にDockerとDocker Composeをインストールするため、サーバ初期設定自動化ツールのcloud-initを使用する方法をまとめました。HTTP/HTTPSプロキシがある環境でも使える内容になっており、デーモンの自動起動設定やVisual Studio Codeから使用する方法についても触れています。
本記事は執筆者の試行内容を記したものであり、所属企業やその他の組織の公式ドキュメントではありません。
また本記事の手順は無保証であり、これを参考にしたことにより生じる問題に関して責任を負いません。

# WSL2でDockerを使う方法
WSL2環境でDockerを使うためには、Docker Desktopをインストールする方法と、WSL2環境にインストールしたLinuxにDockerをインストールする方法があります。
Docker Desktopについては、2021年8月末にDocker社が有償化すると発表しました。 [Docker is Updating and Extending Our Product Subscriptions - Docker Blog](https://www.docker.com/blog/updating-product-subscriptions/)
250人以上または年間売り上げ1000万ドル以上の組織では有償となり得ます。[Docker Desktopが有料化へ、ただし250人未満かつ年間売り上げ1000万ドル（約11億円）未満の組織や個人やオープンソースプロジェクトでは引き続き無料で利用可能 － Publickey](https://www.publickey1.jp/blog/21/docker_desktop250100011.html)
本記事では、WSL2環境にUbuntu20.04をインストールして、そこにDockerをインストールします。

# cloud-init
cloud-initはサーバインスタンスの初期設定を自動化できるツールで、コンテナ等のセットアップに使用できます。詳細は公式ドキュメントをご参照ください。
[cloud\-init Documentation — cloud\-init 21\.4 documentation](https://cloudinit.readthedocs.io/en/latest/index.html)
本記事では、プロキシ設定やファイルのダウンロードやパッケージインストール等をするために使っています。下記の「user-dataの作成」のあたりをご参照ください。

# 環境

- Microsoft Windows 10 Pro 10.0.19042
- WSL2 + Ubuntu 20.04 LTS
- インターネットにアクセスするためにHTTP/HTTPSプロキシを経由する必要のある環境での利用を想定しています。
- 「Windowsの機能の有効化または無効化」で「仮想マシンプラットフォーム」が有効な環境を想定しています。

## WSLのインストール
公式手順はこちらです。
[WSL のインストール \| Microsoft Docs](https://docs.microsoft.com/ja-jp/windows/wsl/install)
最近のWindowsでは、管理者権限でコマンドプロンプトまたはPowerShellを起動し `wsl --install` や `wsl --install -d Ubuntu` を実行するだけで、WSL環境にUbuntuを導入可能となっています。
[WSL のリリース ノート \| Microsoft Docs](https://docs.microsoft.com/ja-jp/windows/wsl/release-notes)
社内規則等でインストールファイルの検証や登録が必要な場合は、手動でインストールする方法が使えます。
[以前のバージョンの WSL の手動インストール手順 \| Microsoft Docs](https://docs.microsoft.com/ja-jp/windows/wsl/install-manual)

以下の通り、Ubuntu 20.04 LTSをインストールし、ubuntuユーザを作成した状態で作業します。

```
Installing, this may take a few minutes...
Please create a default UNIX user account. The username does not need to match your Windows username.
For more information visit: https://aka.ms/wslusers
Enter new UNIX username: ubuntu  【ユーザ名として「ubuntu」を使用】
New password:  【パスワード入力】
Retype new password:  【パスワード入力】
passwd: password updated successfully
Installation successful!
To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.
```

## WSLバージョンの確認
念のためWSLのバージョンが2になっているか確認します。
1であった場合は仮想マシンプラットフォームが有効になっているか確認した後に2に変換します。

```
> wsl -l -v
  NAME      STATE           VERSION
* Ubuntu    Running         1
> wsl --shutdown
> wsl --set-version Ubuntu 2
変換中です。この処理には数分かかることがあります...
WSL 2 との主な違いについては、https://aka.ms/wsl2 を参照してください
変換が完了しました。
>wsl -l -v
  NAME      STATE           VERSION
* Ubuntu    Stopped         2
```

## cloud-initの準備と実行

1.cloud-initフォルダの作成
場所はどこでもよいですが、本記事では `C:\ProgramData` を開き、wslフォルダを作り、その中にcloud-initフォルダを作成します。
`C:\ProgramData\wsl\cloud-init\`

2.meta-dataファイルの作成
cloud-initフォルダの中に、下記の1行を含む `meta-data` という名前のテキストファイルを作成します。

```
dsmode: local
```

3.user-dataファイルの作成
cloud-initフォルダの中に、下記の内容を含む `user-data` という名前のテキストファイルを作成します。
7箇所の `<user>:<pass>@<proxy_host>:<proxy_port>` の部分は、環境に合わせて変更します。
プロキシを使わない環境では、これを含む行を消すかコメントアウトします。

```
#cloud-config

#初期に実行するコマンド
bootcmd:
  #rootユーザのcurl実行時のプロキシ設定
  - echo "proxy=http://<user>:<pass>@<proxy_host>:<proxy_port>" >> /root/.curlrc
  #Docker公式のAPTリポジトリのGPG鍵を追加
  - curl -fsSL https://download.docker.com/linux/ubuntu/gpg | apt-key add -

#ユーザの作成と設定
users:
  - name: ubuntu
    #ubuntuユーザをadm,sudoグループに所属させる
    groups: adm, sudo
    #「パスワードなしで」任意のコマンドをsudoコマンドで管理者権限で実行可能にしたい場合は下記2行のコメントを外す
    #sudo:
    #  - ALL=(ALL) NOPASSWD:ALL

#タイムゾーンとLocaleの設定
timezone: Asia/Tokyo
locale: en_US.UTF-8

#apt設定
apt:
  #当初のsources.listを保持する
  preserve_sources_list: true
  sources:
    #Docker公式のAPTリポジトリを追加
    docker.list:
      source: deb [arch=amd64] https://download.docker.com/linux/ubuntu $RELEASE stable
  #http_proxy,https_proxyを設定する
  http_proxy: http://<user>:<pass>@<proxy_host>:<proxy_port>
  https_proxy: http://<user>:<pass>@<proxy_host>:<proxy_port>

#インストールするパッケージのリスト
packages:
  - docker-ce
  #docker-ceが依存しているdocker-ce-cli,containerd.ioも自動でインストールされる

#終盤に実行するコマンド
runcmd:
  #Dockerデーモンのhttp_proxy/https_proxy環境変数
  - echo 'export http_proxy="http://<user>:<pass>@<proxy_host>:<proxy_port>"' >> /etc/default/docker
  - echo 'export https_proxy="http://<user>:<pass>@<proxy_host>:<proxy_port>"' >> /etc/default/docker
  #ubuntuユーザのhttp_proxy/https_proxy環境変数
  - echo 'export http_proxy="http://<user>:<pass>@<proxy_host>:<proxy_port>"' >> /home/ubuntu/.bashrc
  - echo 'export https_proxy="http://<user>:<pass>@<proxy_host>:<proxy_port>"' >> /home/ubuntu/.bashrc
  #docker-compose(v2.2.1)のダウンロードと実行権限の付加
  - curl -L "https://github.com/docker/compose/releases/download/v2.2.1/docker-compose-$(uname -s)-$(uname -m)" -o /usr/libexec/docker/cli-plugins/docker-compose
  - chmod +x /usr/libexec/docker/cli-plugins/docker-compose
  #ubuntuユーザをdockerグループに所属させる
  - usermod -aG docker ubuntu
```
上記と異なるバージョンのDocker Composeを使いたい場合は [Releases · docker/compose](https://github.com/docker/compose/releases/) を参考に `v2.2.1` の部分を修正します。
docker-compose v1系を使いたい場合は [Releases · docker/compose](https://github.com/docker/compose/releases/) を参考に `v2.2.1` の部分を修正し、`curl` の行と `chmod` の行の `/usr/libexec/docker/cli-plugins/` の部分を `/usr/local/bin/` 等に変更します。

4.cloudinit.shの作成
cloud-initフォルダの中に、下記の内容を含む `cloudinit.sh` という名前のファイルを作成します。

```
#!/bin/sh
export DEBUG_PROC_CMDLINE="ds=nocloud;seedfrom=/mnt/c/ProgramData/wsl/cloud-init/"
cloud-init init --local
cloud-init init
cloud-init modules --mode=config
cloud-init modules --mode=final
```

5.wslでcloud-initを実行して環境設定を実行
エクスプローラのアドレス欄またはコマンドプロンプトに `wsl -u root` を入力してWSLをroot権限で立ち上げ、下記のコマンドを実行します。

```
find /mnt/c/ProgramData/wsl/cloud-init -type f -exec sed -i 's/\r//g' {} \;
cd /mnt/c/ProgramData/wsl/cloud-init
./cloud-init.sh
```

6.WSLを一旦抜けてWSL環境をshutdown

```
# exit
> wsl --shutdown
```

何らかのエラーがあってcloud-initを再実行する場合は、コマンドプロンプトに `wsl -u root` を入力してWSLをroot権限で立ち上げ `cloud-init clean` を実行してから、再実行してください。

## SSHとDockerのサービスの起動と、自動起動設定

1.下記の内容を含む `init.bat` という名前のファイルを作成・実行
場所はどこでもよいですが、本記事では `C:\ProgramData\wsl\init.bat` に作成します。SSHとDockerが自動起動されます。

```
wsl -u root exec service ssh start
wsl -u root exec service docker start
```

docker startやdocker compose up等を自動で開始したい場合も、`wsl -u root exec` 以降に実行したいコマンドを書いて、本ファイルに追記すると実行されます。
root権限が不要の場合は `-u root` を `-u ユーザ名` にするとよいでしょう。
ローカルホストへのポートフォワーディングについては、Windowsのビルド18945以降はWSLにlocalhostForwardingが実装され、デフォルト値はTrueとなっていますので、ユーザが設定しなくてもアクセスできるようになっています。
[WSL のリリース ノート \| Microsoft Docs \| Build 18945](https://docs.microsoft.com/ja-jp/windows/wsl/release-notes#build-18945)

2.Windows起動時に自動起動するように設定

 - 簡単な方法： init.batを右クリックでコピーし、Windows + R キーで「ファイル名を指定して実行」から `shell:startup` を開き、右クリックから「ショートカットの貼り付け」をします。ユーザのログオン時に実行されます。参考：[Windows10のWSL上にあるServiceを簡単に自動起動させる \- Qiita](https://qiita.com/nashi2603/items/d0645305b137017b91b0)
 - 高度な方法： タスクスケジューラでinit.batを起動します。PC起動時に実行されます。参考：[wsl2のsshサーバーに他PCから接続できるよう自動設定する \- Qiita](https://qiita.com/gengen16k/items/18262af0781fd32fc9cd#%E3%82%BF%E3%82%B9%E3%82%AF%E3%82%B9%E3%82%B1%E3%82%B8%E3%83%A5%E3%83%BC%E3%83%A9%E3%81%AE%E8%A8%AD%E5%AE%9A)  
 ノートPCをバッテリで起動した時にも自動起動させるには、「条件」タブで「コンピューターをAC電源で使用している場合のみタスクを開始する」のチェックをOFFにします。

## Dockerでhello-world

1.コマンドプロンプトからwslを実行

```
Microsoft Windows [Version 10.0.19042.1348]
(c) Microsoft Corporation. All rights reserved.

C:\Users\70108602>wsl
```

2.hello-worldを実行

```
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
2db29710123e: Pull complete
Digest: sha256:cc15c5b292d8525effc0f89cb299f1804f3a725c8d05e158653a563f15e4f685
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

To generate this message, Docker took the following steps:
 1. The Docker client contacted the Docker daemon.
 2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
    (amd64)
 3. The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading.
 4. The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal.

To try something more ambitious, you can run an Ubuntu container with:
 $ docker run -it ubuntu bash

Share images, automate workflows, and more with a free Docker ID:
 https://hub.docker.com/

For more examples and ideas, visit:
 https://docs.docker.com/get-started/
```

コンテナ実行時にプロキシの設定が必要となる場合 [防火壁の中の Docker - Qiita](https://qiita.com/jeffi7/items/3e40c59744b5801fd40a) が参考になります。


## Docker Composeでイメージビルド・コンテナ実行

1.docker-composeで使うファイル群を準備
例えば下記のサンプルを参考に、docker-compose.yml, Dockerfile等のファイルを準備します。
[Get started with Docker Compose | Docker Documentation](https://docs.docker.com/compose/gettingstarted/)

2.docker-compose.ymlにビルド時や実行時に使う環境変数を追加
参考: [docker-composeでのプロキシ設定を一つのファイルにまとめる - Qiita](https://qiita.com/SolKul/items/2561c62734958c231b24)

```
version: '3.9'
services:
  web:
    build:
      context: .
      # ビルド中にプロキシを使うための設定
      args:
        - http_proxy=$http_proxy
        - https_proxy=$https_proxy
    # 立ち上がったコンテナでプロキシを使うための設定
    environment:
      - http_proxy=$http_proxy
      - https_proxy=$https_proxy
    ports:
      - "5000:5000"
  redis:
    image: "redis:alpine"
```

上記の設定により `web` サービスのビルド時とコンテナ実行時にhttp_proxy/https_proxy環境変数が使われるようになります。
http_proxy/https_proxy環境変数は、cloud-init実行時に `/home/ubuntu/.bashrc` ファイルに追記され、bash実行時に自動でロードされ、docker-compose実行時に使用されます。

3.Docker Composeを実行

```
$ docker compose up
```

Docker ComposeはVersion2からDockerのCLIプラグインとなっていて、 `docker-compose` コマンドを直接実行するのではなく `docker` コマンドの引数として指定します。

## VSCode Remote Development拡張からWSLを使う

MicrosoftのVisual Studio CodeにRemote Development拡張機能を入れると、WSL環境を使った開発がしやすくなります。
詳細は公式の手順解説が参考になります。
[Developing in the Windows Subsystem for Linux with Visual Studio Code](https://code.visualstudio.com/docs/remote/wsl)
[WSL で VS Code の使用を開始する \| Microsoft Docs](https://docs.microsoft.com/ja-jp/windows/wsl/tutorials/wsl-vscode)

1.VSCodeにRemote Development拡張をインストール

2.コマンドプロンプトからWSL2を起動し、WSL側で `code .` を実行
WindowsのVSCode画面が開き、WSL上のソースコードを編集できます。

```
>wsl
$ cd
$ code .
```

3.VSCodeに設定を追加
ユーザ設定の `http.proxy` にプロキシURLを設定します。
Ctrl+Shift+Pでコマンドパレットよりsettings.jsonを検索し「基本設定:設定(JSON)を開く」を選び、 `"http.proxy": "http://<user>:<pass>@<proxy_host>:<proxy_port>"` を追加します。wwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwwww

4.Dockerコンテナで各言語用の実行環境を立ち上げ
Remote-Containers機能によりVSCodeから実行環境コンテナをDockerで立ち上げできます。
Visual Studio Codeの公式リファレンス: [Developing in the Windows Subsystem for Linux with Visual Studio Code](https://code.visualstudio.com/docs/remote/wsl#_advanced-opening-a-wsl-2-folder-in-a-container)
コンテナ内でプロキシ等の環境変数を設定するにはdevcontainer.jsonの `containerEnv` や `remoteEnv` やプロジェクトルートの `.env` ファイル等で設定できます。
Devコンテナにおける環境変数の設定方法: [Container environment variables](https://code.visualstudio.com/remote/advancedcontainers/environment-variables)
devcontainer.jsonの設定項目のリファレンス: [devcontainer\.json reference](https://code.visualstudio.com/docs/remote/devcontainerjson-reference)
コンテナ起動時に `Start: Downloading VS Code Server` 後にエラーとなる場合はWSL環境にある ~/.vscode-server をコンテナ内の /root/ にdocker cpすると動作します。
参考: [VS Code remote development リモートホストがインターネットにつながらない環境での接続 - Qiita](https://qiita.com/waterame/items/9979f1730cccddc8e608)

# 注意:ソースコードはWSL2側に置く
WSLからWindowsのCドライブのファイルシステムは `/mnt/c/` 以下から参照でき、WindowsからWSLのファイルシステムは `\\wsl$` から参照できますが、これらの参照はネットワークアクセスになるため遅くなります。DockerやWSLを使って開発する場合は、ソースコードはWindows側ではなく、WSL側のファイルシステムに配置する方がパフォーマンスが良くなります。
仕組みについての参考:
 [WSLのアーキテクチャ - roy-n-roy メモ](https://roy-n-roy.nyan-co.page/Windows/WSL%EF%BC%86%E3%82%B3%E3%83%B3%E3%83%86%E3%83%8A/Architecture/)
 [ASCII.jp：Win32側からWindows Subsystem for LinuxのVolFsへのアクセスが可能に (1/2)](https://ascii.jp/elem/000/001/818/1818008/)

# プロキシ情報の設定保存先
本手順でプロキシを設定した場合の認証情報の保存先は下記の通りです。情報の更新や編集や削除が必要な場合は、これらのファイルを変更します。
これらの認証情報はイメージビルドやコンテナやプログラムの実行時等に使用され、意図しない場所に保存されることがあるため、イメージ等を他者に公開する際は認証情報が誤って公開されないように注意します。

|ファイルパス|説明|
|:--|:--|
| C:\ProgramData\wsl\cloud-init\user-data | cloud-init実行時に参照され、実行後は基本的に更新不要 |
| /root/.curlrc | root権限でcurlを実行する場合に参照されるファイル |
| /etc/apt/apt.conf.d/90cloud-init-aptproxy | apt実行によるパッケージインストール時に参照されるファイル |
| /etc/default/docker | Docker Daemonによるイメージのpush/pull時に参照されるファイル |
| /home/ubuntu/.bashrc | ubuntuユーザがbashシェルを実行した時に参照されるファイル |
| C:\Users\ユーザ\AppData\Roaming\Code\User\settings.json |VSCodeの設定ファイル |
|devcontainer.jsonの containerEnv や remoteEnv やプロジェクトルートの .env ファイル等|プロジェクト毎のコンテナの環境変数|

# その他参考資料
- [Developing in the Windows Subsystem for Linux with Visual Studio Code](https://code.visualstudio.com/docs/remote/wsl)
 WSLとVSCodeを使って開発する方法の公式手順です。WSL環境からVSCodeを起動したり、Dockerリモートコンテナで開発環境を立ち上げる方法が整理されています。

- [防火壁の中の Docker - Qiita](https://qiita.com/jeffi7/items/3e40c59744b5801fd40a)
 docker pull/push, コンテナの中から curlやgit clone, Docker build 中に yum install/curl/git clone 等、プロキシ環境下でやりたいこと毎に、設定方法と設定箇所が整理されていて参考になります。