CoreOS の初期設定ツール Ignition を試してみる

2022年08月01日 月曜日


【この記事を書いた人】
r-fujii

IaaS の開発と運用をしています。

「CoreOS の初期設定ツール Ignition を試してみる」のイメージ

こんにちは、クラウド本部所属の r-fujii です。普段は IaaS の開発と運用をしています。

この記事では CoreOS の初期設定ツールである Ignition を紹介します。同種の OS 初期設定ツールとしては cloud-init が有名で広く利用されていますが、Ignition については触れたことのない方も多いかと思います。

記事の前半では Ignition の基本を紹介しながら動作の様子を眺めます。後半では CoreOS 以外の Linux ディストリビューションで Ignition を動かしてみます。

cloud-init と User Data

Ignition を扱う上で cloud-init の知識があると理解しやすいので、周辺知識の導入も兼ねて軽く触れておきます。ご存じの方はこの項目を飛ばして先に進んでいただいても大丈夫です。

cloud-init は名前の通りクラウド(IaaS)のインスタンスの初期設定を行うためのツールです。インスタンスを起動する際に専用の設定を与えることで、

  • ホスト名の設定
  • ユーザの作成
  • 設定ファイルの配置
  • パッケージのインストール

といったセットアップ作業を自動化することができます。設定は YAML 形式で、例えばホスト名の設定とユーザの作成を行う例は以下のようになります。

#cloud-config

# ホスト名を candle に設定
hostname: candle
 
# r-fujii ユーザを作成して sudo グループに追加し SSH 公開鍵を設置 (鍵の内容は省略)
users:
  - name: r-fujii
    groups:
      - sudo
    ssh_authorized_keys:
      - ssh-rsa ...

さて、cloud-init を利用するためには上記の設定をどうにかしてインスタンスに渡す必要がありますが、これには各種 IaaS に備わっている User Data と呼ばれる機能を用います。名前の通り、ユーザが IaaS の Web コンソールや API などから任意のデータ(このデータ自体も User Data と呼びます)を与えることができるもので、与えたデータはインスタンスの中からアクセスできるようになります。

インスタンス内から User Data にアクセスする方法はサービスによって様々で、メタデータサーバから HTTP で取得するものや、インスタンスにアタッチされた CD-ROM イメージから読み取るものなどがあります。cloud-init ではこの部分が datasource として切り替え可能になっていて、各 IaaS の OS テンプレートにはあらかじめそのサービスに応じた設定を仕込んだ cloud-init がインストールされ、インスタンス起動時に自動的に動作するように設定されています。そのため、単に cloud-init を利用するだけであればこのあたりの細かい話を意識する必要はありません。

datasource の中には NoCloud のような特定の IaaS に依存しないものもあるため、cloud-init を利用できる環境は IaaS 上だけに限りません。例えばオンプレミスのベアメタルサーバのセットアップに cloud-init を利用することも可能です。筆者は自宅で動かしている VM の初期設定に cloud-init を利用しているのですが、あるとき「これ以外に同じようなことができるものはないだろうか」と思い立って調べたのが Ignition に興味を持つきっかけでした。

Ignition

それでは Ignition の話に移ります。Ignition を一言で説明するなら「CoreOS 用の cloud-init みたいなもの」です。

CoreOS はコンテナを動作させることに特化した Linux ディストリビューションで、本記事の執筆時点では単に CoreOS というと以下の二つのどちらかを指します。(※1)

  • Fedora CoreOS
  • Red Hat Enterprise Linux CoreOS

本記事では Fedore CoreOS を使って Ignition に触れてみます。今回はどちらかというと Ignition に興味があるので CoreOS そのものについてはあまり深入りしませんが、CoreOS の特徴の一つとして「他の Linux ディストリビューションにあるようなインストーラが存在しない」ことが挙げられます。

ではどうやってプロビジョニングするのかというと、公式に提供されているディスクイメージをそのまま複製してインスタンスを作成し、インスタンスの初回起動時に Ignition で必要なカスタマイズを加えることになっています。つまり、CoreOS を利用するためには Ignition を利用することが必須です。

設定を作る

Ignition を試すためには CoreOS を動かしてみる必要がありますが、CoreOS を動かすためには Ignition で設定する必要がある……ということで、まずは設定を作ります。

Ignition の設定(最終的にインスタンスに与えるもの)は JSON 形式ですが、これをそのまま読み書きするのは大変なので、より人間にやさしい YAML 形式で書いたものを JSON 形式に変換して使うようになっています。変換を行ってくれる Butane (※2)というツールが用意されているので、YAML で書いた設定を Butane で変換し、出てきた JSON をインスタンスに設定して使う、という流れになります。

今回は cloud-init による設定例として先に示したものと同等の設定を作ってみます。比較のために cloud-init の方も再掲します。

Ignition:

variant: fcos
version: 1.4.0
storage:
  # ホスト名を candle に設定
  files:
    - path: /etc/hostname
      mode: 0644
      overwrite: true
      contents:
        inline: candle
passwd:
  # r-fujii ユーザを作成して sudo グループに追加し SSH 公開鍵を設置 (鍵の内容は省略)
  users:
    - name: r-fujii
      groups:
        - sudo
      ssh_authorized_keys:
        - ssh-rsa ...

cloud-init(再掲):

#cloud-config
 
# ホスト名を candle に設定
hostname: candle
 
# r-fujii ユーザを作成して sudo グループに追加し SSH 公開鍵を設置 (鍵の内容は省略)
users:
  - name: r-fujii
    groups:
      - sudo
    ssh_authorized_keys:
      - ssh-rsa ...

並べて眺めてみると(両方 YAML ということもあり)よく似ていて、特にユーザを作成する部分はほとんど同じです。

一方でホスト名を設定する部分は全く異なっていて、cloud-init は単に設定したいホスト名を指定する形ですが、Ignition では /etc/hostname を上書きする形になっています。ここは二つのツールの思想の違いがよく表れている点で、cloud-init が設定対象に応じた様々なモジュールを提供しているのに対し、Ignition は「ディスクの状態を操作する」ことにフォーカスしたシンプルな設計です。仕様を眺めると分かりますが、Ignition はディスクパーティションやファイルシステムの操作以外だとほぼファイルを配置することしかできません。(それさえできればだいたい何とかなる、ということでもあります)

もう一つの違いとして、Ignition の設定は宣言的である、ということが挙げられます。例えば cloud-init では runcmd 等のモジュールで任意のコマンドを実行することができますが、Ignition の設定ではそのような手続き的な内容を表現することができません。では何かコマンドを実行させたい場合はどうするのかというと、「コマンドを実行する systemd unit を配置する」ことで実現します。Ignition の仕事は systemd unit が起動されるように配置するところまでで、そこから実際に unit を起動するのは systemd の役割です。

さて、YAML で書いた Ignition の設定ができたので JSON に変換します。公式ドキュメントにある通りコンテナを使うのが楽です。

$ docker run -i --rm quay.io/coreos/butane:release --pretty --strict < ignition-config.yml > ignition-config.ign

出力された JSON (ignition-config.ign) を眺めてみると、何となく元の YAML と同じ情報が含まれていそうな雰囲気が見て取れます。これを IaaS インスタンスに設定することになります。

{
  "ignition": {
    "version": "3.3.0"
  },
  "passwd": {
    "users": [
      {
        "groups": [
          "sudo"
        ],
        "name": "r-fujii",
        "sshAuthorizedKeys": [
          "ssh-rsa ..."
        ]
      }
    ]
  },
  "storage": {
    "files": [
      {
        "overwrite": true,
        "path": "/etc/hostname",
        "contents": {
          "source": "data:,candle"
        },
        "mode": 420
      }
    ]
  }
}

また、YAML に誤りがあると変換エラーとなるので、単純な構文エラーなどは実際にインスタンスを起動する前に気づいて修正することができます。これは cloud-init より少し便利な点です。

例(passwd を誤って password と書いた場合)

$ docker run -i --rm quay.io/coreos/butane:release --pretty --strict < ignition-config.yml > ignition-config.ign
warning at $.password, line 11 col 1: Unused key password
Config produced warnings and --strict was specified

動かしてみる

設定ができたので動作を確認しましょう。今回は手元の Linux ホストで VM として CoreOS を動かすことにします。以下の手順は公式ドキュメントの Provisioning Fedora CoreOS on libvirt を参考にしています。

  1. Virtualized 版の QEMU 用 qcow2 イメージをダウンロード
    $ curl -O https://builds.coreos.fedoraproject.org/prod/streams/stable/builds/36.20220618.3.1/x86_64/fedora-coreos-36.20220618.3.1-qemu.x86_64.qcow2.xz
    $ unxz fedora-coreos-36.20220618.3.1-qemu.x86_64.qcow2.xz
  2. VM 用のディスクイメージを作成してダウンロードした qcow2 イメージをアップロード
    $ virsh vol-create-as --format qcow2 default candle 10G
    Vol candle created
    $ virsh vol-upload --pool default candle fedora-coreos-36.20220618.3.1-qemu.x86_64.qcow2
  3. VM を作成して起動 (--qemu-commandline が Ignition の設定(変換後の JSON ファイル)を渡している部分 (※3))
    $ virt-install --connect qemu:///system --name=candle \
        --vcpus=1 \
        --memory=1024 \
        --os-variant=fedora-coreos-stable \
        --import \
        --graphics=none \
        --disk="vol=default/candle" \
        --network "network=default" \
        --qemu-commandline="-fw_cfg name=opt/com.coreos/config,file=/usr/share/qemu/ignition-config.ign"

VM を起動するとコンソールに大量の出力が流れますが、Ignition による設定が上手くいけば以下のようなメッセージとログインプロンプトが表示されます。

Fedora CoreOS 36.20220618.3.1
Kernel 5.18.5-200.fc36.x86_64 on an x86_64 (ttyS0)

SSH host key: SHA256:ugwuOj2rxNwjwWpernhyTXzY8k2PCMaNs9F3B4Z/ims (ECDSA)
SSH host key: SHA256:a/75i1LBZqt4IRAtYXOKI6HXzqRE/za7EdZd3l/oefY (ED25519)
SSH host key: SHA256:wMxHcROE3JydnRyIRGzdF/EUwosqBF+yLRMt9/73xVE (RSA)
Ignition: ran on 2022/07/12 10:39:42 UTC (this boot)
Ignition: user-provided config was applied
Ignition: wrote ssh authorized keys file for user: r-fujii
candle login:

デフォルトでは DHCP で IP アドレスが設定されます。libvirt では virsh net-dhcp-leases で DHCP リースの状況を表示できるので、そこから VM に割り当てられた IP アドレスを確認できます。(※4) 各種 IaaS で動かすときは IaaS の管理画面等からインスタンスに割り当てられた IP アドレスを確認できるはずです。

$ virsh net-dhcp-leases default
 Expiry Time           MAC address         Protocol   IP address           Hostname   Client ID or DUID
------------------------------------------------------------------------------------------------------------------------------------------------
 2022-07-12 20:39:50   52:54:00:a7:a3:10   ipv4       192.168.122.237/24   candle     01:52:54:00:a7:a3:10

Ignition で作成したユーザ (r-fujii) で ssh ログインできました。ホスト名も設定した通り candle になっています。

$ ssh -l r-fujii 192.168.122.237
Fedora CoreOS 36.20220618.3.1
Tracker: https://github.com/coreos/fedora-coreos-tracker
Discuss: https://discussion.fedoraproject.org/tag/coreos

Last login: Tue Jul 12 10:48:13 2022 from 192.168.122.1
[r-fujii@candle ~]$

ログイン後に dmesg を見てみると Ignition が動いている様子を確認できます。(長いので一部を抜粋)

Ignition は initramfs 内で動作するため、Ignition による設定処理は各種サービス等が起動するよりも前の、システム起動中の早いタイミングで行われます。

[    0.000000] Linux version 5.18.5-200.fc36.x86_64 (mockbuild@bkernel01.iad2.fedoraproject.org) (gcc (GCC) 12.1.1 20220507 (Red Hat 12.1.1-1), GNU ld version 2.37-27.fc36) #1 SMP PREEMPT_DYNAMIC Thu Jun 16 14:51:11 UTC 2022
[    0.000000] Command line: BOOT_IMAGE=(hd0,gpt3)/ostree/fedora-coreos-69569aff3092dca8450036448d196a161264ecb5edd23565a655a74f1e4d9050/vmlinuz-5.18.5-200.fc36.x86_64 mitigations=auto,nosmt ignition.platform.id=qemu console=tty0 console=ttyS0,115200n8 ignition.firstboot ostree=/ostree/boot.1/fedora-coreos/69569aff3092dca8450036448d196a161264ecb5edd23565a655a74f1e4d9050/0
...
[    4.505527] ignition[713]: Ignition 2.14.0
[    4.506645] ignition[713]: Stage: fetch-offline
[    4.507905] ignition[713]: reading system config file "/usr/lib/ignition/base.d/00-core.ign"
[    4.510810] ignition[713]: reading system config file "/usr/lib/ignition/base.d/30-afterburn-sshkeys-core.ign"
[    4.513900] ignition[713]: no config dir at "/usr/lib/ignition/base.platform.d/qemu"
[    4.516873] ignition[713]: no config URL provided
[    4.517722] ignition[713]: reading system config file "/usr/lib/ignition/user.ign"
[    4.520967] ignition[713]: fetched base config from "system"
[    4.522334] ignition[713]: no config at "/usr/lib/ignition/user.ign"
[    4.523538] ignition[713]: fetched user config from "qemu"
[    4.524648] ignition[713]: op(1): [started]  loading QEMU firmware config module
[    4.536728] ignition[713]: op(1): [finished] loading QEMU firmware config module  ← QEMU に与えた設定を読んでいる
[    4.542458] ignition[713]: fetch-offline: fetch-offline passed
[    4.543571] ignition[713]: Ignition finished successfully
...
[    7.978205] ignition[912]: INFO     : Ignition 2.14.0
[    7.979636] ignition[912]: INFO     : Stage: files
[    7.981035] ignition[912]: INFO     : reading system config file "/usr/lib/ignition/base.d/00-core.ign"
[    7.984130] ignition[912]: DEBUG    : parsing config with SHA512: ff6a5153be363997e4d5d3ea8cc4048373a457c48c4a5b134a08a30aacd167c1e0f099f0bdf1e24c99ad180628cd02b767b863b5fe3a8fce3fe1886847eb8e2e
[    7.988383] ignition[912]: INFO     : reading system config file "/usr/lib/ignition/base.d/30-afterburn-sshkeys-core.ign"
[    7.990385] ignition[912]: DEBUG    : parsing config with SHA512: a30a1921169d5a3b58ef9b25de60783be1add6ea8d05fd44a0746cb60dd1b8a8b34ab51eec5eb14eecc2df2ab6ba1cd3fd7351eed65793d22316ab262a857d95
[    7.993346] ignition[912]: INFO     : no config dir at "/usr/lib/ignition/base.platform.d/qemu"
[    8.000319] ignition[912]: INFO     : files: ensureUsers: op(1): [started]  creating or modifying user "core"
[    8.002233] ignition[912]: DEBUG    : files: ensureUsers: op(1): executing: "useradd" "--root" "/sysroot" "--create-home" "--password" "*" "--comment" "CoreOS Admin" "--groups" "adm,sudo,systemd-journal,wheel" "core"
[    8.887179] ignition[912]: INFO     : files: ensureUsers: op(1): [finished] creating or modifying user "core"
[    8.890080] ignition[912]: INFO     : files: ensureUsers: op(2): [started]  creating or modifying user "r-fujii"
[    8.892853] ignition[912]: DEBUG    : files: ensureUsers: op(2): executing: "useradd" "--root" "/sysroot" "--create-home" "--password" "*" "--groups" "sudo" "r-fujii"  ← ユーザが作成されている
[    9.840973] ignition[912]: INFO     : files: ensureUsers: op(2): [finished] creating or modifying user "r-fujii"
[    9.843152] ignition[912]: INFO     : files: ensureUsers: op(3): [started]  adding ssh keys to user "r-fujii"
[    9.846483] ignition[912]: wrote ssh authorized keys file for user: r-fujii
[    9.848095] ignition[912]: INFO     : files: ensureUsers: op(3): [finished] adding ssh keys to user "r-fujii"
[    9.849900] ignition[912]: INFO     : files: createFilesystemsFiles: createFiles: op(4): [started]  writing file "/sysroot/etc/hostname"
[    9.851921] ignition[912]: INFO     : files: createFilesystemsFiles: createFiles: op(4): [finished] writing file "/sysroot/etc/hostname"  ← ホスト名が設定されている
[    9.853929] ignition[912]: INFO     : files: op(5): [started]  processing unit "afterburn-sshkeys@core.service"
[    9.861850] ignition[912]: INFO     : files: op(5): [finished] processing unit "afterburn-sshkeys@core.service"
[    9.866858] ignition[912]: INFO     : files: op(6): [started]  setting preset to enabled for "afterburn-sshkeys@.service core"
[    9.868782] ignition[912]: INFO     : files: op(6): [finished] setting preset to enabled for "afterburn-sshkeys@.service core"
[    9.873853] ignition[912]: INFO     : files: createResultFile: createFiles: op(7): [started]  writing file "/sysroot/etc/.ignition-result.json"
[    9.875939] ignition[912]: INFO     : files: createResultFile: createFiles: op(7): [finished] writing file "/sysroot/etc/.ignition-result.json"
[    9.881845] ignition[912]: INFO     : files: op(8): [started]  relabeling 18 patterns
[    9.883204] ignition[912]: DEBUG    : files: op(8): executing: "setfiles" "-vF0" "-r" "/sysroot" "/sysroot/etc/selinux/targeted/contexts/files/file_contexts" "-f" "-"
[   10.002533] ignition[912]: INFO     : files: op(8): [finished] relabeling 18 patterns
[   10.004253] ignition[912]: INFO     : files: files passed
[   10.005380] ignition[912]: INFO     : Ignition finished successfully
...
[   10.848539] systemd[1]: Switching root.  ← Ignition による設定が終わった後に Switch Root される

Ignition のもう一つの特徴として「初回起動時のみ動作する」ことが挙げられます。cloud-init ではモジュールごとに実行頻度が異なる(初回のみ実行されるものもあれば、起動するたびに毎回実行されるものもある)ので気を付ける必要がありますが、Ignition はシンプルです。

このあたりの仕組みについては、初回起動時と2回目の起動時でカーネルパラメータを比較してみると見て取れます。(以下の例では見やすいように改行を入れています)

初回起動時:

BOOT_IMAGE=(hd0,gpt3)/ostree/fedora-coreos-69569aff3092dca8450036448d196a161264ecb5edd23565a655a74f1e4d9050/vmlinuz-5.18.5-200.fc36.x86_64
mitigations=auto,nosmt
ignition.platform.id=qemu
console=tty0
console=ttyS0,115200n8
ignition.firstboot
ostree=/ostree/boot.1/fedora-coreos/69569aff3092dca8450036448d196a161264ecb5edd23565a655a74f1e4d9050/0

    2回目の起動時:

    BOOT_IMAGE=(hd0,gpt3)/ostree/fedora-coreos-69569aff3092dca8450036448d196a161264ecb5edd23565a655a74f1e4d9050/vmlinuz-5.18.5-200.fc36.x86_64
    mitigations=auto,nosmt
    ignition.platform.id=qemu
    console=tty0
    console=ttyS0,115200n8
    ostree=/ostree/boot.1/fedora-coreos/69569aff3092dca8450036448d196a161264ecb5edd23565a655a74f1e4d9050/0
    root=UUID=af36a759-2236-4121-a560-00ee534fc4ce
    rw
    rootflags=prjquota
    boot=UUID=2159057d-dcc4-417f-afcd-452afe340aa2

    初回起動時には ignition.firstboot といういかにもそれらしい名前のパラメータがありますが、これが Ignition が動作するかどうかを制御しています。このパラメータは2回目では消えていて、代わりにルートファイルシステム関連のパラメータが増えていますが、この変更は Ignition 自身が初回起動時に行っています。(※5)

    CoreOS 以外で動かしてみる

    さて、とりあえず Ignition を動かして眺めることができましたが、CoreOS は一般的な Linux ディストリビューションと流儀がかなり異なっているので、もう少し複雑なことをしようと思うと CoreOS について学ぶ必要が出てきます。先に述べた通り今回はどちらかというと Ignition に興味があるので、どうにかして CoreOS 以外で Ignition を動かせると嬉しいです。

    Ignition は initramfs 内で動くものなので、単にビルドしてバイナリを置いておくだけではダメで、initramfs に組み込んで起動時に実行されるようにする必要があります。何か手掛かりになるものはないだろうか、と Ignition の公式リポジトリを眺めていたところ、dracut (initramfs を生成するためのツール)のモジュールを発見しました。

    dracut を利用しているディストリビューションであればこれを使って動かせそうな気がする……ということで、Rocky Linux 8 で試してみることにします。

    また、CoreOS での動作確認は手元の VM で行いましたが、せっかくなので IaaS 上で動かしてみようということで IIJ GIO P2 パブリックリソース(以下 P2-PUB)を使います。P2-PUB に固有の事柄は適宜補足しますが、他の IaaS でもほぼ同じように試せるかと思います。P2-PUB 上で Rocky Linux 8 のインスタンスを起動する手順については本題ではないため触れず、すでに利用可能なインスタンスがあり、sudo を利用可能な一般ユーザでログインできるものとして進めます。

    cloud-init を削除

    まず初めに、P2-PUB のインスタンスではデフォルトで cloud-init がインストールされているので削除しておきます。

    他の IaaS で試す場合も cloud-init がインストールされている場合は削除してください。(Ignition と cloud-init の両方が動いてしまうとややこしいことになるので)

    $ sudo dnf -y remove cloud-init

    Ignition のビルド

    Rocky Linux 8 上で Ignition をビルドします。幸いビルドは簡単です。使用するバージョンは本記事執筆時点で最新のリリースの v2.14.0 とします。

    1. Ignition は Go で書かれているので、Go の処理系をインストール
      $ curl -L -O https://go.dev/dl/go1.18.4.linux-amd64.tar.gz
      $ sudo tar -C /usr/local -xzf go1.18.4.linux-amd64.tar.gz
      $ export PATH=$PATH:/usr/local/go/bin
    2. 必要なパッケージをインストール (gcc は cgo で libblkid-devel を使うために必要。あとはリポジトリを clone するための git とビルドで使う make)
      $ sudo dnf -y install gcc libblkid-devel git make
    3. リポジトリを clone して v2.14.0 をチェックアウト
      $ git clone https://github.com/coreos/ignition.git
      $ cd ignition
      $ git checkout v2.14.0
    4. ビルド (make するだけ)
      $ make
      ./build
      Using version from git: v2.14.0
      Building ignition...
      Building ignition-validate...
      $ ls bin/amd64/
      ignition  ignition-validate

    ビルドが成功すると以下の二つの実行ファイルが生成されます。

    • ignition(本体)
    • ignition-validate(JSON 設定のバリデータ)

    initramfs への組み込み

    続いてビルドした Ignition を initramfs に組み込みます。リポジトリに dracut のモジュールが含まれていることは先に触れましたが、実は make install するとこれもインストールされるようになっていて、その後 initramfs を生成し直すだけで Ignition 入りの initramfs が出来上がります。簡単ですね。

    素の実装では make install するたびにビルドをやり直すようになっていたので Makefile を少しだけ改変しています。これ以外は v2.14.0 から全くいじっていません。

    $ git diff
    diff --git a/Makefile b/Makefile
    index d4a2e318..19b62985 100644
    --- a/Makefile
    +++ b/Makefile
    @@ -17,7 +17,7 @@ all:
            ./build
    
     .PHONY: install
    -install: all
    +install:
            for x in dracut/*; do \
              bn=$$(basename $$x); \
              install -m 0644 -D -t $(DESTDIR)/usr/lib/dracut/modules.d/$${bn} $$x/*; \
    $ sudo make install
    for x in dracut/*; do \
      bn=$(basename $x); \
      install -m 0644 -D -t /usr/lib/dracut/modules.d/${bn} $x/*; \
    done
    chmod a+x /usr/lib/dracut/modules.d/*/*.sh /usr/lib/dracut/modules.d/*/*-generator
    install -m 0644 -D -t /usr/lib/systemd/system systemd/ignition-delete-config.service
    install -m 0755 -D -t /usr/lib/dracut/modules.d/30ignition bin/amd64/ignition
    install -m 0755 -D -t /usr/bin bin/amd64/ignition-validate
    install -m 0755 -d /usr/libexec
    ln -sf ../lib/dracut/modules.d/30ignition/ignition /usr/libexec/ignition-rmcfg

    加えて、P2-PUB では User Data が CD-ROM イメージ(OpenStack の Config drive v2 形式のサブセット)としてインスタンスにアタッチされるため、initramfs でこれを mount して読めるように isofs のカーネルモジュールを含めておく必要があります。これは dracut の設定を一行書けば実現できます。

    $ cat /etc/dracut.conf.d/isofs.conf
    add_drivers+=" isofs"

    他の IaaS で試す場合は、各 IaaS の User Data の仕様に応じて必要なものを追加してください。準備ができたら initramfs を再生成します。(※6)

    $ sudo dracut -f "/boot/initramfs-$(uname -r).img" "$(uname -r)"

    カーネルパラメータの追加

    さて、Ignition を initramfs に組み込むことができましたが、組み込んだものが起動時に動くようにする必要があります。CoreOS で試した際に見た通り、これはカーネルパラメータ(ignition.firstboot)で制御されていました。CoreOS は Ignition の利用を前提にしているのであらかじめ仕込みが済んでいましたが、当然ながら Rocky Linux 8 はそうなっていないので、GRUB の設定を書き換えて直接カーネルパラメータを追加します。

    # /etc/default/grub を以下の通り編集
    
    # 変更前
    GRUB_CMDLINE_LINUX="crashkernel=auto rhgb quiet"
    
    # 変更後
    GRUB_CMDLINE_LINUX="crashkernel=auto ignition.firstboot ignition.platform.id=openstack"

    ignition.firstboot の他に ignition.platform.id=openstack というパラメータも追加しています。これは cloud-init でいうところの datasource の種類を指定するもので、initramfs への組み込みの項目で述べた P2-PUB の User Data の仕様に沿ってここでは openstack としています。また、起動時のコンソールへの出力が見えると状況が分かりやすいので、rhgbquiet も削除しています。

    他の IaaS で試す場合は ignition.platform.id の値を環境に合わせて変更してください。指定できる値の一覧は ignition --help で確認できます。

    $ ./ignition --help
    Usage of ./ignition:
    ...
      -platform value
            current platform. [aliyun aws azure azurestack brightbox cloudstack digitalocean exoscale file gcp ibmcloud kubevirt metal nutanix openstack packet powervs qemu virtualbox vmware vultr zvm]
    ...

    設定を書き換えたら忘れずに反映しましょう。

    $ sudo grub2-mkconfig -o /boot/grub2/grub.cfg
    

    sshd の設定変更

    ここまでで Ignition を動かす準備はできましたが、最後に sshd の設定を少しだけ変更します。

    Ignition でユーザを作成する際に与えた SSH 公開鍵は ~/.ssh/authorized_keys.d/ignition に保存されます。Rocky Linux 標準の設定だと sshd がこの鍵を見てくれないので、設定したのにログインできない……とならないように設定を追加しておきます。この後すぐにインスタンスを起動し直すので、設定の reload は不要です。

    # /etc/ssh/sshd_config を以下の通り編集
    
    # 変更前
    AuthorizedKeysFile      .ssh/authorized_keys
    
    # 変更後
    AuthorizedKeysFile      .ssh/authorized_keys .ssh/authorized_keys.d/ignition

    User Data を与えてインスタンスを起動し直す

    あとは Ignition の設定を User Data として与えてインスタンスを起動し直すだけです。

    使用する設定は CoreOS で試したものとほぼ同じ内容ですが、Rocky Linux には sudo グループが存在しないので、代わりに sudoers の設定を追加します。User Data としてインスタンスに与えるのはこれを Butane で変換した後の JSON です。

    variant: fcos
    version: 1.4.0
    storage:
      files:
        # ホスト名を candle に設定
        - path: /etc/hostname
          mode: 0644
          overwrite: true
          contents:
            inline: candle
        # r-fujii ユーザによる sudo (パスワード無し) を許可
        - path: /etc/sudoers.d/r-fujii
          mode: 0440
          contents:
            inline: r-fujii ALL=(ALL) NOPASSWD:ALL
    passwd:
      # r-fujii ユーザを作成して SSH 公開鍵を設置 (鍵の内容は省略)
      users:
        - name: r-fujii
          ssh_authorized_keys:
            - ssh-rsa ...

    P2-PUB では画像のようにコントロールパネルから設定できますが、他の IaaS で試す場合はそれぞれの環境に応じた方法に従ってください。

    User Data の設定

     

    動作確認

    インスタンスを起動すると、Ignition で作成したユーザ(r-fujii)で ssh ログインできるようになっています(ここでは別のインスタンスからプライベートネットワーク経由で ssh しています)。ホスト名の変更も反映されています。

    [root@localhost ~]# ssh -l r-fujii 10.203.0.70
    Activate the web console with: systemctl enable --now cockpit.socket
    
    This system is not registered to Red Hat Insights. See https://cloud.redhat.com/
    To register this system, run: insights-client --register
    
    [r-fujii@candle ~]$

    ログを見ると Ignition が動作していることを確認できます。

    [r-fujii@candle ~]$ sudo journalctl
    -- Logs begin at Thu 2022-07-14 12:44:57 JST, end at Thu 2022-07-14 12:47:17 JST. --
    Jul 14 12:44:57 localhost kernel: Linux version 4.18.0-348.2.1.el8_5.x86_64 (mockbuild@dal1-prod-builder001.bld.equ.rockylinux.org) (gcc version 8.5.0 20210514 (Red Hat 8.5.0-3) (GCC)) #1 SMP Mon Nov 15 20:49:28 UTC 2021
    Jul 14 12:44:57 localhost kernel: Command line: BOOT_IMAGE=(hd0,msdos1)/boot/vmlinuz-4.18.0-348.2.1.el8_5.x86_64 root=/dev/vda1 ro crashkernel=auto ignition.firstboot ignition.platform.id=openstack
    ...
    Jul 14 12:44:58 localhost ignition[454]: Stage: fetch
    Jul 14 12:44:58 localhost ignition[454]: no config dir at "/usr/lib/ignition/base.d"
    Jul 14 12:44:58 localhost ignition[454]: no config dir at "/usr/lib/ignition/base.platform.d/openstack"
    Jul 14 12:44:58 localhost ignition[454]: parsed url from cmdline: ""
    Jul 14 12:44:58 localhost ignition[454]: no config URL provided
    Jul 14 12:44:58 localhost ignition[454]: reading system config file "/usr/lib/ignition/user.ign"
    Jul 14 12:44:58 localhost ignition[454]: no config at "/usr/lib/ignition/user.ign"
    Jul 14 12:44:58 localhost ignition[454]: GET http://169.254.169.254/openstack/latest/user_data: attempt #1
    Jul 14 12:44:58 localhost ignition[454]: GET error: Get "http://169.254.169.254/openstack/latest/user_data": dial tcp 169.254.169.254:80: connect: network is unreachable
    Jul 14 12:44:58 localhost ignition[454]: config drive ("/dev/disk/by-label/config-2") not found. Waiting...
    Jul 14 12:44:58 localhost ignition[454]: config drive ("/dev/disk/by-label/CONFIG-2") not found. Waiting...
    Jul 14 12:44:58 localhost ignition[454]: GET http://169.254.169.254/openstack/latest/user_data: attempt #2
    Jul 14 12:44:58 localhost ignition[454]: GET error: Get "http://169.254.169.254/openstack/latest/user_data": dial tcp 169.254.169.254:80: connect: network is unreachable
    Jul 14 12:44:59 localhost ignition[454]: GET http://169.254.169.254/openstack/latest/user_data: attempt #3
    Jul 14 12:44:59 localhost ignition[454]: GET error: Get "http://169.254.169.254/openstack/latest/user_data": dial tcp 169.254.169.254:80: connect: network is unreachable
    Jul 14 12:44:59 localhost ignition[454]: creating temporary mount point
    Jul 14 12:44:59 localhost ignition[454]: op(1): [started]  mounting config drive
    Jul 14 12:44:59 localhost ignition[454]: op(1): executing: "mount" "-o" "ro" "-t" "auto" "/dev/disk/by-label/config-2" "/tmp/ignition-configdrive2777772110"
    Jul 14 12:44:59 localhost ignition[454]: op(1): config drive ("/dev/disk/by-label/CONFIG-2") not found. Waiting...
    Jul 14 12:44:59 localhost ignition[454]: op(1): [finished] mounting config drive
    Jul 14 12:44:59 localhost ignition[454]: op(2): [started]  unmounting "/dev/disk/by-label/config-2" at "/tmp/ignition-configdrive2777772110"
    Jul 14 12:44:59 localhost ignition[454]: op(2): [finished] unmounting "/dev/disk/by-label/config-2" at "/tmp/ignition-configdrive2777772110"
    Jul 14 12:44:59 localhost ignition[454]: parsing config with SHA512: dcfff555992b5bc8c2f3460af96d0eece4e8f9b76892103dd9378da89fdfb46190450b03b041a7333b554afb2af5cdf4098eb2e6d067af572e69d87423a04447
    Jul 14 12:44:59 localhost ignition[454]: fetched base config from "system"
    Jul 14 12:44:59 localhost ignition[454]: fetched base config from "system"
    Jul 14 12:44:59 localhost ignition[454]: fetched user config from "openstack"
    Jul 14 12:44:59 localhost ignition[454]: fetch: fetch complete
    Jul 14 12:44:59 localhost ignition[454]: fetch: fetch passed
    Jul 14 12:44:59 localhost ignition[454]: Ignition finished successfully
    ...
    Jul 14 12:44:59 localhost ignition[511]: INFO     : Ignition v2.14.0
    Jul 14 12:44:59 localhost ignition[511]: INFO     : Stage: files
    Jul 14 12:44:59 localhost ignition[511]: INFO     : no config dir at "/usr/lib/ignition/base.d"
    Jul 14 12:44:59 localhost ignition[511]: INFO     : no config dir at "/usr/lib/ignition/base.platform.d/openstack"
    Jul 14 12:44:59 localhost ignition[511]: INFO     : files: ensureUsers: op(1): [started]  creating or modifying user "r-fujii"
    Jul 14 12:44:59 localhost ignition[511]: DEBUG    : files: ensureUsers: op(1): executing: "useradd" "--root" "/sysroot" "--create-home" "--password" "*" "r-fujii"
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: ensureUsers: op(1): [finished] creating or modifying user "r-fujii"
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: ensureUsers: op(2): [started]  adding ssh keys to user "r-fujii"
    Jul 14 12:45:01 localhost ignition[511]: wrote ssh authorized keys file for user: r-fujii
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: ensureUsers: op(2): [finished] adding ssh keys to user "r-fujii"
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: createFilesystemsFiles: createFiles: op(3): [started]  writing file "/sysroot/etc/hostname"
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: createFilesystemsFiles: createFiles: op(3): [finished] writing file "/sysroot/etc/hostname"
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: createFilesystemsFiles: createFiles: op(4): [started]  writing file "/sysroot/etc/sudoers.d/r-fujii"
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: createFilesystemsFiles: createFiles: op(4): [finished] writing file "/sysroot/etc/sudoers.d/r-fujii"
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: createResultFile: createFiles: op(5): [started]  writing file "/sysroot/etc/.ignition-result.json"
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: createResultFile: createFiles: op(5): [finished] writing file "/sysroot/etc/.ignition-result.json"
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: op(6): [started]  relabeling 17 patterns
    Jul 14 12:45:01 localhost ignition[511]: DEBUG    : files: op(6): executing: "setfiles" "-vF0" "-r" "/sysroot" "/sysroot/etc/selinux/targeted/contexts/files/file_contexts" "-f" "-"
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: op(6): [finished] relabeling 17 patterns
    Jul 14 12:45:01 localhost ignition[511]: INFO     : files: files passed
    Jul 14 12:45:01 localhost ignition[511]: INFO     : Ignition finished successfully
    ...
    Jul 14 12:45:02 localhost systemd[1]: Switching root.

    以上のように、CoreOS 以外の Linux ディストリビューションでも Ignition を動かせることが確認できました。

    ここから先

    cloud-init の代わりとして実用に供するためにはさらに以下のような事柄が必要になります……が、長くなってきたので本記事はここまでとします。

    • cloud-init の代わりに Ignition をあらかじめ組み込んだ OS テンプレートの作成
      • 今回はとりあえず動かしてみるために既存のインスタンス上で Ignition を導入しましたが、本来は新しく作成したインスタンスの初期設定に用いるものなので、インスタンスを作成するためのテンプレートに組み込んでおく必要があります
    • 初回起動時のみ Ignition を実行させるための仕込み(ignition.firstbootまわりの制御)
      • 素の実装は CoreOS に合わせたものになっているので、使用するディストリビューションに合わせて dracut モジュールの実装に手を加える必要があると思われます
    • 今回使用していない機能の動作確認

    まとめ

    Ignition は CoreOS 以外の Linux ディストリビューションでも動かせました。


      1. 現在の CoreOS は Red Hat 社による CoreOS 社の買収後に以下の二つが合流して生まれたものです。
        • CoreOS 社が開発していた Container Linux (旧称: CoreOS)
        • Red Hat 社が開発していた Atomic Host

        Web 上で検索すると古い情報 (Container Linux や旧 CoreOS に関する記述)がヒットする場合もあるため少々注意が必要です [↑]

      2. 以前は FCCT (Fedora CoreOS Config Transpiler) という名前だったようです [↑]
      3. 公式ドキュメントにも記載がありますが、SELinux や AppArmor が有効な環境ではこれらに阻まれて QEMU が Ignition の設定を読めない場合があるため要注意です。手元の環境 (Debian GNU/Linux bullseye) では /usr/share/qemu/ が AppArmor に阻まれずに QEMU からアクセス可能なようなので、とりあえずここに設定ファイルを置きました(手抜き)[↑]
      4. ドキュメントによると設定されたアドレスはコンソールに表示されるようですが、手元で試した際は表示されませんでした [↑]
      5. /boot/ignition.firstboot が存在する場合に ignition.firstboot をカーネルパラメータに追加する仕組みが GRUB の設定に仕込まれています。このファイルは元々ディスクイメージに含まれていて、初回起動が完了すると削除されます [↑]
      6. ここで /usr/lib/dracut/modules.d/45url-lib/module-setup.sh: line 33: warning: command substitution: ignored null byte in input という warning が出ますが、とりあえず無視して進めます [↑]

    r-fujii

    2022年08月01日 月曜日

    IaaS の開発と運用をしています。

    Related
    関連記事