Container Linux のプロビジョニングツールである Ignition の理解を深める

CoreOS が提供する Container Linux をプロビジョニングするためのツールである Ignition の理解を深めるために、公式ドキュメントを読んで得た知見と、VirtualBox と Vagrant を使ってローカルに用意した Container Linux の VM に対して Ignition でプロビジョニングを行うまでの流れを、自分なりに噛み砕いてまとめてみました。

# Ignition とは？

CoreOS社 が提供する [公式ドキュメント](https://coreos.com/ignition/docs/latest/) を参考にしながら説明していきます。

## 概要

[Ignition](https://github.com/coreos/ignition) とは、Container Linux（ 旧CoreOS [^1] ） に特化したプロビジョニングツールです。ディスクのパーティショニング、ファイル作成、ユーザー設定、などが可能となっており、最初のOS起動時のみ稼働する仕様と、OS起動プロセスのもとても早い段階でプロビジョニングを実行できる（例: Systemd が起動する前にファイルシステムやネットワークの設定の変更が可能になる [^2] ）ことを強みとしているようです。蛇足ですが、Ignition は日本語で「点火」という意味です。

以前までは、自前で用意した Cloud-Config または自前のスクリプトを user-data [^3] に渡して Container Linux のプロビジョニングを行う [coreos-cloudinit](https://github.com/coreos/coreos-cloudinit) が推奨されていましたが、現在は Container Linux Config（YAML形式）を [Config Transpiler](https://github.com/coreos/container-linux-config-transpiler/) で Ignition Config（JSON形式）に変換して、Ignition でプロビジョニングする方法が推奨されています。[^4][^5]

なぜ、わざわざ Container Linux Config を Ignition Config に変換して利用する仕様なのか気になったので調べたところ、以下の理由からこのような仕様となっているようです。 [^6]

- Ignition Config 作成時の時間短縮
    - マシンの起動を試みる前に Config Transpiler を介すことで早い段階で内容に誤りがないかチェックできる
        - トラブルシューティングの際に都度都度マシンに接続して Ignition のログを見るのは避けたい
        - 変換時にはバリデーションと指定されたクラウドプロバイダーに適用するための処理が実行されている
- 設定ファイルを作成する上で JSON より YAML の方が人間には理解しやすい
    - プログラムでのパースのしやすさは JSON の方が優れているので YAML => JSON という変換の形をとった
        - 人間目線だと YAML が望ましい
        - マシン目線だと JSON が望ましい

## Container Linux Config と Ignition Config の例

この時点で、少しでもイメージが膨らむように各設定ファイルの例を記述しておきます。

以下のファイルは、「指定した URL からバイナリをダウンロードして `/opt/bin/kubectl` に配置する」という処理を記述したものです。Container Linux Config ファイルは自前で用意して、Ignition Config ファイルは [Config Transpiler](https://github.com/coreos/container-linux-config-transpiler/) を使って動的に生成したものとなっております。

```bash
# Container Linux Config を作成
$ vim cl-config.yaml

# Ignition Config は Container Linux Config から動的生成
$ ct -pretty -strict -in-file cl-config.yaml -out-file ignition-config.json
```

### Container Linux Config （ YAML形式 ）

```yaml:cl-config.yaml
storage:
  files:
    - path: /opt/bin/kubectl
      filesystem: root
      mode: 0755
      contents:
        remote:
          url: https://storage.googleapis.com/kubernetes-release/release/v1.13.2/bin/linux/amd64/kubectl
```

### Ignition Config （ JSON形式 ）

```json:ignition-config.json
{
  "ignition": {
    "config": {},
    "security": {
      "tls": {}
    },
    "timeouts": {},
    "version": "2.2.0"
  },
  "networkd": {},
  "passwd": {},
  "storage": {
    "files": [
      {
        "filesystem": "root",
        "path": "/opt/bin/kubectl",
        "contents": {
          "source": "https://storage.googleapis.com/kubernetes-release/release/v1.13.2/bin/linux/amd64/kubectl",
          "verification": {}
        },
        "mode": 493
      }
    ]
  },
  "systemd": {}
}

```

個人差はあると思いますが、JSON と比べて YAML の方が人間には理解しやすいように思えます。
上記のような Ignition Config を使って、Ignition で Container Linux のプロビジョニングを行う流れとなります。後半では、実際に VM を立ててプロビジョニングを行うので、ここでは大体のイメージを掴んでもらえたらと思っています。

## 参考情報

知っておいた方が良さそうな考え方や仕様を、備忘録として列挙しておきます。

- 主要なクラウドプロバイダーで Ignition を利用した Container Linux のプロビジョニングが可能
    - 詳細は [Supported Platforms](https://coreos.com/ignition/docs/latest/supported-platforms.html) を参照すること
- Ignition のログは journald から拾うことが可能
    - 目ぼしいものが無かったら root で実行すること
    - verbose したいなら journald.conf で log level を調整すること
- Ignition はプロビジョニングツールであって設定ファイル管理ツールではない
    - 設定ファイル管理ツールとして使おうと思えば使えるがベストではない
- Immutable infrastructure の実現を促進するツール
    - 構成変更は古いノードを破棄した上でのリプロビジョニング
- Ignition による処理が失敗したらマシンは正常に起動しない
    - 中途半端な構成でマシンが起動することはない
- Ignition では任意のロジックを組み込むことはできない
    - 任意のロジックを組み込みたいのであれば oneshot な systemd service を作って実施すること
- YAML で書かれた Container Linux Config から JSON で書かれた Ignition Config に変換する運用を推奨
    - Ignition Config を自分で書いて動かすことも不可能ではない
    - 仕様も [Ignition Configuration Specification](https://coreos.com/ignition/docs/0.30.0/configuration-v2_3.html) としてまとめられている
- Ignition では [Afterburn（旧coreos-metadata）](https://github.com/coreos/afterburn) という仕組みを使って各インスタンスの IP などの動的データを扱うことが可能
    - Afterburn（旧coreos-metadata）= 起動中の Container Linux に関する情報をまとめた環境変数
    - 各クラウドプロバイダーによって Afterburn（旧coreos-metadata）に展開される情報が異なる
        - 詳細は [Afterburn（旧coreos-metadata） の README](https://github.com/coreos/afterburn/blob/master/README.md) を参照下さい
    - もちろん自前での metadata 定義も可能
        - 詳細は [Custom metadata agent](https://coreos.com/ignition/docs/latest/metadata.html) を参照下さい
        - 仕組みは単純で Afterburn（旧coreos-metadata）の実データが格納されているファイル `/run/metadata/afterburn`（各クラウドプロバイダー毎にファイルは異なる）に環境変数を書き込む oneshot な systemd service を用意するだけ

# 実際に Ignition を動かしながら理解を深めていく

VirtualBox と Vagrant を使ってローカルに Container Linux の VM を用意して、その VM の起動時に Ignition でプロビジョニングを行う流れで進めていきたいと思います。

## 利用する OS/ミドルウェア/CLI のバージョン

ホストOS側として記載があるもので不足しているものがあれば、インストールを済ませておいてください。

- ホストOS側
    - MacOS Sierra v10.12.6
    - VirtualBox v5.2.24
    - Vagrant v2.2.3
    - Config Transpiler (ct) v0.9.0
    - vagrant-ignition v0.0.3
- ゲストOS側
    - Container Linux Stable v1967.3.0 [^7]
    - Container Linux Config v2023.0.0 のドキュメントに記載があるもの [^8]

## Container Linux の VM をローカルに起動する

CoreOS が [coreos-vagrant](https://github.com/coreos/coreos-vagrant) というリポジトリを公開してくれているので、そちらをベースに Vagrantfile を作成します。
もちろん coreos-vagrant を git clone してきてそのまま使う形でも問題ないので、お好きなやり方で進めて頂いて構いません。

まずは coreos-vagrant 内の Vagrantfile だけをダウンロードします。

```bash
$ wget https://raw.githubusercontent.com/coreos/coreos-vagrant/master/Vagrantfile
```

Vagrantfile 内の以下の部分を書き換えてください。

```diff
# その1（27行目付近）
- $update_channel = "alpha"
+ $update_channel = "stable"

# その2（168行目付近）
config.vm.provider :virtualbox do |vb|
    config.ignition.hostname = vm_name
    config.ignition.drive_name = "config" + i.to_s
    # when the ignition config doesn't exist, the plugin automatically generates a very basic Ignition with the ssh key
    # and previously specified options (ip and hostname). Otherwise, it appends those to the provided config.ign below
-     if File.exist?(IGNITION_CONFIG_PATH)
-         config.ignition.path = 'config.ign'
-     end
+    # if File.exist?(IGNITION_CONFIG_PATH)
+        # config.ignition.path = 'config.ign'
+    # end
+    config.ignition.path = 'cl-config/ignition-config.json'
end
```

Vagrantfile の書き換えが終わったら、以下のコマンドを実行して Container Linux Stable v1967.3.0 の VM を起動します。

```bash
$ vagrant up

$ vagrant ssh core-01
Last login: Sun Jan 20 12:36:21 UTC 2019 from 10.0.2.2 on ssh
Container Linux by CoreOS stable (1967.3.0)
core@core-01 ~ $ cat /etc/os-release
NAME="Container Linux by CoreOS"
ID=coreos
VERSION=1967.3.0
VERSION_ID=1967.3.0
BUILD_ID=2019-01-08-0044
PRETTY_NAME="Container Linux by CoreOS 1967.3.0 (Rhyolite)"
ANSI_COLOR="38;5;75"
HOME_URL="https://coreos.com/"
BUG_REPORT_URL="https://issues.coreos.com"
COREOS_BOARD="amd64-usr"
```

ここまでで VM を起動させるための準備は完了したので、VM は一旦削除しておいてください。
※ Ignition は最初のOS起動時のみ稼働する仕様なので動きを試したい度に削除が必要

```bash
$ vagrant destroy -f core-01
```

## Container Linux Config を用意して Ignition Config を生成する

今回は、以下の要件でプロビジョニングを行う Container Linux Config を作成しました。

- `Hello, Ignition!` という文字列を1秒毎に出力する `hello-ignition.service` を稼働させる
    - `hello-ignition.service` は新規に hello-ignition ユーザーを作成して実行すること
    - `Hello, Ignition!` を出力するスクリプトは `/opt/bin/hello-ignition` に配置すること

```bash
$ mkdir cl-config
$ vim cl-config/cl-config.yaml
```

`cl-config/cl-config.yaml` は以下の内容で作成。

```yaml:cl-config/cl-config.yaml
storage:
  files:
    - path: /opt/bin/hello-ignition
      filesystem: root
      mode: 0755
      contents:
        inline: |
          #!/usr/bin/env bash
          while true ; do
            echo 'Hello, Igninition!'
            sleep 1
          done

systemd:
  units:
    - name: hello-ignition.service
      enable: true
      contents: |
        [Unit]
        Description=Hello Ignition

        [Service]
        ExecStart=/opt/bin/hello-ignition
        Restart=always
        Type=simple
        User=hello-ignition
        Group=hello-ignition

        [Install]
        WantedBy=multi-user.target

passwd:
  users:
    - name: hello-ignition
```

Config Transpiler の wrapper script を作成します。 [^9]
管理のしやすさを考えて、わざわざ wrapper script を作成していますが、単純にコマンドを実行する形でも問題はないので、好きなやり方で進めてください。

```bash
$ mkdir scripts
$ vim scripts/config_transpile.sh
```

`scripts/config_transpile.sh` は以下の内容で作成。

```bash:scripts/config_transpile.sh
#!/usr/bin/env bash

set -eu

readonly BASE_DIR=$(cd $(dirname $0); pwd)

ct -pretty -strict \
    -platform vagrant-virtualbox \
    -in-file ${BASE_DIR}/../cl-config/cl-config.yaml \
    -out-file ${BASE_DIR}/../cl-config/ignition-config.json
```

以下のコマンドを実行して Ignition Config ファイルを動的生成します。

```bash
$ bash scripts/config_transpile.sh
```

以下の Ignition Config が `cl-config/ignition-config.json` に動的生成されました。

```json:cl-config/ignition-config.json
{
  "ignition": {
    "config": {},
    "security": {
      "tls": {}
    },
    "timeouts": {},
    "version": "2.2.0"
  },
  "networkd": {},
  "passwd": {
    "users": [
      {
        "name": "hello-ignition"
      }
    ]
  },
  "storage": {
    "files": [
      {
        "filesystem": "root",
        "path": "/opt/bin/hello-ignition",
        "contents": {
          "source": "data:,%23!%2Fusr%2Fbin%2Fenv%20bash%0Awhile%20true%20%3B%20do%0A%20%20echo%20'Hello%2C%20Igninition!'%0A%20%20sleep%201%0Adone%0A",
          "verification": {}
        },
        "mode": 493
      }
    ]
  },
  "systemd": {
    "units": [
      {
        "contents": "[Unit]\nDescription=Hello Ignition\n\n[Service]\nExecStart=/opt/bin/hello-ignition\nRestart=always\nType=simple\nUser=hello-ignition\nGroup=hello-ignition\n\n[Install]\nWantedBy=multi-user.target\n",
        "enable": true,
        "name": "hello-ignition.service"
      }
    ]
  }
}
```

## VM を起動して Ignition によるプロビジョニングが完了したか確認する

VM を起動してみます。

```bash
$ vagrant up
```

作成された VM に接続して、想定通りのプロビジョニングが実行されているか確認します。

```bash
$ vagrant ssh core-01
Last login: Sun Jan 20 13:31:54 UTC 2019 from 10.0.2.2 on pts/0
Container Linux by CoreOS stable (1967.3.0)
core@core-01 ~ $
```

以下の通り、想定通りのプロビジョニングが実行されていることが確認できました！！！

#### `hello-ignition` ユーザーが作成されている

```bash
$ core@core-01 ~ $ id hello-ignition
uid=1000(hello-ignition) gid=1000(hello-ignition) groups=1000(hello-ignition)
```

#### `/opt/bin/hello-ignition` にスクリプトが配置されている

```bash
core@core-01 ~ $ cat /opt/bin/hello-ignition
#!/usr/bin/env bash
while true ; do
  echo 'Hello, Igninition!'
  sleep 1
done
```

#### `hello-ignition.service` が正常に稼働している

```bash
core@core-01 ~ $ systemctl status hello-ignition.service
● hello-ignition.service - Hello Ignition
   Loaded: loaded (/etc/systemd/system/hello-ignition.service; enabled; vendor preset: enabled)
   Active: active (running) since Sun 2019-01-20 13:31:31 UTC; 14min ago
 Main PID: 774 (bash)
    Tasks: 2 (limit: 7577)
   Memory: 712.0K
   CGroup: /system.slice/hello-ignition.service
           ├─ 774 bash /opt/bin/hello-ignition
           └─1855 sleep 1
```

#### journald に `Hello, Ignition!` という文字列を1秒毎に出力されている

```bash
core@core-01 ~ $ journalctl -u hello-ignition.service -f
-- Logs begin at Sun 2019-01-20 13:31:17 UTC. --
Jan 20 13:46:30 core-01 hello-ignition[774]: Hello, Igninition!
Jan 20 13:46:31 core-01 hello-ignition[774]: Hello, Igninition!
Jan 20 13:46:32 core-01 hello-ignition[774]: Hello, Igninition!
Jan 20 13:46:33 core-01 hello-ignition[774]: Hello, Igninition!
```

以上が、Ignition を利用した Container Linux に対するプロビジョニングの確認となります。

# 追記: 2019-03-28

coreos-metadata が Afterburn という名称に変更されました。
https://github.com/coreos/afterburn/pull/189

以下の Issue で名称を変更するまでの議論がなされていますので、詳細は以下を参照ください。
https://github.com/coreos/afterburn/issues/126

上記の Issue で metadata を扱う以外にも機能があるというやりとりを見かけたので、後日深掘りできたらと思っています。

# さいごに

今回作成したサンプルコードは以下のリポジトリに上げてありますので、興味ある方はどうぞ。
https://github.com/ryysud/ignition-sample

公式ドキュメントを参照しながら、手元で VM として Container Linux を稼働させて Ignition を使ってみたことで、非常に理解が深まりました。やはり四の五の言わずに手を動かして色々と試してみるのが、学習コスパ最強です。

今回はローカルの VM に対してプロビジョニングを実行する形をとったのですが、Terraform から [Ignition Provider](https://www.terraform.io/docs/providers/ignition/index.html) が提供されているので、作成した Ignition Config を user-data に流し込むような Terraform ファイルを作成すれば、主要クラウドプロバイダー上のインスタンスへデプロイすることが可能です。

次回は、今回は割愛した Afterburn（旧coreos-metadata）を利用方法や、より詳細な Container Linux Config の書き方（ファイル作成など1つとっても多くのパラメータが提供されています）や、単純なファイル/サービス/ユーザー作成でなく Contaienr Linux Service として提供されている etcd, docker, flannel, locksmith の構築方法なども、自分の理解がてらまとめていきたいと考えています。

引き続き CoreOS のプロダクトに強くなれるよう精進していきます ₍₍ (ง ˘ω˘ )ว ⁾⁾

# 参考

- [CoreOS Container Linux Documentation](https://coreos.com/os/docs/latest/)
- [Ignition Documentation | Install Ignition with CoreOS](https://coreos.com/ignition/docs/latest/)

[^1]: [コンテナに最適化したCoreOS、ブランド名を「Container Linux」に変更。](https://www.publickey1.jp/blog/16/corelinuxcontainer_linux.html) の記事にあるように 2016年12月 に CoreOS社 が提供する OS の名称が CoreOS から Container Linux に変更された。
[^2]: Ignition が実行されるタイミングに関しては [CoreOS Container Linux startup process](https://coreos.com/ignition/docs/latest/boot-process.html) が参考になるかと思います。
[^3]: インスタンス起動時にインスタンスに転送する一連のコマンドという認識で大きな誤りはないと思います。
[^4]: [Migrating from Cloud-Config to Container Linux Config](https://coreos.com/os/docs/latest/migrating-to-clcs.html) なるドキュメントも提供されているので、coreos-cloudinit から Ignition への移行時にはこれを参考にすれば良さそうです。
[^5]: coreos-cloudinit の代わりに Ignition が登場した背景や利点などは、[CoreOS Container Linuxをディスクへインストールする方法とIgnitionについて - ゆるふわキャンパー](https://blog.lorentzca.me/install-container-linux-to-disk/) に、とてもわかりやすくまとめられているので本記事では割愛します。
[^6]: [Config transpiler overview](https://coreos.com/os/docs/latest/overview-of-ct.html) を自分なりに噛み砕いてまとめています。
[^7]: [CoreOS Container Linux Release Notes](https://coreos.com/releases/)
[^8]: [Configuration Specification | Container Linux | CoreOS](https://coreos.com/os/docs/latest/configuration.html)
[^9]: [別の記事](https://qiita.com/ryysud/items/d2fd0cdfbdfc210de455) で紹介した "コピペですぐに使えるオプション付き ct テンプレート" をベースに作成した wrapper script となります。