自宅サーバー構築譚：LXD

# 能書き

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

少々考えている事がありまして、このタイミングでコンテナを構築します。それもLXDとDockerを両方という贅沢仕様。意味が無い！と周囲に一蹴されたのですが、私なりに必然性を持った計画なのです。

ただ、[Dockerとの同時インストールはLXDの公式も否定的である](https://lxd-ja.readthedocs.io/ja/latest/howto/network_bridge_firewalld/#lxd-docker)ので、もしかすると試行錯誤の末に諦めるかも知れません。

なお「LXD」の正しい読み方は「レクスディー」のようです。

# 目標

- LXDでシステムコンテナを起動
- コンテナのネットワークをブリッジ接続して、外部からアクセス
- ネットワークのIPアドレスは固定

# 参考文献

- [第582回いろいろなディストリビューションでsnapとLXDを利用する - Ubuntu Weekly Recipe - gihyo.jp](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0582)
- [snapパッケージの関連ディレクトリ - 第714回 Firefoxを含むsnapパッケージとの付き合い方 - gihyo.jp](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0714#sec4)
- [Ubuntuでsnapパッケージを管理する！ - LAB4ICT](https://lab4ict.com/system/archives/919)
- [LXD ドキュメント](https://lxd-ja.readthedocs.io/ja/latest/)
- [外部からコンテナへの通信について - LXDにおけるネットワーク通信について - LXDを使用して瞬時にサーバを量産する（Ubuntu18.04）](https://nwengblog.com/ubuntu-lxd/)

# スナップショット

後述の通り、LXDはsnapというパッケージ管理システムでインストールされています。これは関連するディレクトリが普通とは別になっているので、それを理解する必要があります。

参考文献：[snapパッケージの関連ディレクトリ - 第714回 Firefoxを含むsnapパッケージとの付き合い方 - gihyo.jp](https://gihyo.jp/admin/serial/01/ubuntu-recipe/0714#sec4)

スナップショットを撮ります。

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

## ロールバックする場合の注意

ここまでロールバックする事などまず無いと思いますが。もしもロールバックする場合、4つのスナップショットの他に、ZFSストレージプールを破棄する必要があります。そして少々面倒臭い手順が必要です。

まず全コンテナを停止して削除します。

```console
lxc list -c n -f csv | xargs -rt lxc stop
lxc list -c n -f csv | xargs -rt lxc delete
```

ZFSストレージプールを削除します。
後述しますが、ZFSストレージプール名としてデフォルト値を選んだ場合（紛らわしいですがこの場合は`default`という名前になります）次のコマンドを実行します。

```console
sudo zpool destroy default
```

そしてZFSスナップショットまでロールバックします。

```console
zfs list -t snapshot -o name -H | grep before_LXD | xargs -rtL 1 sudo zfs rollback
```

ここまでやってからマシン再起動で、ロールバックを反映します。

```console
sudo reboot
```

# インストール

毎度おなじみの`apt update`。

```console
sudo apt update
```

Ubuntuには最初からsnapdがインストールされている、との事…だったんですが、Ubuntuサーバーにはインストールされていないようです。そこでまずはsnapdのインストールから。
インストール済ならばこの手順は不要です。

```console
sudo apt install -y snapd
```

これで`snap`コマンドを使えます。

```console
snap --version
```

LXDについて。

```console
snap info lxd
```

現在のLTS版は5.0です。これをインストールします。

```console
sudo snap install lxd --channel=5.0/stable
```

インストールを確認。

```console
lxd --version
```

# 初期化

コンテナ構築前にlxdの初期化が必要です。対話式の設定は色々と解説記事もあるようですので、ここでは非対話式で設定してみます。

ほぼ全てデフォルトですが、3箇所だけ。

- ネットワークブリッジのIPv4アドレスは固定で`192.168.0.1/24`とします。
- IPv6は`none`にしました。一般論ですが、トラブルの元になる場合が散見されるので。きちんと理解していれば良いんですが、私はまだIPv6を理解していないのです。
- イメージの自動更新はしません。安定稼働していたサーバがある日突然停止した、という事故が怖いので。

参考文献：[LXDを初期化するには - LXDを使い始めるには](https://lxd-ja.readthedocs.io/ja/latest/howto/initialize/)

```console
lxd init --preseed <<___
config:
  images.auto_update_interval: "0"
networks:
- config:
    ipv4.address: 192.168.0.1/24
    ipv4.nat: "true"
    ipv6.address: none
  description: ""
  name: lxdbr0
  type: ""
  project: default
storage_pools:
- config:
    size: 30GiB
  description: ""
  name: default
  driver: zfs
profiles:
- config: {}
  description: ""
  devices:
    eth0:
      name: eth0
      network: lxdbr0
      type: nic
    root:
      path: /
      pool: default
      type: disk
  name: default
projects: []
cluster: null
___
```

# コンテナ構築

## 利用可能なイメージを探す

利用可能なイメージを探します。今回はUbuntuの最新版である22.04通称jammyにします。

参考文献：[利用可能なイメージをフィルタする - 利用可能なイメージを一覧表示する - イメージを管理するには](https://lxd-ja.readthedocs.io/ja/latest/howto/images_manage/#images-manage-filter)

```console
lxc image list images: ubuntu jammy architecture=x86_64 type=container
```

出力例。

```console
$ lxc image list images: ubuntu jammy architecture=x86_64 type=container
To start your first container, try: lxc launch ubuntu:22.04
Or for a virtual machine: lxc launch ubuntu:22.04 --vm

+-----------------------------+--------------+--------+-------------------------------------+--------------+-----------+----------+-------------------------------+
|            ALIAS            | FINGERPRINT  | PUBLIC |             DESCRIPTION             | ARCHITECTURE |   TYPE    |   SIZE   |          UPLOAD DATE          |
+-----------------------------+--------------+--------+-------------------------------------+--------------+-----------+----------+-------------------------------+
| ubuntu/jammy (7 more)       | 633d8a9878c2 | yes    | Ubuntu jammy amd64 (20230415_07:42) | x86_64       | CONTAINER | 116.44MB | Apr 15, 2023 at 12:00am (UTC) |
+-----------------------------+--------------+--------+-------------------------------------+--------------+-----------+----------+-------------------------------+
| ubuntu/jammy/cloud (3 more) | 6c1ee3154ef0 | yes    | Ubuntu jammy amd64 (20230415_07:43) | x86_64       | CONTAINER | 133.32MB | Apr 15, 2023 at 12:00am (UTC) |
+-----------------------------+--------------+--------+-------------------------------------+--------------+-----------+----------+-------------------------------+
```

因みに、`jammy`ではなくて`22.04`と指定しても同じ結果になりました。

```console
$ lxc image list images: ubuntu 22.04 architecture=x86_64 type=container
+-----------------------------+--------------+--------+-------------------------------------+--------------+-----------+----------+-------------------------------+
|            ALIAS            | FINGERPRINT  | PUBLIC |             DESCRIPTION             | ARCHITECTURE |   TYPE    |   SIZE   |          UPLOAD DATE          |
+-----------------------------+--------------+--------+-------------------------------------+--------------+-----------+----------+-------------------------------+
| ubuntu/jammy (7 more)       | 633d8a9878c2 | yes    | Ubuntu jammy amd64 (20230415_07:42) | x86_64       | CONTAINER | 116.44MB | Apr 15, 2023 at 12:00am (UTC) |
+-----------------------------+--------------+--------+-------------------------------------+--------------+-----------+----------+-------------------------------+
| ubuntu/jammy/cloud (3 more) | 6c1ee3154ef0 | yes    | Ubuntu jammy amd64 (20230415_07:43) | x86_64       | CONTAINER | 133.32MB | Apr 15, 2023 at 12:00am (UTC) |
+-----------------------------+--------------+--------+-------------------------------------+--------------+-----------+----------+-------------------------------+
```

# コンテナ作成

コンテナを作成します。IPv4アドレスを固定で`192.168.0.2`にします。

参考文献：[インスタンスを作成するには](https://lxd-ja.readthedocs.io/ja/latest/howto/instances_create/)

```console
lxc init images:ubuntu/22.04 container-name --device eth0,ipv4.address=192.168.0.2
```

コンテナを作成しましたが停止状態です。

```console
$ lxc list
+----------------+---------+------+------+-----------+-----------+
|      NAME      |  STATE  | IPV4 | IPV6 |   TYPE    | SNAPSHOTS |
+----------------+---------+------+------+-----------+-----------+
| container-name | STOPPED |      |      | CONTAINER | 0         |
+----------------+---------+------+------+-----------+-----------+
```

開始コマンドは下記です。

```console
lxc start container-name
```

確認は`list`コマンドで。

```console
$ lxc list
+----------------+---------+--------------------+------+-----------+-----------+
|      NAME      |  STATE  |        IPV4        | IPV6 |   TYPE    | SNAPSHOTS |
+----------------+---------+--------------------+------+-----------+-----------+
| container-name | RUNNING | 192.168.0.2 (eth0) |      | CONTAINER | 0         |
+----------------+---------+--------------------+------+-----------+-----------+
```

`list`コマンドのオプションも色々あります。`lxc list --help`で調べてみて下さい。
例えばコンテナ名とIPv4アドレスを取得してCSVで出力する場合は下記になります。

```console
$ lxc list -c n4 -f csv
container-name,192.168.0.2 (eth0)
```

また、`init`と`start`を同時に実行するのが`launch`です。

```console
$ lxc launch images:ubuntu/jammy container-name2 --device eth0,ipv4.address=192.168.0.3
Creating container-name2
Starting container-name2
$ lxc list
+-----------------+---------+--------------------+------+-----------+-----------+
|      NAME       |  STATE  |        IPV4        | IPV6 |   TYPE    | SNAPSHOTS |
+-----------------+---------+--------------------+------+-----------+-----------+
| container-name  | RUNNING | 192.168.0.2 (eth0) |      | CONTAINER | 0         |
+-----------------+---------+--------------------+------+-----------+-----------+
| container-name2 | RUNNING | 192.168.0.3 (eth0) |      | CONTAINER | 0         |
+-----------------+---------+--------------------+------+-----------+-----------+
```

コンテナの停止は`stop`です。

```console
$ lxc stop container-name container-name2
$ lxc list
+-----------------+---------+------+------+-----------+-----------+
|      NAME       |  STATE  | IPV4 | IPV6 |   TYPE    | SNAPSHOTS |
+-----------------+---------+------+------+-----------+-----------+
| container-name  | STOPPED |      |      | CONTAINER | 0         |
+-----------------+---------+------+------+-----------+-----------+
| container-name2 | STOPPED |      |      | CONTAINER | 0         |
+-----------------+---------+------+------+-----------+-----------+
```

コンテナの削除は`delete`です。

```console
$ lxc delete container-name2
$ lxc list
+----------------+---------+------+------+-----------+-----------+
|      NAME      |  STATE  | IPV4 | IPV6 |   TYPE    | SNAPSHOTS |
+----------------+---------+------+------+-----------+-----------+
| container-name | STOPPED |      |      | CONTAINER | 0         |
+----------------+---------+------+------+-----------+-----------+
```

なお稼働状態だと削除できません。

```console
$ lxc start container-name
$ lxc list
+----------------+---------+--------------------+------+-----------+-----------+
|      NAME      |  STATE  |        IPV4        | IPV6 |   TYPE    | SNAPSHOTS |
+----------------+---------+--------------------+------+-----------+-----------+
| container-name | RUNNING | 192.168.0.2 (eth0) |      | CONTAINER | 0         |
+----------------+---------+--------------------+------+-----------+-----------+
$ lxc delete container-name
Error: The instance is currently running, stop it first or pass --force
```

# コンテナ内ファイルへのアクセス

## ファイルのpushとpull

参考文献：[インスタンス内のファイルにアクセスするには](https://lxd-ja.readthedocs.io/ja/latest/howto/instances_access_files/)

テストに使用するファイルを用意します。中身は何でもいいのですが。

```console
cat <<___ >/tmp/twinkle.txt
Twinkle, twindle, little star,
How I wonder what you are!
___
```

コンテナの中へ送付します。後述しますが、Ubuntuのコンテナにはデフォルトの一般ユーザーとしてubuntuが用意されていましたので、そのホームディレクトリに置いてみます。

```console
lxc file push /tmp/twinkle.txt container-name/home/ubuntu/littlestar.txt
```

今度はコンテナ内から取得。

```console
lxc file pull container-name/home/ubuntu/littlestar.txt /tmp
```

2つのファイルが一致する事を確認します。

```console
$ diff -s /tmp/twinkle.txt /tmp/littlestar.txt
Files /tmp/twinkle.txt and /tmp/littlestar.txt are identical
```

因みにファイルパスとしてハイフン`-`を指定すると、標準入出力になるそうです。
コンテナ内に送付する場合のコマンド例：

```console
lxc file push - container-name/home/ubuntu/up.txt <<___
Up above the world so high,
Like a diamond in the sky.
___
```

コンテナから取得する場合の実行結果例：

```console
$ lxc file pull container-name/home/ubuntu/up.txt -
Up above the world so high,
Like a diamond in the sky.
```

## 直接編集

コンテナ内のファイルを直接編集する事も可能です。エディタはデフォルトでnanoのようです。私にとって使い慣れたviを指定する場合は、環境変数`EDITOR`で指定します。

```console
EDITOR=vi lxc file edit container-name/home/ubuntu/littlestar.txt
```

## 削除

コンテナ内のファイルを直接削除する事も可能です。

```console
lxc file delete container-name/home/ubuntu/up.txt
```

# コンテナ内でコマンド実行

## コマンドの実行

`exec`サブコマンドを指定します。オプションを指定する場合は`--`で区切ります。

参考文献：[インスタンス内でコマンドを実行するには](https://lxd-ja.readthedocs.io/ja/latest/instance-exec/)

例えば：

```console
lxc exec container-name -- ls -l /home/ubuntu
```

Ubuntuコンテナでは最初からubuntuユーザーが用意されていました。そう言ったコンテナ内ユーザーとしてコンテナに入る為には下記コマンドを実行します。

```console
lxc exec container-name -- su --login ubuntu
```

ところで、このubuntuユーザのパスワードはubuntuという記事を見かけましたが、実際に入力するとどうも違うようです。

```console
$ passwd
Changing password for ubuntu.
Current password:
passwd: Authentication token manipulation error
passwd: password unchanged
```

```console
exit
```

仕方が無いので、rootでubuntuユーザのパスワードを設定しておきましょう。

```console
lxc exec container-name passwd ubuntu <<___
ubuntu
ubuntu
___
```

## コンソールに接続

コンソールに接続してみます。

参考文献：[コンソールにアクセスするには](https://lxd-ja.readthedocs.io/ja/latest/howto/instances_console/)

```console
lxc console container-name
```

これでコンソールに接続します。わかりにくいですが、ログインの要求が始まっています。

```console
$ lxc console container-name
To detach from the console, press: <ctrl>+a q
ubuntu
Password:
Welcome to Ubuntu 22.04.2 LTS (GNU/Linux 5.15.0-69-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

 * Introducing Expanded Security Maintenance for Applications.
   Receive updates to over 25,000 software packages with your
   Ubuntu Pro subscription. Free for personal use.

     https://ubuntu.com/pro

The programs included with the Ubuntu system are free software;
the exact distribution terms for each program are described in the
individual files in /usr/share/doc/*/copyright.

Ubuntu comes with ABSOLUTELY NO WARRANTY, to the extent permitted by
applicable law.

To run a command as administrator (user "root"), use "sudo <command>".
See "man sudo_root" for details.

ubuntu@container-name:~$ exit
logout

Ubuntu 22.04.2 LTS container-name console

container-name login:
```

このコンソールから抜けるには Ctrl-a の後に q を入力します。

# ネットワーク

## コンテナ内から外部へ

デフォルトで、コンテナ内から外部へアクセスできます。名前解決も出来ています。

```console
lxc exec container-name -- ping -c2 example.com
```

実行結果。

```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=119 ms

--- example.com ping statistics ---
2 packets transmitted, 2 received, 0% packet loss, time 1002ms
rtt min/avg/max/mdev = 118.755/118.767/118.779/0.012 ms
```

## 外部からコンテナ内へ

まずは動作確認に備えて、`openssh-server`をインストールしておきます。

```console
lxc exec container-name -- apt install -y openssh-server
```

外部からコンテナ内へのアクセスについては、ポートフォワーディングを設定するのが最も理解し易いようです。
ただ、参考文献に載っていた方法は、少々癖があってわかりにくい。

参考文献：[ネットワークフォワードを設定するには](https://lxd-ja.readthedocs.io/ja/latest/howto/network_forwards/)

まず「ネットワークフォワード」と呼ばれる構造を作成します。

- `lxdbr0`は最初の初期化時に作られたネットワークの名前
- 172.16.1.3はホストのIPアドレス（0.0.0.0では上手くいかないようです）

```console
lxc network forward create lxdbr0 172.16.1.3
```

それから、この「ネットワークフォワード」に、ポートの割り当てを追加します。

- 192.168.0.2はコンテナのIPアドレス（上述の通り、コンテナ作成時に指定したIPv4アドレス）

```console
lxc network forward port add lxdbr0 172.16.1.3 tcp 10022 192.168.0.2 22
```

これで外部からコンテナへ接続が可能になります。

```console
ssh -p 10022 ubuntu@172.16.1.3
```

少し異なる設定方法として、下記のコマンドを実行すると、一発で設定できる模様。

参考文献：[外部からコンテナへの通信について - LXDにおけるネットワーク通信について - LXDを使用して瞬時にサーバを量産する（Ubuntu18.04）](https://nwengblog.com/ubuntu-lxd/)

```console
lxc config device add container-name sshForward proxy listen=tcp:172.16.1.3:20022 connect=tcp:192.168.0.2:22 bind=host
```

接続確認。

```console
ssh -p 20022 ubuntu@172.16.1.3
```

# 仕舞い

一通り設定できた所で、例によって`/etc`をコミットします。

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

今回の実行結果。

```console
$ sudo svn st /etc
M       /etc/cups/subscriptions.conf
M       /etc/cups/subscriptions.conf.O
M       /etc/passwd
M       /etc/shadow
?       /etc/systemd/system/multi-user.target.wants/snap-core20-1852.mount
?       /etc/systemd/system/multi-user.target.wants/snap-lxd-24322.mount
?       /etc/systemd/system/multi-user.target.wants/snap-snapd-18596.mount
?       /etc/systemd/system/multi-user.target.wants/snap.lxd.activate.service
?       /etc/systemd/system/snap-core20-1852.mount
?       /etc/systemd/system/snap-lxd-24322.mount
?       /etc/systemd/system/snap-snapd-18596.mount
?       /etc/systemd/system/snap.lxd.activate.service
?       /etc/systemd/system/snap.lxd.daemon.service
?       /etc/systemd/system/snap.lxd.daemon.unix.socket
?       /etc/systemd/system/snap.lxd.user-daemon.service
?       /etc/systemd/system/snap.lxd.user-daemon.unix.socket
?       /etc/systemd/system/snapd.mounts.target.wants
?       /etc/systemd/system/sockets.target.wants/snap.lxd.daemon.unix.socket
?       /etc/systemd/system/sockets.target.wants/snap.lxd.user-daemon.unix.socket
?       /etc/udev/rules.d/70-snap.snapd.rules
M       /etc/zfs/zfs-list.cache/tank
M       /etc/zfs/zpool.cache
```

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

残りの操作としてcloud-initを使いたいとかありますが、それはまた後日にします。
それを除けば、これでLXDを一通り操作できました。やったね:thumbsup_tone1: