私的サーバー構築日誌：Docker on ZFS

# 能書き

[私的サーバー構築日誌：仕切り直し](https://qiita.com/katz_engineer/items/1f21de2bd9072c260ae6)からの自宅サーバー構築の続きです。

[前回](https://qiita.com/katz_engineer/items/4a3856b27d13ebcd1890)はLXDをインストールして、一通り触ってみました。今回はLXDと併存する形でDockerをインストールします。

但し[Dockerとの同時インストールはLXDの公式も否定的である](https://lxd-ja.readthedocs.io/ja/latest/howto/network_bridge_firewalld/#lxd-docker)ようです。その辺も確認します。

## 目標

- Dockerでアプリケーションコンテナを起動
- Dockerコンテナに外部からアクセス
- LXDのシステムコンテナも同時に動く事を確認

## 参考文献

- [自宅サーバー構築譚：Docker - Qiita](https://qiita.com/katz_engineer/items/f3d6ab54e5db711b03ab)
- [Install Docker Engine on Ubuntu - Docker Engine - docker docs](https://docs.docker.com/engine/install/ubuntu/)
- [Docker Composeを使ってサクッとNginxコンテナを起動する - Qiita](https://qiita.com/NagaokaKenichi/items/c03366f0b84b12c0a30d)
- [コマンドでDockerコンテナを停止・削除、イメージの削除をする - Qiita](https://qiita.com/shisama/items/48e2eaf1dc356568b0d7)
- [LXD と Docker の問題を回避する - ファイアウォールを設定するには](https://lxd-ja.readthedocs.io/ja/latest/howto/network_bridge_firewalld/#lxd-docker)
- [LXDとDockerを同時利用するためにiptables設定を調整 - hnakamur's blog](https://hnakamur.github.io/blog/2022/06/18/adjust-iptables-for-lxd-and-docker/)

zfsを利用する時には`/var/lib/docker`を独立したデータセットにしておいた方が、管理上、都合が良いです。

- [ZFS on linuxでdockerを使う - Zenn](https://zenn.dev/yakumo/articles/dad85dd47555ef)

事前にLXDをインストールしている事が、今回の前提条件です。

- [私的サーバー構築日誌：LXD- Qiita](https://qiita.com/katz_engineer/items/4a3856b27d13ebcd1890)

# アプリケーションコンテナDocker

## スナップショット

今回の[スナップショット](https://qiita.com/katz_engineer/items/f3bd2f7af2ba49db156c#%E3%82%B9%E3%83%8A%E3%83%83%E3%83%97%E3%82%B7%E3%83%A7%E3%83%83%E3%83%88%E3%81%AE%E4%BD%9C%E6%88%90)はこんな感じです。

```console
sudo zfs snapshot tank/root/ubuntu@$(date +%Y%m%d_%H%M%S)_before_Docker
```

## zfs

最初にdocker用のデータセットを作ります。

```console
sudo zfs create -o mountpoint=/var/lib/docker tank/docker
```

## インストール

定番の`apt update`。

```console
sudo apt update
```

Dockerのaptリポジトリを追加するのに必要な道具をインストールします。

```console
sudo apt install -y ca-certificates curl gnupg
```

DockerのGPG鍵を追加します。

```console
sudo install -m 0755 -d /etc/apt/keyrings
```

```console
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
```

```console
sudo chmod a+r /etc/apt/keyrings/docker.gpg
```

リポジトリのセットアップ。

```console
echo "deb [arch="$(dpkg --print-architecture)" signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu "$(. /etc/os-release && echo "$VERSION_CODENAME")" stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
```

そして再度`apt update`です。

```console
sudo apt update
```

ここでいよいよ Docker Engine のインストール！最新バージョンが良いでしょう。

```console
sudo apt install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
```

インストールは以上となります。

```console
docker --version
```

## `/etc`

`/etc`はこんな風に変化しました。

```console
$ sudo svn st /etc
?       /etc/apt/keyrings/docker.gpg
?       /etc/apt/sources.list.d/docker.list
?       /etc/bash_completion.d
?       /etc/containerd
?       /etc/default/docker
?       /etc/docker
M       /etc/group
M       /etc/gshadow
?       /etc/init.d/docker
M       /etc/ld.so.cache
?       /etc/perl
?       /etc/rc0.d/K01docker
?       /etc/rc1.d/K01docker
?       /etc/rc2.d/S01docker
?       /etc/rc3.d/S01docker
?       /etc/rc4.d/S01docker
?       /etc/rc5.d/S01docker
?       /etc/rc6.d/K01docker
?       /etc/systemd/system/multi-user.target.wants/containerd.service
?       /etc/systemd/system/multi-user.target.wants/docker.service
?       /etc/systemd/system/multi-user.target.wants/snap-core20-2015.mount
?       /etc/systemd/system/snap-core20-2015.mount
?       /etc/systemd/system/snapd.mounts.target.wants/snap-core20-2015.mount
?       /etc/systemd/system/sockets.target.wants/docker.socket
```

- いつもの`ld.so.cache`、init.d関連、systemd関連。
- perlの`/etc`が追加された模様です。中身を調べるとlibnetとかいう物のようです。
- docker関連の`/etc`設定。
- `/etc/containerd`というのがありますが、containerdとはコンテナのライフサイクルを管理する為のDockerコンポーネントの一つ、らしい。
- `/etc/bash_completion.d`はbashの補完スクリプト…？何やってくれるんでしょうね。
- keyringsとsources.list.dは、恐らく、Dockerインストールに先立って入れたキーなどでしょう。
- dockerグループが追加されています（groupとgshadow）

Subversionで登録します。

```console
sudo svn st /etc | grep "^?" | cut -b9- | xargs -I{} sudo find {} -type f -or -type d -or -type l | xargs -t sudo svn add
```

```console
sudo svn ci /etc -m"install Docker"
```

## ユーザーグループ

dockerグループに所属しているユーザーならば、`docker`コマンド実行時に`sudo`は不要のようです。

```console
sudo usermod -aG docker $USER
```

自分自身のグループを変更したので、反映させる為に一度ログアウトしてログインし直します。

```console
logout
```

正しく動作できるかの確認は、下記を実行します。

```console
docker run hello-world
```

「hello, world」の1行を表示するだけかと思ったら、何だか色々とメッセージを表示してくれますね。一瞬、エラーが発生したのかと勘違いしました。

終わった後もDockerが残っています。

```console
$ docker ps -a
CONTAINER ID   IMAGE         COMMAND    CREATED          STATUS                      PORTS     NAMES
302ff60f2e92   hello-world   "/hello"   16 seconds ago   Exited (0) 15 seconds ago             gifted_wright
```

zfs にも痕跡が残っています。

```console
$ zfs list -r tank/docker
NAME                                                                                USED  AVAIL     REFER  MOUNTPOINT
tank/docker                                                                         716K  1.80T      456K  /var/lib/docker
tank/docker/0212ade3da9f427cf650704111949e1f7e3a516660788ba6a65906a880649b48         64K  1.80T      116K  legacy
tank/docker/0212ade3da9f427cf650704111949e1f7e3a516660788ba6a65906a880649b48-init    88K  1.80T      116K  legacy
tank/docker/e100ba7cebe0945b77644f7cd60774da7119b74599aecad44171e2de3bd74e78        108K  1.80T      108K  legacy
```

綺麗に消しましょう。

```console
docker ps -aq | xargs -rt docker rm
docker images -q | xargs -rt docker rmi
```

zfs上の痕跡も消えます。

```console
$ zfs list -r tank/docker
NAME          USED  AVAIL     REFER  MOUNTPOINT
tank/docker   368K  1.80T      368K  /var/lib/docker
```

上手くいったので`/etc`を確認。

```console
sudo svn st /etc
```

グループ追加の影響が出てます。

```console
$ sudo svn st /etc
M       /etc/group
M       /etc/gshadow
```

いつもの手順で登録。

```console
sudo svn st /etc | grep "^?" | cut -b9- | xargs -I{} sudo find {} -type f -or -type d -or -type l | xargs -rt sudo svn add
```

```console
sudo svn ci /etc -m"set $USER's group"
```

# ネットワークについて

## LXD

### 軽くチェック

どうやらUbuntuServerとXubuntuとで、ネットワークの挙動が違うようです。どなたか詳しい方がいらっしゃいましたら、詳細について記事を書いて、コメントください。私はお手上げです。

Xubuntuの場合、この時点でLXDコンテナはネットワークが遮断されたような挙動になります。これについては[以前の記事](https://qiita.com/katz_engineer/items/f3d6ab54e5db711b03ab#%E3%83%8D%E3%83%83%E3%83%88%E3%83%AF%E3%83%BC%E3%82%AF%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6)を参照ください。UbuntuServerでは問題無いようなので、今回は省略します。

取り敢えず、ネットワークの確認だけ。LXDコンテナ内でpingを実行してみます。

```console
lxc launch images:ubuntu/22.04 test --device eth0,ipv4.address=192.168.0.2
```

```console
lxc exec test -- ping -c2 example.com
```

成功します。

```console
$ lxc launch images:ubuntu/22.04 test --device eth0,ipv4.address=192.168.0.2
Creating test
Starting test
$ lxc exec test -- ping -c2 example.com
PING example.com (93.184.216.34) 56(84) bytes of data.
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=1 ttl=52 time=94.3 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=52 time=110 ms

--- example.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 94.276/102.383/110.491/8.107 ms
```

### 動作確認

コンテナ内から外部へのネットワーク接続を確認できたら、ソフトをインストールしてみます。

```console
lxc exec -- test apt install -y nginx
```

外部からコンテナ内への接続を定義。

- `lxdbr0`は今回のLXDコンテナのブリッジネットワーク
- `172.16.1.2`はホストマシンのIPアドレス
- `192.168.0.2`はLXDコンテナのIPアドレス

※[前回](https://qiita.com/katz_engineer/items/74d7b357750e51e9c61c)の続きで`lxdbr0`の設定が残っていたら、この`create`は飛ばして次の`port add`だけ実行してください。

```console
lxc network forward create lxdbr0 172.16.1.2
```

```console
lxc network forward port add lxdbr0 172.16.1.2 tcp 10080 192.168.0.2 80
```

ここで……  
**食らえ！HTTP/0.9！**

```console
echo -en "GET /\r\n" | nc 172.16.1.2 10080
```

なぜ化石のような規格のHTTP/0.9を使うかと言いますとですね。レスポンスにヘッダが付かないというメリットがあるんですね。
だからこんな荒技も出来るんです。

```console
diff -s --label GET <(echo -en "GET /\r\n" | nc 172.16.1.2 10080) --label IN_CONTAINER <(lxc file pull test/var/www/html/index.nginx-debian.html -)
```

実行結果はこうなります。

```console
$ diff -s --label GET <(echo -en "GET /\r\n" | nc 172.16.1.2 10080) --label IN_CONTAINER <(lxc file pull test/var/www/html/index.nginx-debian.html -)
Files GET and IN_CONTAINER are identical
```

美しい……

以上でLXDの方は動作確認できました。

## Docker

今度はDockerです。

まず設定ファイル`docker-compose.yml`を準備。難しい事はしないので`Dockerfile`は要りません。

```console
mkdir ~/nginx
cd ~/nginx
cat >docker-compose.yml <<___
version: '3'
services:
  nginx:
    image: nginx:latest
    build: .
    ports:
      - "20080:80"
___
```

バックグラウンドで起動します。

```console
docker compose up -d
```

今度もHTTP/0.9でアクセスしますが……  

今回は、`docker compose cp`コマンドで出力にハイフン - を指定する（これは標準出力を表す）と、良く分からないデータを頭にくっつけやがる（そして抑制できない）という謎仕様の為、1行に纏める事が出来ませんでした。
なぜかプロセス置換もエラーになってしまってダメです。

仕方が無いので`/tmp`に一時ファイルを作成しました。

```console
docker compose cp nginx:/usr/share/nginx/html/index.html /tmp/test.html >&/dev/null
diff -s --label GET <(echo -en "GET /\r\n" | nc 172.16.1.2 20080) --label IN_CONTAINER /tmp/test.html
```

実行結果。

```console
$ docker compose cp nginx:/usr/share/nginx/html/index.html /tmp/test.html >&/dev/null
$ diff -s --label GET <(echo -en "GET /\r\n" | nc 172.16.1.2 20080) --label IN_CONTAINER /tmp/test.html
Files GET and IN_CONTAINER are identical
```

ま、とにかく、Dockerコンテナがちゃんと動いている事は間違い無いようです。

確認が終わったら、綺麗に消しましょう。

```console
docker compose down --rmi all --volumes
cd ..
rm -r nginx
```

# 仕舞い

今回はDockerをインストールし、LXDとDockerが同時に稼働できる事を確認しました。