【Ansible】WSL2のUbuntuにrootless Dockerをセットアップする

## 1. はじめに
Ansible初学者が、Ansible学習の一環としてWSL2上のUbuntuにrootless Dockerをセットアップしてみた手順をまとめます。
GPT-4.1の助けを借りてセットアップを実施しました。（この記事自体の作成も助けてもらいました。）
WSL2のUbuntu 22.04 LTS／24.04 LTSの両環境で動作確認済みです。
学び始めたばかりのため、インベントリー及びPlaybookの記載内容がちぐはぐな部分があるかもしれませんが、ご指摘頂ければ幸いです。

### Ansibleとは？
Ansibleは「サーバーのセットアップやアプリのデプロイを自動化できる」構成管理ツールです。
サーバー構成をYAML（人間が読みやすい形式）で記述し、複数台のマシンへの一括適用や再現性の高い構成管理が可能になります。

### rootless Dockerとは？
rootless Dockerは「一般ユーザーでもroot権限なしでDocker環境を利用できる」Dockerの運用モードです。
rootユーザーを使わないため、本番運用や開発用に安全性を重視したい場合に有効です。


## 2. Ansibleのインストール手順
Ansibleの公式ドキュメントを参考に、WSL2上のUbuntuにAnsibleをインストールします。
```sh
$ sudo apt update
$ sudo apt install software-properties-common
$ sudo add-apt-repository --yes --update ppa:ansible/ansible
$ sudo apt install ansible
```


## 3. セットアップに使用するディレクトリ構成
今回は学習用途としてシンプルな構成で実施しました。
```
rootless_docker_setup/
├── hosts.yml
└── docker_rootless_setup.yml
```


## 4. インベントリーの作成
`hosts.yml` を配置します。ファイル内容は学習用にシンプルなものです。
```hosts.yml
---
all:
  hosts:
    localhost:
      ansible_connection: local
```


## 5. Playbookの作成
`docker_rootless_setup.yml` を作成・配置します。こちらも学習用としてシンプルな内容になっています。
<details><summary>ymlファイル</summary>

```docker_rootless_setup.yml
---
- name: 通常のDockerのセットアップ
  hosts: localhost
  become: true

  tasks:
    - name: 依存パッケージのインストール
      apt:
        name:
          - ca-certificates
          - curl
        state: present
        update_cache: true

    - name: /etc/apt/keyringsディレクトリの作成
      file:
        path: /etc/apt/keyrings
        state: directory
        mode: '0755'

    - name: Docker公式GPGキーの追加
      get_url:
        url: https://download.docker.com/linux/ubuntu/gpg
        dest: /etc/apt/keyrings/docker.asc
        mode: '0644'
        force: yes

    - name: Dockerリポジトリ追加
      ansible.builtin.shell: |
        echo \
        "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
        $(. /etc/os-release && echo \"${UBUNTU_CODENAME:-$VERSION_CODENAME}\") stable" | tee /etc/apt/sources.list.d/docker.list > /dev/null
      args:
        creates: /etc/apt/sources.list.d/docker.list

    - name: aptキャッシュを更新（Dockerリポジトリ追加後）
      ansible.builtin.apt:
        update_cache: true

    - name: Dockerパッケージのインストール
      apt:
        name:
          - docker-ce
          - docker-ce-cli
          - containerd.io
          - docker-buildx-plugin
          - docker-compose-plugin
        state: present
        update_cache: true

    - name: docker composeコマンドの存在確認
      ansible.builtin.shell: docker compose version
      register: compose_version
      ignore_errors: true

    - debug:
        var: compose_version.stdout

    - name: 通常のDockerのserviceとsocketを停止して無効化
      systemd:
        name: "{{ item }}"
        enabled: no
        state: stopped
      loop:
        - docker.service
        - docker.socket

    - name: 既存のdocker.sockを削除
      file:
        path: /var/run/docker.sock
        state: absent

- name: rootless Dockerのセットアップ
  hosts: localhost
  become: true
  vars:
    docker_user: "vagrant"  # ←この部分は自分のWSLユーザー名に置き換えてください
  tasks:
    - name: rootless Docker依存パッケージのインストール
      apt:
        name:
          - dbus-user-session
          - uidmap
          - fuse-overlayfs
        state: present

    - name: rootless Dockerセットアップコマンドの実行
      become: false
      ansible.builtin.shell: |
        export XDG_RUNTIME_DIR=/run/user/$(id -u)
        dockerd-rootless-setuptool.sh install
      environment:
        PATH: "{{ ansible_env.PATH }}:/usr/bin:/usr/local/bin"
      args:
        executable: /bin/bash
      become_user: "{{ docker_user }}"

    - name: rootless Docker環境変数の永続化
      become: false
      lineinfile:
        path: "/home/{{ docker_user }}/.bashrc"
        line: |
          export PATH=/usr/bin:$PATH
          export DOCKER_HOST=unix:///run/user/$(id -u)/docker.sock
        insertafter: EOF
      become_user: "{{ docker_user }}"
```
</details>

Dockerのインストール手順一式に関しては、2025/05時点の公式サイトの手順に則ったものになります。
公式サイトの手順が変更された場合は再調整が必要になることをご留意ください。
常に最新の公式手順を確認するようにしてください。


## 6. Playbookの構文チェック
Playbookの構文チェックは以下のコマンドで行います。
```sh
ansible-playbook -i hosts.yml docker_rootless_setup.yml --syntax-check
```
問題なければ`playbook: docker_rootless_setup.yml`という出力のみ出てきます。
問題がある場合は下記のようになります。4行目の`task`を`aaa task`としてみた場合の出力です。
```
ERROR! 'aaa tasks' is not a valid attribute for a Play

The error appears to be in '/home/vagrant/rootless_docker_setup/docker_rootless_setup.yml': line 2, column 3, but may
be elsewhere in the file depending on the exact syntax problem.

The offending line appears to be:

---
- name: 通常のDockerのセットアップ
  ^ here
```

- このコマンドは構文チェックのみであり、`--check`オプションによるDry Run（実際の処理内容をシミュレーション）は行いません。
- なお、同じPlaybook内で「リポジトリ追加」と「追加したリポジトリからのパッケージインストール」を処理する場合、Dry Runでは失敗することがあります。
- 構文チェック自体も不要であればスキップしてください。


## 7. ansible-playbookコマンドに-Kオプションを指定して実行
コマンド実行時にsudoパスワードを要求させるため、`-K`オプションをつけて実行します。
通常のDockerをインストールする処理でroot権限が必要なために指定します。
```sh
ansible-playbook -i hosts.yml docker_rootless_setup.yml -K
```
パスワード要求をしないようにUbuntuの設定を変更する手もありますが、セキュリティリスクを鑑みて変更しないことにしました。

実行結果は下記になります。
```sh
BECOME password:

PLAY [通常のDockerのセットアップ] *************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************
[WARNING]: Platform linux on host localhost is using the discovered Python interpreter at /usr/bin/python3.12, but future
installation of another Python interpreter could change the meaning of that path. See https://docs.ansible.com/ansible-
core/2.18/reference_appendices/interpreter_discovery.html for more information.
ok: [localhost]

TASK [依存パッケージのインストール] *************************************************************************************************
ok: [localhost]

TASK [/etc/apt/keyringsディレクトリの作成] ******************************************************************************************
ok: [localhost]

TASK [Docker公式GPGキーの追加] ******************************************************************************************************
changed: [localhost]

TASK [Dockerリポジトリ追加] *********************************************************************************************************
changed: [localhost]

TASK [aptキャッシュを更新（Dockerリポジトリ追加後）] ********************************************************************************
changed: [localhost]

TASK [Dockerパッケージのインストール] ***********************************************************************************************
changed: [localhost]

TASK [docker composeコマンドの存在確認] *********************************************************************************************
changed: [localhost]

TASK [debug] ************************************************************************************************************************
ok: [localhost] => {
    "compose_version.stdout": "Docker Compose version v2.36.2"
}

TASK [通常のDockerのserviceとsocketを停止して無効化] ******************************************************************************
changed: [localhost] => (item=docker.service)
changed: [localhost] => (item=docker.socket)

TASK [既存のdocker.sockを削除] ******************************************************************************************************
changed: [localhost]

PLAY [rootless Dockerのセットアップ] ************************************************************************************************

TASK [Gathering Facts] **************************************************************************************************************
ok: [localhost]

TASK [rootless Docker依存パッケージのインストール] **********************************************************************************
changed: [localhost]

TASK [rootless Dockerセットアップコマンドの実行] ************************************************************************************
changed: [localhost]

TASK [rootless Docker環境変数の永続化] **********************************************************************************************
changed: [localhost]

PLAY RECAP **************************************************************************************************************************
localhost                  : ok=15   changed=10   unreachable=0    failed=0    skipped=0    rescued=0    ignored=0
```


## 8. rootless Dockerがセットアップされたことの確認
### バージョン情報の確認
```sh
docker version
```

### Hello-worldコンテナで動作確認
```sh
docker run hello-world
```
いずれも正常に実行できればセットアップ完了になります。


## 9. 参考にしたサイト
- [Installing Ansible on specific operating systems](https://docs.ansible.com/ansible/latest/installation_guide/installation_distros.html)
- [Install Docker Engine on Ubuntu](https://docs.docker.com/engine/install/ubuntu/)
- [Rootless mode](https://docs.docker.com/engine/security/rootless/)
- [インスタンスのブート時に Rootless Docker で compose.yml を開始する](https://qiita.com/oya_tadashi/items/8fdd8fee97cae86545ff)
- [Chapter 01 はじめに](https://zenn.dev/y_mrok/books/ansible-no-tsukaikata/viewer/chapter1)
- [Ansible 実行前の文法チェック –syntax-checkオプション](https://blog.rutake.com/techmemo/2015/09/30/ansible-%E5%AE%9F%E8%A1%8C%E5%89%8D%E3%81%AE%E6%96%87%E6%B3%95%E3%83%81%E3%82%A7%E3%83%83%E3%82%AF-syntax-check%E3%82%AA%E3%83%97%E3%82%B7%E3%83%A7%E3%83%B3/)


## 10. 補足情報
### Ubuntu 22.04でhello-worldコンテナ実行時のエラー対策
`docker run hello-world`実行時にエラーとなったので、対処法を下記に記載します。
1. `/etc/wsl.conf`を編集し、下記を追記
   ```
   [network]
   generateResolvConf=false
   ```
2. WSL2からログアウトし、PowerShell等で`wsl -t <ディストリビューション名>`で停止
3. 再ログインし`/etc/resolv.conf`が存在しないことを確認
4. `/etc/resolv.conf`を作成し、nameserverにvEthernet (WSL (Hyper-V firewall))のIPv4アドレスを記載
   - WindowsのPowerShellまたはcmdで`ipconfig /all`コマンドを実行することで確認
5. 再度WSL2を停止
6. 再ログインし`/etc/resolv.conf`の内容を確認（記載した内容のままになっていること）
7. `docker run hello-world`が正常に実行できるようになる

**注意点**
- なんらかの理由でvEthernet (WSL (Hyper-V firewall))のIPv4アドレスが変更された場合、`/etc/resolv.conf`のnameserverを手動で修正し直す必要があります。
  - 参考までに、自分が1年以上使っている別のUbuntu 22.04環境ではIPv4アドレスが変更されたことは無いです。

参考情報として実際のエラーを下記に示します。このままでは`docker pull`が使えない状態です。
```
vagrant@john:~/rootless_docker_setup$ docker run hello-world
Unable to find image 'hello-world:latest' locally
docker: Error response from daemon: Get "https://registry-1.docker.io/v2/": dial tcp: lookup registry-1.docker.io on 10.0.2.3:53: read udp 10.0.2.100:49046->10.0.2.3:53: i/o timeout

Run 'docker run --help' for more information
```

`ipconfig /all`コマンドを実行した際の出力例の一部です。念のため一部は伏せておきます。
```
イーサネット アダプター vEthernet (WSL (Hyper-V firewall)):

   接続固有の DNS サフィックス . . . . .:
   説明. . . . . . . . . . . . . . . . .: Hyper-V Virtual Ethernet Adapter
   物理アドレス. . . . . . . . . . . . .: XX-XX-XX-XX-XX-XX
   DHCP 有効 . . . . . . . . . . . . . .: いいえ
   自動構成有効. . . . . . . . . . . . .: はい
   リンクローカル IPv6 アドレス. . . . .: fe80::xxxx:xxxx:xxxx:xxxx%xx(優先)
   IPv4 アドレス . . . . . . . . . . . .: 172.26.208.1(優先) ←これを使います
   サブネット マスク . . . . . . . . . .: 255.255.240.0
```


## 11. 環境情報
- 実行PC
  - OS: Windows 11 Home 24H2
  - CPU: AMD Ryzen 5 5560U with Radeon Graphics 2.30 GHz
  - メモリ: 16GB
  - ストレージ: SSD 477GB
- WSL2バージョン: 2.5.7.0
- WSL2のUbuntu
  - Ubuntu 24.04 LTS
  - Ubuntu 22.04 LTS