自宅サーバー構築譚：LXDでcloud-initによる初期設定の自動化

# 能書き

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

[先日インストールしたLXDでコンテナを作成](https://qiita.com/katz_engineer/items/74d7b357750e51e9c61c)する際にcloud-initを使ってみました。その手順を公開します。同じ内容の記事は大量にありますが、いずれも若干バージョンが古いようで、この記事は最新版に対応した内容です。と言っても大した差はありませんが。

# 目標

`/etc`をSubversionでバージョン管理します。具体的には[以前書いた記事の通りの仕込み](https://qiita.com/katz_engineer/items/92d0b58677b8783e1fe5)です。
ついでにタイムゾーンとロケールも設定します。

- [自宅サーバー構築譚：Subversionをインストールして /etc をバージョン管理・Ubuntu 22.04](https://qiita.com/katz_engineer/items/92d0b58677b8783e1fe5)

# 参考文献

- [cloud-initを使ったLinux OSの初期設定 - Qiita](https://qiita.com/yamada-hakase/items/40fa2cbb5ed669aaa85b)
- [cloud-initを使用するには](https://lxd-ja.readthedocs.io/ja/latest/cloud-init/)
- [LXDとcloud-initを使ってコンテナインスタンスを自動作成する](https://qiita.com/yo-yamada/items/74fff9418f681acace64)
- [Quick-start tutorial with LXD](https://cloudinit.readthedocs.io/en/latest/tutorial/lxd.html)

# cloud-initを実験してみました

## コンテナを作成

cloud-init実験用コンテナを作成します。`launch`ではなくて`init`とします。

そしてイメージは`ubuntu/jammy`ではなくて`ubuntu/jammy/cloud`です。

コンテナ名は`test1`としましょう。折角なのでIPアドレスを`192.168.0.3`で固定してみます。

```console
lxc init images:ubuntu/jammy/cloud test1 --device eth0,ipv4.address=192.168.0.3
```

確認。

```console
lxc ls
```

実行結果はこうなります。STATEがSTOPPEDです。

```console
$ lxc ls
+-------+---------+------+------+-----------+-----------+
| NAME  |  STATE  | IPV4 | IPV6 |   TYPE    | SNAPSHOTS |
+-------+---------+------+------+-----------+-----------+
| test1 | STOPPED |      |      | CONTAINER | 0         |
+-------+---------+------+------+-----------+-----------+
```

## 設定

新しいバージョンだと`cloud-init.user-data`に設定するようです。設定方法は下記。
今回は実験として、`/var/tmp/test.txt`に適当な文言を出力するだけの内容です。

```console
lxc config set test1 cloud-init.user-data="$(cat <<___
#cloud-config
runcmd:
  - echo "hello, world" >/var/tmp/test.txt
___
)"
```

設定した内容を確認。

```console
lxc config show test1
```

確認結果は下記のようになります。

```console
$ lxc config show test1
architecture: x86_64
config:
  cloud-init.user-data: |-
    #cloud-config
    runcmd:
      - echo "hello, world" >/var/tmp/test.txt
  image.architecture: amd64
  image.description: Ubuntu jammy amd64 (20230427_07:42)
  image.os: Ubuntu
  image.release: jammy
  image.serial: "20230427_07:42"
  image.type: squashfs
  image.variant: cloud
  volatile.apply_template: create
  volatile.base_image: ef8308fae2cb4394f755363e108a8e59e4b1484a754f519e416557206fa6ca77
  volatile.cloud-init.instance-id: c4d4282c-a884-49e3-b821-d682774affe6
  volatile.eth0.hwaddr: 00:16:3e:69:f6:3d
  volatile.idmap.base: "0"
  volatile.idmap.next: '[{"Isuid":true,"Isgid":false,"Hostid":1000000,"Nsid":0,"Maprange":1000000000},{"Isuid":false,"Isgid":true,"Hostid":1000000,"Nsid":0,"Maprange":1000000000}]'
  volatile.last_state.idmap: '[]'
  volatile.uuid: 1f950763-ace2-4e86-8f9c-d8ae167d8f00
devices:
  eth0:
    ipv4.address: 192.168.0.3
    name: eth0
    network: lxdbr0
    type: nic
ephemeral: false
profiles:
- default
stateful: false
description: ""
```

## 確認

ここでコンテナを起動します。

```console
lxc start test1
```

起動しました。

```console
$ lxc ls
+-------+---------+--------------------+------+-----------+-----------+
| NAME  |  STATE  |        IPV4        | IPV6 |   TYPE    | SNAPSHOTS |
+-------+---------+--------------------+------+-----------+-----------+
| test1 | RUNNING | 192.168.0.3 (eth0) |      | CONTAINER | 0         |
+-------+---------+--------------------+------+-----------+-----------+
```

cloud-initが正しく動いたか確認します。

```console
lxc file pull test1/var/tmp/test.txt -
```

`hello, world`と表示されれば成功です。

```console
$ lxc file pull test1/var/tmp/test.txt -
hello, world
```

## 初回だけ動く事を確認

作成されたファイルを削除します。

```console
lxc file rm test1/var/tmp/test.txt
```

削除されました。

```console
$ lxc exec test1 ls /var/tmp/test.txt
ls: cannot access '/var/tmp/test.txt': No such file or directory
```

コンテナを止めます。

```console
lxc stop test1
lxc ls
```

止まればSTATEがSTOPPEDと表示されます。

```console
$ lxc stop test1
$ lxc ls
+-------+---------+------+------+-----------+-----------+
| NAME  |  STATE  | IPV4 | IPV6 |   TYPE    | SNAPSHOTS |
+-------+---------+------+------+-----------+-----------+
| test1 | STOPPED |      |      | CONTAINER | 0         |
+-------+---------+------+------+-----------+-----------+
```

コンテナを再開します。

```console
lxc start test1
lxc ls
```

STATEがRUNNINGとなった事を確認。

```console
$ lxc start test1
$ lxc ls
+-------+---------+------+------+-----------+-----------+
| NAME  |  STATE  | IPV4 | IPV6 |   TYPE    | SNAPSHOTS |
+-------+---------+------+------+-----------+-----------+
| test1 | RUNNING |      |      | CONTAINER | 0         |
+-------+---------+------+------+-----------+-----------+
```

さて`/var/tmp/test.txt`は如何に？

```console
lxc exec test1 ls /var/tmp/test.txt
```

作成されないのが正しいです。

```console
$ lxc exec test1 ls /var/tmp/test.txt
ls: cannot access '/var/tmp/test.txt': No such file or directory
```

ヨシ！

## 後始末

コンテナを止めて削除します。

```console
lxc stop test1
lxc rm test1
lxc ls
```

# cloud-initでいつもの初期設定を実現しました

## 作成と設定

下記の設定を実現してみます。

- [自宅サーバー構築譚：Subversionをインストールして /etc をバージョン管理・Ubuntu 22.04](https://qiita.com/katz_engineer/items/92d0b58677b8783e1fe5)

ついでにTimezoneとLocaleも設定しておきましょう。

```console
lxc init images:ubuntu/jammy/cloud test2 --device eth0,ipv4.address=192.168.0.4
lxc config set test2 cloud-init.user-data="$(cat <<___
#cloud-config
timezone: Asia/Tokyo
locale: ja_JP.utf8
package_upgrade: true
packages:
  - subversion
runcmd:
  - [mkdir, /etc/.svn_repo]
  - [svnadmin, create, /etc/.svn_repo/]
  - [mkdir, /tmp/etc]
  - [svn, import, /tmp/etc, file:///etc/.svn_repo/, -m, create project]
  - [rmdir, /tmp/etc]
  - [svn, co, file:///etc/.svn_repo/, /etc]
  - rm -v /etc/ssl/certs/NetLock*
  - [/bin/bash, -c, ls /etc/ssl/certs/*.0 | while read f; do echo -n \$f\$'\\t'; readlink \$f; done | grep NetLock | cut -f 1 | xargs -rt rm]
  - [sed, -i, -e, /NetLock/d, /etc/ca-certificates.conf.dpkg-old, /etc/ca-certificates.conf]
  - svn add /etc/*
  - [/bin/bash, -c, ls /etc/*- | xargs -rt svn revert]
  - [svn, ci, /etc, -m, first commit]
  - [svn, up, /etc]
  - /bin/bash -c "cd /etc; svn propset svn:ignore -F <(svn st | cut -b9-) ."
  - [svn, ci, /etc, -m, set svn:ignore]
___
)"
lxc start test2
```

状態を確認するには`cloud-init status`を実行します。

```console
lxc exec test2 cloud-init status
```

すぐに実行すると、まだ実行中かも知れません。

```console
$ lxc exec test2 cloud-init status
status: running
```

`--wait`オプションを付けると、完了するまで待ってくれます。

```console
$ lxc exec test2 -- cloud-init status --wait
.............................................
status: done
```

## 結果を確認

まずはタイムゾーン。`date`コマンドを実行します。

```console
lxc exec test2 date
```

UTCではなくてJSTで表示されればOK。

```console
$ lxc exec test2 date
Fri Apr 28 18:04:29 JST 2023
```

次にロケール。`localectl`コマンドを実行します。

```console
lxc exec test2 localectl
```

指定した`LANG=ja_JP.utf8`となっていればOKです。

```console
$ lxc exec test2 localectl
   System Locale: LANG=ja_JP.utf8
       VC Keymap: n/a
      X11 Layout: us
       X11 Model: pc105
```

`/etc`のSubversion管理は、ログを確認してみましょう。

```console
lxc exec test2 svn log /etc
```

成功していれば下記のようになります。

```console
$ lxc exec test2 svn log /etc
------------------------------------------------------------------------
r2 | root | 2023-04-28 20:49:30 +0900 (Fri, 28 Apr 2023) | 1 line

"set svn:ignore"
------------------------------------------------------------------------
r1 | root | 2023-04-28 20:49:28 +0900 (Fri, 28 Apr 2023) | 1 line

"first commit"
------------------------------------------------------------------------
```

## ログ

cloud-initのログは`/var/log`に出力される模様。

- `cloud-init.log`。cloud-init自体のログです。
- `cloud-init-output.log`。cloud-init実行中の、標準出力と標準エラー出力が書き込まれるようです。

これらを見てデバッグする事になります。

```console
$ lxc exec test2 -- /bin/bash -c "ls /var/log/cloud-init*.log"
/var/log/cloud-init-output.log  /var/log/cloud-init.log
```

```console
$ lxc exec test2 -- head -n5 /var/log/cloud-init.log
2023-04-29 02:04:57,689 - util.py[DEBUG]: Cloud-init v. 23.1.2-0ubuntu0~22.04.1 running 'init-local' at Sat, 29 Apr 2023 02:04:57 +0000. Up 1.35 seconds.
2023-04-29 02:04:57,689 - main.py[DEBUG]: No kernel command line url found.
2023-04-29 02:04:57,689 - main.py[DEBUG]: Closing stdin.
2023-04-29 02:04:57,697 - util.py[DEBUG]: Writing to /var/log/cloud-init.log - ab: [640] 0 bytes
2023-04-29 02:04:57,698 - util.py[DEBUG]: Changing the ownership of /var/log/cloud-init.log to 104:4
```

```console
$ lxc exec test2 -- tail /var/log/cloud-init-output.log
Transmitting file data .................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................done
Committing transaction...
Committed revision 1.
Updating 'etc':
At revision 1.
property 'svn:ignore' set on '.'
Sending        etc
Committing transaction...
Committed revision 2.
Cloud-init v. 23.1.2-0ubuntu0~22.04.1 finished at Sun, 21 May 2023 02:39:09 +0000. Datasource DataSourceLXD.  Up 25.57 seconds
```

## 後始末

コンテナを止めて削除します。

```console
lxc stop test2
lxc rm test2
```

# 仕舞い

cloud-initを使ってLXDコンテナの初期化をしました。私の手順に従って、タイムゾーンとロケールと、`/etc`のバージョン管理を設定して、内容を確認できました。初期設定の自動化！やったね:thumbsup_tone1: