自宅サーバー構築譚：Docker

# 能書き

[自宅サーバー構築譚：基本構想](https://qiita.com/katz_engineer/items/cb592be44daa28a51d9b)に基づく自宅サーバー構築、[Ubuntu22.04LTSインストールその2](https://qiita.com/katz_engineer/items/033471b69143bf76650e)の続きです。

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

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

# 目標

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

# 参考文献

- [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/)

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

- [自宅サーバー構築譚：LXD - Qiita](https://qiita.com/katz_engineer/items/74d7b357750e51e9c61c)

# スナップショット

今回の[スナップショット](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_install_Docker
```

```console
sudo zfs snapshot tank/ROOT/ubuntu/var/lib/apt@$(date +%Y%m%d_%H%M%S)_before_install_Docker
```

```console
sudo zfs snapshot tank/ROOT/ubuntu/var/lib/dpkg@$(date +%Y%m%d_%H%M%S)_before_install_Docker
```

```console
sudo zfs snapshot tank/ROOT/ubuntu/var/lib/docker@$(date +%Y%m%d_%H%M%S)_before_install_Docker
```

```console
sudo zfs snapshot tank/ROOT/ubuntu/var/lib/containerd@$(date +%Y%m%d_%H%M%S)_before_install_Docker
```

# インストール

## 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
?       /etc/init.d/docker
M       /etc/ld.so.cache
?       /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/sockets.target.wants/docker.socket
```

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
REPOSITORY    TAG       IMAGE ID       CREATED         SIZE
hello-world   latest    feb5d9fea6a5   19 months ago   13.3kB
```

綺麗に消しましょう。

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

# ネットワークについて

## LXD

### 軽くチェック

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

Xubuntuの場合、この時点でLXDコンテナはネットワークが遮断されたような挙動になります。

```console
$ lxc start container-name
$ lxc exec container-name -- ping -c2 example.com
PING example.com (93.184.216.34) 56(84) bytes of data.

--- example.com ping statistics ---
2 packets transmitted, 0 received, 100% packet loss, time 1006ms
```

### ダメだった場合の対応

※UbuntuServerの場合は前節でpingが通りますので、この節の設定は不要です。

問題は解決方法ですが。

参考文献：[LXD と Docker の問題を回避する](https://lxd-ja.readthedocs.io/ja/latest/howto/network_bridge_firewalld/#lxd-docker)

これに従うと何やら訳のわからないエラーが。

```console
$ iptables -I $USER -o lxdbr0 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
iptables v1.8.7 (nf_tables): Couldn't load match `conntrack':No such file or directory

Try `iptables -h' or 'iptables --help' for more information.
```

意外と当てになりませんな公式。

こちらの参考文献に従ったら成功しました。

参考文献：[LXDとDockerを同時利用するためにiptables設定を調整 - hnakamur's blog](https://hnakamur.github.io/blog/2022/06/18/adjust-iptables-for-lxd-and-docker/)

具体的には次の2つのコマンドです。

```console
sudo iptables -I FORWARD -o lxdbr0 -m comment --comment "generated for LXD network lxdbr0" -j ACCEPT
```

```console
sudo iptables -I FORWARD 2 -i lxdbr0 -m comment --comment "generated for LXD network lxdbr0" -j ACCEPT
```

こんな感じ。

```console
$ lxc exec container-name -- 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=119 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=52 time=117 ms

--- example.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 2561ms
rtt min/avg/max/mdev = 116.663/117.585/118.508/0.922 ms
```

但しこれ、起動する度に実行の必要があるとの事。それについても解決策が載っていました。ありがたや。

しかし、私の手順でインストールしていると、なぜか`nft`コマンドを使えません。UbuntuServerだと使えるようなので、Xubuntuだけかも知れませんが。

```console
$ nft
-bash: nft: command not found
```

仕方無いので`iptables`コマンドを使った設定にしておきます。

```console
lxd_network=lxdbr0
cat <<___ | sudo tee /etc/systemd/system/iptables-for-lxd-with-docker.service >/dev/null
[Unit]
Description=Add iptables rules for LXD coexisting with Docker
After=docker.service

[Service]
Type=oneshot
# https://discuss.linuxcontainers.org/t/lxd-and-docker-firewall-redux-how-to-deal-with-forward-policy-set-to-drop/9953/7
ExecStart=iptables -I FORWARD -o $lxd_network -m comment --comment "generated for LXD network $lxd_network" -j ACCEPT
ExecStart=iptables -I FORWARD 2 -i $lxd_network -m comment --comment "generated for LXD network $lxd_network" -j ACCEPT

[Install]
WantedBy=multi-user.target
___
```

```console
sudo systemctl daemon-reload
```

```console
sudo systemctl enable --now iptables-for-lxd-with-docker
```

これで、再起動してもLXDコンテナでネットワークがOKになります。

```console
sudo reboot
```

```console
$ lxc exec container-name -- 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=117 ms
64 bytes from 93.184.216.34 (93.184.216.34): icmp_seq=2 ttl=52 time=118 ms

--- example.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1258ms
rtt min/avg/max/mdev = 116.933/117.549/118.166/0.616 ms
```

### 動作確認

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

```console
lxc exec -- container-name apt install -y nginx
```

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

- `lxdbr0`は今回のLXDコンテナのブリッジネットワーク
- `172.16.1.3`はホストマシンの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.3
```

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

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

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

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

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

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

```console
$ diff -s --label GET <(echo -en "GET /\r\n" | nc 172.16.1.3 10080) --label IN_CONTAINER <(lxc file pull container-name/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.3 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.3 20080) --label IN_CONTAINER /tmp/test.html
Files GET and IN_CONTAINER are identical
```

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

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

```console
docker compose down --rmi all --volumes
```

# 仕舞い

`/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"Docker with LXD setting"
```

今回はDockerをインストールし、LXDとDockerが同時に稼働できる事を確認しました。Xubuntuの場合はダメのようですが、対応する設定をして、そして同時に稼働できました。
やったね:thumbsup_tone1: