Proxmox VEで学ぶcloud-init

サムネイル

はじめに

ITインフラ本部 インフラ部 ネットワークグループ NREチームの山口です。私の所属しているチームではネットワーク運用の自動化やそれらにまつわるシステムの開発をしています。

本記事では、仮想マシンの初期設定を自動化するcloud-initについて、オープンソースの仮想化プラットフォーム「Proxmox Virtual Environment(以下、Proxmox VEと呼ぶ)」1を使って解説します。Proxmox VEは企業環境から自宅ラボまで幅広く使われている実用的な仮想化基盤です。

記事の読み方:

cloud-initとは

cloud-initは、クロスプラットフォームのクラウドインスタンス初期化において広く採用されているマルチディストリビューション方式です。主要なパブリッククラウド、プライベートクラウド、ベアメタルインストールなど、幅広い環境でサポートされています。cloud-initはインスタンスの起動時に、実行されているクラウドを識別し、それに応じてシステムを初期化し、ネットワーク、ストレージ、SSHキー、パッケージなどのプロビジョニングを行います。

Proxmox VEでもcloud-initを正式にサポートしています。cloud-initを利用することでインスタンスを作成したあとに毎回行っている共通の設定やパッケージのインストールを自動化できます。

Proxmox VEで簡単にcloud-initを体験してみよう

cloud-initの詳細に入る前にProxmox VEの機能を使って簡単にcloud-initによるインスタンスの管理を体験してみましょう。これだけでも十分に活用できるはずです。

cloud-initイメージの取得

まずはcloud-initのベースイメージを準備します。様々なディストリビューションからcloud-initとしてすぐに利用可能なイメージが配布されています。今回はUbuntu24.04 nobleのcloud-initイメージを利用します。下記のリンクから実行するサーバーのCPUアーキテクチャに一致したイメージをダウンロードしてください。

https://cloud-images.ubuntu.com/noble/current/

イメージのダウンロード、アップロードはWebUIから行うこともできます。

  • 直接WebUIからダウンロードする場合: サイドバー localストレージ > ISO Images > Download from URL
  • ローカルからWebUIにアップロードする場合: サイドバー localストレージ > ISO Images > Upload

直接WebUIからダウンロードする

WebUIからイメージを保存した場合は下記のパスに格納されます。

/var/lib/vz/template/iso/

CLIからコマンドでダウンロードする場合:

Proxmox VEノードにログインして作業します。

# イメージの格納先は好きな場所で大丈夫です。テンプレート作成時にパスを指定するため、場所はメモしておいてください。
wget https://cloud-images.ubuntu.com/noble/current/noble-server-cloudimg-amd64.img -P /var/lib/vz/template/iso/

テンプレートの作成

テンプレートとはインスタンスをLinked Cloneする際のベースとなる読み取り専用のインスタンスです。cloud-initの利用に必要なわけではありませんが、テンプレートからクローンしてインスタンスを作成することで基本の設定を共通化できます。また、クローン方式をLinked CloneにすることでCopy On Writeによって記憶領域をすべてコピーする必要がなくなり、一部をテンプレートから参照するため、起動を高速化できます。2

今回作成するテンプレートのパラメーターは公式ドキュメントに記載された手順を参考に記載しています。それぞれのパラメーターは必要に応じて変更してください。またこれらの設定はWebUIからも変更できます。

Proxmox VEノードにログインして作業します。

# メモリー、接続ネットワーク、SCSIコントローラーの設定
qm create 9000 --name ubuntu24-04 --memory 2048 --net0 virtio,bridge=vmbr0 --scsihw virtio-scsi-pci

# ダウンロードしたディスクイメージをローカルLVMストレージにインポートし、SCSIドライブとして接続
qm set 9000 --scsi0 local-lvm:0,import-from=/var/lib/vz/template/iso/noble-server-cloudimg-amd64.img

# cloud-initのCDドライブを構成
qm set 9000 --ide2 local-lvm:cloudinit

# シリアルコンソールを設定。qm terminal <vmid> でCLIからコンソールに接続できるようになる
qm set 9000 --serial0 socket --vga serial0

# Cloud-Initイメージから直接起動できるようにするため、Bootパラメータを order=scsi0 に設定
qm set 9000 --boot order=scsi0

# デフォルトユーザー設定
qm set 9000 --ciuser init-user --cipassword your_password

# テンプレートの作成
qm template 9000

つぎのコマンドで設定を確認します。

qm config 9000

インスタンスのクローン

テンプレートからクローンを作成することで同じ設定のインスタンスを作成できます。

WebUIからクローンを作成する:

対象のテンプレートを右クリック > Cloneを選択 > ModeをLinked Cloneにする > Cloneをクリック。

VM Clone 1 VM Clone 2

CLIからクローンを作成する:

つぎのコマンドでテンプレートからクローンします(デフォルト Linked Clone)。

qm clone 9000 123 --name ubuntu-clone

cloud-initのパラメーターを変更する

cloud-initのパラメーターはインスタンス作成後でも変更できます。ただし設定が反映されるのは再起動後からです。

この際、内部的にはuser-dataやnetwork-configと呼ばれるcloud-configフォーマットの構成ファイルが自動的に作成され内容が編集されています。これについては後述します。

WebUIからcloud-initのパラメーターを変更する:

対象のインスタンス > Cloud-Init > 対象のパラメーターを選択 > Editをクリック。

cloud-init setting

CLIからcloud-initのパラメーターを変更する:

qm set 123 \
    --ciuser change-user \
    --cipassword your_password \
    --sshkey ~/.ssh/id_rsa.pub \
    --ipconfig0 ip=192.0.2.1/24,gw=192.0.2.254 \
    --ciupgrade false \
    --nameserver 192.0.2.53 \
    --searchdomain example.com

上から:

  • デフォルトユーザー
  • デフォルトユーザーパスワード
  • SSH公開鍵
  • IPアドレス・GW
  • 最初の起動後に自動でパッケージアップグレードを実行するか
  • DNSサーバー
  • DNS検索ドメイン

インスタンスの起動:

qm start 123

インスタンスが起動するとcloud-initで設定したパラメーターが反映されているはずです。

以上がProxmox VEでの基本的なcloud-initの活用でした。ここからはcloud-initの内部的な仕組みを詳しく見ていきましょう。

cloud-initの仕組み

ここからはcloud-initの仕組みについて詳しく見ていきます。cloud-initで利用する構成ファイルの書き方やインスタンスが起動された後、実際にどのようにcloud-initが動作するかを一緒に学んでいきましょう。

cloud-config

cloud-initによって構成されるパラメーターの設定は「cloud-config」と呼ばれる特定のユーザーデータ形式によって管理されています。cloud-configはYAML形式のファイルです。

下記は先ほどの手順で作成したインスタンスのcloud-configのうちuser-dataとnetwork-configです。入力したパラメーターが記述されているのがわかります。今回紹介した2つの構成ファイルの他に2種類、合計4種類の構成ファイルがありますが、それらは後ほど紹介します。

  • user-data
#cloud-config
hostname: ubuntu-clone
manage_etc_hosts: true
fqdn: ubuntu-clone.example.com
user: change-user
password: $5XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
ssh_authorized_keys:
- ssh-rsa AAAAB3XXXXXXXXX...
chpasswd:
expire: False
users:
- default
  • network-config
network:
  version: 2
  ethernets:
    eth0:
      match:
        macaddress: "00:00:5e:00:53:01"
      addresses:
      - "192.0.2.1/24"
      nameservers:
        addresses:
        - 192.0.2.53
        search:
        - example.com
      set-name: "eth0"
      routes:
      - to: "default"
        via: "192.0.2.254"

User-data formats

cloud-configの書式を見ていきます。

1行目に記載されているコメント行は使用されているユーザーデータ形式を表すヘッダーとして機能します。これは必ず指定しなければなりません。

#cloud-config

cloud-initはこのヘッダーを確認することでユーザーデータ形式を特定し処理を行います。今回は基本的なcloud-configの説明にとどめて、それぞれの詳細には立ち入りませんが、Jinjaテンプレートを利用して動的にユーザーデータを構成することや複数のユーザーデータをまとめたり様々な機能をサポートしています。

USER-DATA FORMAT HEADER CONTENT-TYPE
Cloud config data #cloud-config text/cloud-config
User-data script #! text/x-shellscript
Cloud boothook #cloud-boothook text/cloud-boothook
MIME multi-part Content-Type: multipart/mixed multipart/mixed
Cloud config archive #cloud-config-archive text/cloud-config-archive
Jinja template ## template: jinja text/jinja2
Include file #include text/x-include-url
Part handler #part-handler text/part-handler

引用:cloud-init documentation. User-data formats.

Modules

2行目以降に続く行はモジュールを示します。キーバリュー方式でキーにモジュール名、バリューにその引数を渡します。ここで指定したモジュールがインスタンスを起動した際に各フェーズごとに実行され、インスタンスを構成します。

例えば、起動時に特定のコマンドを実行したい場合はruncmdモジュールを利用します。

#cloud-config
runcmd:
  - [ls, -l, /]

こちらではpackageモジュールを利用して下記を実行します。

  • パッケージ情報を更新
  • slをインストール
#cloud-config
package_update: true
packages:
  - sl

このようにインスタンスの初期構成に必要な処理を簡単に記述できます。

なお、実行タイミングはモジュールによって異なります。例えば上記の例では runcmd モジュールが先にconfigステージで動作し、そのあとに package モジュールがfinalステージで動作します。この実行タイミングは /etc/cloud/cloud.cfg で定義されています。

# The modules that run in the 'init' stage
cloud_init_modules:
  - seed_random
  - bootcmd
  - write_files
  - growpart
  - resizefs
  - disk_setup
  - mounts
  - set_hostname
  - update_hostname
  - update_etc_hosts
  - ca_certs
  - rsyslog
  - users_groups
  - ssh
  - set_passwords

# The modules that run in the 'config' stage
cloud_config_modules:
  - wireguard
  - snap
  - ubuntu_autoinstall
  - ssh_import_id
  - keyboard
  - locale
  - grub_dpkg
  - apt_pipelining
  - apt_configure
  - ubuntu_pro
  - ntp
  - timezone
  - disable_ec2_metadata
  - runcmd
  - byobu

# The modules that run in the 'final' stage
cloud_final_modules:
  - package_update_upgrade_install
  - fan
  - landscape
  - lxd
  - ubuntu_drivers
  - write_files_deferred
  - puppet
  - chef
  - ansible
  - mcollective
  - salt_minion
  - reset_rmc
  - scripts_vendor
  - scripts_per_once
  - scripts_per_boot
  - scripts_per_instance
  - scripts_user
  - ssh_authkey_fingerprints
  - keys_to_console
  - install_hotplug
  - phone_home
  - final_message
  - power_state_change

利用可能なモジュールの詳細については下記のリンクを参照してください。

cloud-init documentation. Module reference.

cloud-initステージ遷移

cloud-initは5つのブートステージがあります。先ほどモジュールの紹介の中で、実行タイミングがモジュールによって異なるという話をしました。その実行タイミングというのが各ブートステージに当たります。

下記はcloud-initのブートステージの遷移を表した図です。

boot-stages

画像引用:cloud-init documentation. Boot stages

  1. Detect 最初のステージです。ds-identifyと呼ばれるプラットフォーム識別ツールが実行されます。このツールは、インスタンスがどのプラットフォームで実行されているかを検出します。このツールはinitシステムに統合されており、プラットフォームが見つからない場合はcloud-initを無効にし、有効なプラットフォームが検出された場合はcloud-initを有効にします。
  2. Local このステージではローカルのデータソースの探索の実行とネットワーク構成をシステムに適用します。データソースはつぎの項で解説します。
  3. Network このステージでは検出されたユーザーデータを処理します。また、cloud_init_modules にリストされたモジュールが実行されます。これらを処理するため、この時点で設定されているすべてのネットワークがオンラインになっている必要があります。このステージが完了すると、シリアルコンソールやSSH経由でシステムにアクセスできるようになります。
  4. Config このステージは cloud_config_modules にリストされているモジュールを実行します。
  5. Final 最後のステージです。 cloud_final_modules にリストされているモジュールを実行します。

データソースとランタイム構成ファイル

冒頭でも紹介したとおり「cloud-initは、クロスプラットフォームのクラウドインスタンス初期化において広く採用されているマルチディストリビューション方式です。」

そのためプラットフォームごとに対応した データソース により自動的にランタイム構成ファイルを検出します。3

Proxmox VEでは2つのデータソースをサポートしています。4

  • NoCloud:Linuxでインスタンスを作成した場合のデフォルト
  • configdrive2:Windowsでインスタンスを作成した場合のデフォルト

NoCloudでは実行時に4つのランタイム構成(cloud-config)ファイルを検出し、インスタンスを構成します。5ProxmoxVEでは qm set コマンドの --cicustom オプションでランタイム構成ファイルをインスタンスに渡し、インスタンスの起動時にこれをマウントすることでローカルからランタイム構成ファイルを検出できます。

qm set 9000 --cicustom "user=<volume>,network=<volume>,meta=<volume>,vendor=<volume>"

それぞれのランタイム構成ファイルの役割はつぎのとおりです。

  • user-data: ユーザーがインスタンスを構成できるようにする構成ファイルです。Proxmox VE のWebUIや qm set コマンドからインスタンスのcloud-configを操作した場合、自動的にこのファイルが更新されます。
  • meta-data: クラウドからインスタンスに提供される情報を含む構成ファイルです。インスタンスIDと、その他のクラウド固有のキーを含める必要があります。これは Proxmox VEによって自動的に作成されるため基本的に変更する必要はありません。
  • vendor-data: クラウド固有のデフォルト設定を提供するために使用できます。記述する内容は user-data と同じ形式です。この設定はユーザーデータによって上書きされます。
  • network-config: クラウド固有のネットワーク設定が行われます。Proxmox VE のWebUIや qm set コマンドからインスタンスのcloud-configを操作した場合、自動的にこのファイルが更新されます。

cloud-initをカスタムしてみよう

cloud-initの動作を確認するために自作のcloud-configを作成し、実際にインスタンスのvendor-dataに登録します。なぜuser-dataではないのかというと、user-dataを更新するとカスタムのcloud-configが優先され、WebUIや qm set コマンドで設定したcloud-initのパラメーターが上書きされてしまうためです。

そのため今回はvendor-dataを利用します。実際に利用する場合は要件に合わせて使い分けてください。

カスタムのcloud-configファイルを作成します。ファイルは /var/lib/vz/snippets/ に配置してください。

vi /var/lib/vz/snippets/custom-vendor.yml

ここではつぎのモジュールの動作を確認します。この3つのモジュールはそれぞれ異なるステージで実行されます。

  • Networkステージ:write_files
  • Configステージ:runcmd
  • Finalステージ:packages (slコマンドをインストール)

write_filesのcontentをencoding: b64 としているため、文字列をBase64で符号化したものを設定します。実際に書き込まれるときはデコードされ、元の文字列が見えるはずです。

#cloud-config

runcmd:
  - [ls, -l, /]

write_files:
  - encoding: b64
    content: |
        44GT44GT44G+44Gn6KiY5LqL44KS6Kqt44KT44Gn44GP44Gg44GV44KK44GC44KK44GM44Go44GG
        44GU44GW44GE44G+44GZ77yB44Gd44KT44Gq5o6i5rGC5b+D44KS44KC44Gj44Gf44GC44Gq44Gf
        44Gr77yBCgpETU0g44Gn44Gv44CB44ON44OD44OI44Ov44O844Kv44Kw44Or44O844OX44GuIE5S
        Re+8iE5ldHdvcmsgUmVsaWFiaWxpdHkgRW5naW5lZXJpbmfvvInjg4Hjg7zjg6Djga7ku7LplpPj
        gpLli5/pm4bjgZfjgabjgYTjgb7jgZnjgIIKTlJFIOOBr+ODjeODg+ODiOODr+ODvOOCr+OBruS/
        oemgvOaAp+OCkueiuuS/neOBmeOCi+OBoOOBkeOBp+OBquOBj+OAgeiHquWLleWMluOChOOCveOD
        leODiOOCpuOCp+OCoueahOOBquOCouODl+ODreODvOODgeOBp+mBi+eUqOOCkuaUueWWhOOBl+OB
        puOBhOOBj+OBk+OBqOOCkuODn+ODg+OCt+ODp+ODs+OBqOOBl+OBpuOBhOOBvuOBmeOAggoK44ON
        44OD44OI44Ov44O844Kv44Gu55+l6K2Y44KS44OZ44O844K544Gr44CB44KC44Gj44Go6Ieq5YuV
        5YyW44KS5Y+W44KK5YWl44KM44Gm44G/44Gf44GE5pa5CuOCs+ODvOODieOChOODhOODvOODq+OC
        kumnhuS9v+OBl+OBpuOAjOODjeODg+ODiOODr+ODvOOCr+mBi+eUqOOCkumAsuWMluOBleOBm+OB
        n+OBhOOAjeOBqOaAneOBo+OBpuOBhOOCi+aWuQrjgqTjg7Pjg5Xjg6njga7mlLnlloTjgavkuLvk
        vZPnmoTjgavlj5bjgorntYTjgb/jgZ/jgYTmlrkK44ON44OD44OI44Ov44O844Kv5qmf5Zmo44Gu
        44Oh44OI44Oq44Kv44K544Gu5Y+O6ZuG44KE5rS755So44KS44GX44Gf44GE44Go5oCd44Gj44Gm
        44GE44KL5pa5CuOBneOCk+OBquaAneOBhOOCkuaMgeOBo+OBn+aWueOBr+OAgeOBnOOBsuOCq+OC
        uOODpeOCouODq+mdouirh+OCguOChOOBo+OBpuOBhOOCi+OBruOBp+awl+i7veOBq+OBqeOBhuOB
        nu+8gQpodHRwczovL3BpdHRhLm1lL21hdGNoZXMvVFhneG9LU2NHaUZFCg==
    owner: root:root
    path: /var/tmp/message.txt
    permissions: '0644'

package_update: true
packages:
  - sl

つぎのコマンドでcloud-configを登録します。Proxmox VE Ver8.4時点ではWebUIからの登録はできません。 qm コマンドを使用します。

qm set 9000 --cicustom "vendor=local:snippets/custom-vendor.yml"

インスタンスをクローンして起動します。パッケージをインストールするためにインターネットに接続する必要があります。DHCPを利用するかIPアドレスの設定をしてください。

qm clone 9000 124 --name ubuntu-custom
qm set 124 --ipconfig0 ip=192.0.2.124/24,gw=192.0.2.254 --nameserver 192.0.2.53
qm start 124

# シリアルコンソールで接続。接続後は Ctrl+o で接続を終了できます。
qm terminal 124

起動直後にシリアルコンソールでターミナルに接続するとcloud-initのログが出力されているのがわかります。このログは保存されているため後から見返すことができます。詳細については「cloud-initのデバッグ」の項で解説します。

インスタンスにログインしたあと下記のコマンドを実行し、 status: done と出力されていればcloud-initの処理はすべて終了しています。

cloud-init status

ファイルを見てみましょう。先ほど custom-vendor.yml 内の write_files モジュールで定義したファイルが作成されています🎉

中に何が書かれているかはぜひご自身で確かめてください!

init-user@ubuntu-custom:~$ ls /var/tmp/message.txt
/var/tmp/message.txt

また、 sl コマンドも実行可能になっているはずです。

cloud-initのデバッグ

今までcloud-initの基本的な仕組みと簡単なcloud-configの動作を見てきました。実行時のログをみて裏側でどのように処理されているか確認してみましょう。

/var/log 配下に2つのログファイルが作成されています。

init-user@ubuntu-custom:~$ sudo ls /var/log/cloud-init*
/var/log/cloud-init-output.log  /var/log/cloud-init.log

/var/log/cloud-init-output.log

このログはcloud-init実行時の各ステージからの出力をキャプチャしたものです。

ログを見ていくと runcmd で実行したコマンドの結果やパッケージのダウンロードログが出力されているのがわかります。

  • ls コマンド
total 76
lrwxrwxrwx   1 root root     7 Apr 22  2024 bin -> usr/bin
drwxr-xr-x   2 root root  4096 Feb 26  2024 bin.usr-is-merged
drwxr-xr-x   5 root root  4096 Nov  5 06:43 boot
drwxr-xr-x  18 root root  3900 Nov  5 06:42 dev
drwxr-xr-x 106 root root  4096 Nov  5 06:43 etc
drwxr-xr-x   3 root root  4096 Nov  5 06:42 home
lrwxrwxrwx   1 root root     7 Apr 22  2024 lib -> usr/lib
drwxr-xr-x   2 root root  4096 Apr  8  2024 lib.usr-is-merged
lrwxrwxrwx   1 root root     9 Apr 22  2024 lib64 -> usr/lib64
drwx------   2 root root 16384 Oct 26 12:54 lost+found
drwxr-xr-x   2 root root  4096 Oct 26 12:51 media
drwxr-xr-x   2 root root  4096 Oct 26 12:51 mnt
drwxr-xr-x   2 root root  4096 Oct 26 12:51 opt
dr-xr-xr-x 160 root root     0 Nov  5 06:41 proc
drwx------   3 root root  4096 Oct 26 12:54 root
drwxr-xr-x  28 root root   860 Nov  5 06:43 run
lrwxrwxrwx   1 root root     8 Apr 22  2024 sbin -> usr/sbin
drwxr-xr-x   2 root root  4096 Mar 31  2024 sbin.usr-is-merged
drwxr-xr-x   2 root root  4096 Nov  5 06:42 snap
drwxr-xr-x   2 root root  4096 Oct 26 12:51 srv
dr-xr-xr-x  13 root root     0 Nov  5 06:41 sys
drwxrwxrwt  14 root root  4096 Nov  5 06:43 tmp
drwxr-xr-x  12 root root  4096 Oct 26 12:51 usr
drwxr-xr-x  13 root root  4096 Nov  5 06:41 var
  • パッケージダウンロード
The following NEW packages will be installed:
sl
0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded.
Need to get 12.7 kB of archives.
After this operation, 60.4 kB of additional disk space will be used.
Get:1 http://archive.ubuntu.com/ubuntu noble/universe amd64 sl amd64 5.02-1 [12.7 kB]
Fetched 12.7 kB in 0s (33.2 kB/s)
Selecting previously unselected package sl.
(Reading database ... 106393 files and directories currently installed.)
Preparing to unpack .../archives/sl_5.02-1_amd64.deb ...
Unpacking sl (5.02-1) ...
Setting up sl (5.02-1) ...
Processing triggers for man-db (2.12.0-4build2) ...

/var/log/cloud-init.log

実行された各アクションのデバッグ出力を含む非常に詳細なログです。このログを追うことで実際にcloud-initがどのように動作しているかを確認できます。少し覗いてみましょう。

これはログの最初の2行です。

PID1(systemd)によってinit-local(cloud-init-local.service)が実行されます。これが2つ目のブートステージであるLocalステージの開始です。

2025-11-05 06:42:00,986 - log_util.py[DEBUG]: Cloud-init v. 25.2-0ubuntu1~24.04.1 running 'init-local' at Wed, 05 Nov 2025 06:42:00 +0000. Up 14.94 seconds.
2025-11-05 06:42:00,987 - main.py[INFO]: PID [1] started cloud-init 'init-local'.

Localステージ

Localステージが始まると、キャッシュのチェックやログの書き込み行われます。下記はその処理後のログです。インスタンスのディストリビューションの特定と利用可能なデータソースを探索します。 Detected DataSourceNoCloud と表示されているとおり、NoCloudの検出に成功しています。

2025-11-05 06:42:01,022 - stages.py[DEBUG]: Using distro class <class 'cloudinit.distros.ubuntu.Distro'>
2025-11-05 06:42:01,023 - sources[DEBUG]: Looking for data source in: ['NoCloud', 'None'], via packages ['', 'cloudinit.sources'] that matches dependencies ['FILESYSTEM']
2025-11-05 06:42:01,028 - sources[DEBUG]: Searching for local data source in: ['DataSourceNoCloud']
2025-11-05 06:42:01,028 - handlers.py[DEBUG]: start: init-local/search-NoCloud: searching for local data from DataSourceNoCloud
2025-11-05 06:42:01,028 - sources[DEBUG]: Seeing if we can get any data from <class 'cloudinit.sources.DataSourceNoCloud.DataSourceNoCloud'>
2025-11-05 06:42:01,029 - sources[DEBUG]: Update datasource metadata and network config due to events: boot-new-instance
2025-11-05 06:42:01,029 - sources[DEBUG]: Detected DataSourceNoCloud

その後、ランタイム構成ファイルの探索が始まります。この環境では最終的に/dev/sr0(0番目のCD-ROM)からランタイム構成ファイルを検出しています。

2025-11-05 06:42:01,030 - util.py[DEBUG]: Reading from /var/lib/cloud/seed/nocloud/user-data (quiet=False)
2025-11-05 06:42:01,030 - util.py[DEBUG]: Reading from /var/lib/cloud/seed/nocloud/meta-data (quiet=False)
2025-11-05 06:42:01,030 - util.py[DEBUG]: Reading from /var/lib/cloud/seed/nocloud/vendor-data (quiet=False)
2025-11-05 06:42:01,030 - util.py[DEBUG]: Reading from /var/lib/cloud/seed/nocloud/network-config (quiet=False)
2025-11-05 06:42:01,030 - util.py[DEBUG]: Reading from /var/lib/cloud/seed/nocloud-net/user-data (quiet=False)
2025-11-05 06:42:01,030 - util.py[DEBUG]: Reading from /var/lib/cloud/seed/nocloud-net/meta-data (quiet=False)
2025-11-05 06:42:01,031 - util.py[DEBUG]: Reading from /var/lib/cloud/seed/nocloud-net/vendor-data (quiet=False)
2025-11-05 06:42:01,031 - util.py[DEBUG]: Reading from /var/lib/cloud/seed/nocloud-net/network-config (quiet=False)
2025-11-05 06:42:01,031 - subp.py[DEBUG]: Running command ['blkid', '-tTYPE=vfat', '-odevice'] with allowed return codes [0, 2] (shell=False, capture=True)
2025-11-05 06:42:01,104 - performance.py[DEBUG]: Running ['blkid', '-tTYPE=vfat', '-odevice'] took 0.073 seconds
2025-11-05 06:42:01,104 - subp.py[DEBUG]: Running command ['blkid', '-tTYPE=iso9660', '-odevice'] with allowed return codes [0, 2] (shell=False, capture=True)
2025-11-05 06:42:01,117 - performance.py[DEBUG]: Running ['blkid', '-tTYPE=iso9660', '-odevice'] took 0.013 seconds
2025-11-05 06:42:01,117 - subp.py[DEBUG]: Running command ['blkid', '-tLABEL=CIDATA', '-odevice'] with allowed return codes [0, 2] (shell=False, capture=True)
2025-11-05 06:42:01,129 - performance.py[DEBUG]: Running ['blkid', '-tLABEL=CIDATA', '-odevice'] took 0.012 seconds
2025-11-05 06:42:01,129 - subp.py[DEBUG]: Running command ['blkid', '-tLABEL=cidata', '-odevice'] with allowed return codes [0, 2] (shell=False, capture=True)
2025-11-05 06:42:01,142 - performance.py[DEBUG]: Running ['blkid', '-tLABEL=cidata', '-odevice'] took 0.012 seconds
2025-11-05 06:42:01,142 - subp.py[DEBUG]: Running command ['blkid', '-tLABEL_FATBOOT=cidata', '-odevice'] with allowed return codes [0, 2] (shell=False, capture=True)
2025-11-05 06:42:01,153 - performance.py[DEBUG]: Running ['blkid', '-tLABEL_FATBOOT=cidata', '-odevice'] took 0.012 seconds
2025-11-05 06:42:01,154 - DataSourceNoCloud.py[DEBUG]: Attempting to use data from /dev/sr0

実際に検出に利用されたコマンドを確認しましょう。sr0のディスクにcidataというラベルが付与されているのがわかります。cloud-initはこのラベルを目印にしてランタイム構成ファイルを探索しています。

init-user@ubuntu-custom:~$ blkid -tLABEL=cidata -odevice
/dev/sr0

init-user@ubuntu-custom:~$ lsblk -f /dev/sr0
NAME FSTYPE FSVER LABEL  UUID                                 FSAVAIL FSUSE% MOUNTPOINTS
sr0  iso966       cidata 2025-11-05-15-41-36-00

探索が完了するとディスクをマウントし、内容を読み取ります。読み取りが完了するとすぐにディスクはアンマウントされます。

2025-11-05 06:42:01,155 - subp.py[DEBUG]: Running command ['mount', '-o', 'ro', '-t', 'auto', '/dev/sr0', '/run/cloud-init/tmp/tmplxf0cgdy'] with allowed return codes [0] (shell=False, capture=True)
2025-11-05 06:42:01,256 - performance.py[DEBUG]: Running ['mount', '-o', 'ro', '-t', 'auto', '/dev/sr0', '/run/cloud-init/tmp/tmplxf0cgdy'] took 0.101 seconds
2025-11-05 06:42:01,257 - util.py[DEBUG]: Reading from /run/cloud-init/tmp/tmplxf0cgdy//user-data (quiet=False)
2025-11-05 06:42:01,261 - util.py[DEBUG]: Reading 230 bytes from /run/cloud-init/tmp/tmplxf0cgdy//user-data
2025-11-05 06:42:01,261 - util.py[DEBUG]: Reading from /run/cloud-init/tmp/tmplxf0cgdy//meta-data (quiet=False)
2025-11-05 06:42:01,265 - util.py[DEBUG]: Reading 54 bytes from /run/cloud-init/tmp/tmplxf0cgdy//meta-data
2025-11-05 06:42:01,265 - util.py[DEBUG]: Reading from /run/cloud-init/tmp/tmplxf0cgdy//vendor-data (quiet=False)
2025-11-05 06:42:01,269 - util.py[DEBUG]: Reading 1630 bytes from /run/cloud-init/tmp/tmplxf0cgdy//vendor-data
2025-11-05 06:42:01,270 - util.py[DEBUG]: Reading from /run/cloud-init/tmp/tmplxf0cgdy//network-config (quiet=False)
2025-11-05 06:42:01,274 - util.py[DEBUG]: Reading 328 bytes from /run/cloud-init/tmp/tmplxf0cgdy//network-config
2025-11-05 06:42:01,274 - subp.py[DEBUG]: Running command ['umount', '/run/cloud-init/tmp/tmplxf0cgdy'] with allowed return codes [0] (shell=False, capture=True)

実際にマウントして中身を確認してみましょう。

4つのランタイム構成ファイルが確認できました。さらにファイルの中を確認すると見覚えのある設定が記述されているはずです。

# CD-ROMのマウント
init-user@ubuntu-custom:~$ sudo mkdir /mnt/config
init-user@ubuntu-custom:~$ sudo mount /dev/sr0 /mnt/config
mount: /mnt/config: WARNING: source write-protected, mounted read-only.

# CD-ROMの中のファイルを確認
init-user@ubuntu-custom:~$ ls /mnt/config
meta-data  network-config  user-data  vendor-data

設定の読み取りが完了した後は最初にネットワークの設定が行われます。

※一部情報はマスクしています。

2025-11-05 06:42:01,356 - networking.py[DEBUG]: net: all expected physical devices present
2025-11-05 06:42:01,356 - stages.py[DEBUG]: applying net config names for {'version': 1, 'config': [{'type': 'physical', 'name': 'eth0', 'mac_address': 'xx:xx:xx:xx:xx:xx', 'subnets': [{'type': 'static', 'address': 'x.x.x.x', 'netmask': '255.255.255.0', 'gateway': 'x.x.x.x'}]}, {'type': 'nameserver', 'address': ['x.x.x.x'], 'search': ['xxxxxxxx']}]}

finish: init-local のログが出力され無事にLocalステージが終了しました。そのすぐ後にcloud-init.service が起動し、Networkステージが開始されます。

2025-11-05 06:42:02,502 - handlers.py[DEBUG]: finish: init-local: SUCCESS: searching for local datasources
2025-11-05 06:42:05,365 - log_util.py[DEBUG]: Cloud-init v. 25.2-0ubuntu1~24.04.1 running 'init' at Wed, 05 Nov 2025 06:42:05 +0000. Up 19.33 seconds.
2025-11-05 06:42:05,366 - main.py[INFO]: PID [1] started cloud-init 'init'.

Networkステージ

ユーザーデータファイルのヘッダーを確認しユーザーデータ形式ごとにそれぞれ処理を行っています。この後、ベンダーデータでも同様の処理を行います。

2025-11-05 06:42:05,518 - handlers.py[DEBUG]: start: init-network/consume-user-data: reading and applying user-data
2025-11-05 06:42:05,519 - stages.py[DEBUG]: Added default handler for {'text/cloud-config-jsonp', 'text/cloud-config'} from CloudConfigPartHandler: [['text/cloud-config', 'text/cloud-config-jsonp']]
2025-11-05 06:42:05,519 - stages.py[DEBUG]: Added default handler for {'text/x-shellscript'} from ShellScriptPartHandler: [['text/x-shellscript']]
2025-11-05 06:42:05,519 - stages.py[DEBUG]: Added default handler for {'text/x-shellscript-per-boot'} from ShellScriptByFreqPartHandler: [['text/x-shellscript-per-boot']]
2025-11-05 06:42:05,519 - stages.py[DEBUG]: Added default handler for {'text/x-shellscript-per-instance'} from ShellScriptByFreqPartHandler: [['text/x-shellscript-per-instance']]
2025-11-05 06:42:05,519 - stages.py[DEBUG]: Added default handler for {'text/x-shellscript-per-once'} from ShellScriptByFreqPartHandler: [['text/x-shellscript-per-once']]
2025-11-05 06:42:05,520 - stages.py[DEBUG]: Added default handler for {'text/cloud-boothook'} from BootHookPartHandler: [['text/cloud-boothook']]
2025-11-05 06:42:05,520 - stages.py[DEBUG]: Added default handler for {'text/jinja2'} from JinjaTemplatePartHandler: [['text/jinja2']]
2025-11-05 06:42:05,520 - handlers[DEBUG]: Calling handler CloudConfigPartHandler: [['text/cloud-config', 'text/cloud-config-jsonp']] (__begin__, None, 3) with frequency once-per-instance
2025-11-05 06:42:05,520 - handlers[DEBUG]: Calling handler ShellScriptPartHandler: [['text/x-shellscript']] (__begin__, None, 2) with frequency once-per-instance
2025-11-05 06:42:05,520 - handlers[DEBUG]: Calling handler ShellScriptByFreqPartHandler: [['text/x-shellscript-per-boot']] (__begin__, None, 2) with frequency once-per-instance
2025-11-05 06:42:05,520 - handlers[DEBUG]: Calling handler ShellScriptByFreqPartHandler: [['text/x-shellscript-per-instance']] (__begin__, None, 2) with frequency once-per-instance
2025-11-05 06:42:05,520 - handlers[DEBUG]: Calling handler ShellScriptByFreqPartHandler: [['text/x-shellscript-per-once']] (__begin__, None, 2) with frequency once-per-instance
2025-11-05 06:42:05,521 - handlers[DEBUG]: Calling handler BootHookPartHandler: [['text/cloud-boothook']] (__begin__, None, 2) with frequency once-per-instance
2025-11-05 06:42:05,521 - handlers[DEBUG]: Calling handler JinjaTemplatePartHandler: [['text/jinja2']] (__begin__, None, 3) with frequency once-per-instance
2025-11-05 06:42:05,521 - handlers[DEBUG]: {'MIME-Version': '1.0', 'Content-Type': 'text/cloud-config', 'Content-Disposition': 'attachment; filename="part-001"'}
2025-11-05 06:42:05,521 - handlers[DEBUG]: Calling handler CloudConfigPartHandler: [['text/cloud-config', 'text/cloud-config-jsonp']] (text/cloud-config, part-001, 3) with frequency once-per-instance
2025-11-05 06:42:05,521 - util.py[DEBUG]: Attempting to load yaml from string of length 230 with allowed root types (<class 'dict'>,)
2025-11-05 06:42:05,523 - cloud_config.py[DEBUG]: Merging by applying [('dict', ['replace']), ('list', []), ('str', [])]
2025-11-05 06:42:05,524 - handlers[DEBUG]: Calling handler CloudConfigPartHandler: [['text/cloud-config', 'text/cloud-config-jsonp']] (__end__, None, 3) with frequency once-per-instance
2025-11-05 06:42:05,537 - performance.py[DEBUG]: Dumping yaml took 0.013 seconds
2025-11-05 06:42:05,538 - util.py[DEBUG]: Writing to /var/lib/cloud/instances/2ded61c9a451b9df3586001759916bffb7efc148/cloud-config.txt - wb: [600] 266 bytes
2025-11-05 06:42:05,539 - handlers[DEBUG]: Calling handler ShellScriptPartHandler: [['text/x-shellscript']] (__end__, None, 2) with frequency once-per-instance
2025-11-05 06:42:05,539 - handlers[DEBUG]: Calling handler ShellScriptByFreqPartHandler: [['text/x-shellscript-per-boot']] (__end__, None, 2) with frequency once-per-instance
2025-11-05 06:42:05,539 - handlers[DEBUG]: Calling handler ShellScriptByFreqPartHandler: [['text/x-shellscript-per-instance']] (__end__, None, 2) with frequency once-per-instance
2025-11-05 06:42:05,539 - handlers[DEBUG]: Calling handler ShellScriptByFreqPartHandler: [['text/x-shellscript-per-once']] (__end__, None, 2) with frequency once-per-instance
2025-11-05 06:42:05,539 - handlers[DEBUG]: Calling handler BootHookPartHandler: [['text/cloud-boothook']] (__end__, None, 2) with frequency once-per-instance
2025-11-05 06:42:05,539 - handlers[DEBUG]: Calling handler JinjaTemplatePartHandler: [['text/jinja2']] (__end__, None, 3) with frequency once-per-instance
2025-11-05 06:42:05,539 - handlers.py[DEBUG]: finish: init-network/consume-user-data: SUCCESS: reading and applying user-data

write_files モジュールで /var/tmp/message.txt ファイルを作成したことがわかります。同様にユーザー設定やSSH、ホスト名の設定などのモジュールもこのタイミングで実行されています。

2025-11-05 06:42:05,794 - handlers.py[DEBUG]: start: init-network/config-write_files: running config-write_files with frequency once-per-instance
2025-11-05 06:42:05,795 - util.py[DEBUG]: Writing to /var/lib/cloud/instances/2ded61c9a451b9df3586001759916bffb7efc148/sem/config_write_files - wb: [644] 24 bytes
2025-11-05 06:42:05,795 - helpers.py[DEBUG]: Running config-write_files using lock (<FileLock using file '/var/lib/cloud/instances/2ded61c9a451b9df3586001759916bffb7efc148/sem/config_write_files'>)
2025-11-05 06:42:05,795 - util.py[DEBUG]: Writing to /var/tmp/message.txt - wb: [644] 955 bytes
2025-11-05 06:42:05,796 - util.py[DEBUG]: Changing the ownership of /var/tmp/message.txt to 0:0
2025-11-05 06:42:05,796 - handlers.py[DEBUG]: finish: init-network/config-write_files: SUCCESS: config-write_files ran successfully and took 0.002 seconds

finish: init-network のログが出力され無事にNetworkステージが終了しました。その後cloud-config.serviceが起動し、Configステージが開始されます。

2025-11-05 06:42:07,828 - handlers.py[DEBUG]: finish: init-network: SUCCESS: searching for network datasources
2025-11-05 06:42:11,755 - log_util.py[DEBUG]: Cloud-init v. 25.2-0ubuntu1~24.04.1 running 'modules:config' at Wed, 05 Nov 2025 06:42:11 +0000. Up 25.60 seconds.
2025-11-05 06:42:11,756 - main.py[INFO]: PID [1] started cloud-init 'modules:config'.

Configステージ

この段階ではモジュールの実行のみが行われます。以下のログでは runcmd モジュールが実行されています。

2025-11-05 06:42:12,653 - modules.py[DEBUG]: Running module runcmd (<module 'cloudinit.config.cc_runcmd' from '/usr/lib/python3/dist-packages/cloudinit/config/cc_runcmd.py'>) with frequency once-per-instance
2025-11-05 06:42:12,654 - handlers.py[DEBUG]: start: modules-config/config-runcmd: running config-runcmd with frequency once-per-instance
2025-11-05 06:42:12,654 - util.py[DEBUG]: Writing to /var/lib/cloud/instances/2ded61c9a451b9df3586001759916bffb7efc148/sem/config_runcmd - wb: [644] 24 bytes
2025-11-05 06:42:12,656 - helpers.py[DEBUG]: Running config-runcmd using lock (<FileLock using file '/var/lib/cloud/instances/2ded61c9a451b9df3586001759916bffb7efc148/sem/config_runcmd'>)
2025-11-05 06:42:12,656 - util.py[DEBUG]: Shellified 1 commands.
2025-11-05 06:42:12,657 - util.py[DEBUG]: Writing to /var/lib/cloud/instances/2ded61c9a451b9df3586001759916bffb7efc148/scripts/runcmd - wb: [700] 24 bytes
2025-11-05 06:42:12,658 - handlers.py[DEBUG]: finish: modules-config/config-runcmd: SUCCESS: config-runcmd ran successfully and took 0.005 seconds

finish: modules-config のログが出力されこちらも無事に処理が終了しました。その後cloud-final.serviceが起動し、Finalステージが開始されます。

2025-11-05 06:42:12,665 - handlers.py[DEBUG]: finish: modules-config: SUCCESS: running modules for config
2025-11-05 06:42:16,175 - log_util.py[DEBUG]: Cloud-init v. 25.2-0ubuntu1~24.04.1 running 'modules:final' at Wed, 05 Nov 2025 06:42:16 +0000. Up 30.12 seconds.
2025-11-05 06:42:16,175 - main.py[INFO]: PID [1] started cloud-init 'modules:final'.

Finalステージ

Finalステージではパッケージのインストールやユーザースクリプトの実行が行われます。

下記は package_update_upgrade_install モジュールのログです。パッケージの更新や sl コマンドがインストールされているのがわかります。

2025-11-05 06:42:16,236 - handlers.py[DEBUG]: start: modules-final/config-package_update_upgrade_install: running config-package_update_upgrade_install with frequency once-per-instance
2025-11-05 06:42:16,236 - util.py[DEBUG]: Writing to /var/lib/cloud/instances/2ded61c9a451b9df3586001759916bffb7efc148/sem/config_package_update_upgrade_install - wb: [644] 24 bytes
2025-11-05 06:42:16,237 - helpers.py[DEBUG]: Running config-package_update_upgrade_install using lock (<FileLock using file '/var/lib/cloud/instances/2ded61c9a451b9df3586001759916bffb7efc148/sem/config_package_update_upgrade_install'>)
2025-11-05 06:42:16,237 - util.py[DEBUG]: Writing to /var/lib/cloud/instances/2ded61c9a451b9df3586001759916bffb7efc148/sem/update_sources - wb: [644] 22 bytes
2025-11-05 06:42:16,238 - helpers.py[DEBUG]: Running update-sources using lock (<FileLock using file '/var/lib/cloud/instances/2ded61c9a451b9df3586001759916bffb7efc148/sem/update_sources'>)
2025-11-05 06:42:16,238 - apt.py[DEBUG]: Waiting for APT lock
2025-11-05 06:42:16,241 - apt.py[DEBUG]: APT lock available
2025-11-05 06:42:16,242 - subp.py[DEBUG]: Running command ['eatmydata', 'apt-get', '--option=Dpkg::Options::=--force-confold', '--option=Dpkg::options::=--force-unsafe-io', '--assume-yes', '--quiet', 'update'] with allowed return codes [0] (shell=False, capture=False)
2025-11-05 06:42:44,136 - performance.py[DEBUG]: Running ['eatmydata', 'apt-get', '--option=Dpkg::Options::=--force-confold', '--option=Dpkg::options::=--force-unsafe-io', '--assume-yes', '--quiet', 'update'] took 26.924 seconds
2025-11-05 06:42:44,138 - apt.py[DEBUG]: Waiting for APT lock
2025-11-05 06:42:44,139 - apt.py[DEBUG]: APT lock available
2025-11-05 06:42:44,139 - subp.py[DEBUG]: Running command ['eatmydata', 'apt-get', '--option=Dpkg::Options::=--force-confold', '--option=Dpkg::options::=--force-unsafe-io', '--assume-yes', '--quiet', 'dist-upgrade'] with allowed return codes [0] (shell=False, capture=False)
2025-11-05 06:43:23,107 - performance.py[DEBUG]: Running ['eatmydata', 'apt-get', '--option=Dpkg::Options::=--force-confold', '--option=Dpkg::options::=--force-unsafe-io', '--assume-yes', '--quiet', 'dist-upgrade'] took 38.968 seconds
2025-11-05 06:43:23,108 - subp.py[DEBUG]: Running command ['snap', 'get', 'system', '-d'] with allowed return codes [0] (shell=False, capture=True)
2025-11-05 06:43:24,038 - performance.py[DEBUG]: Running ['snap', 'get', 'system', '-d'] took 0.930 seconds
2025-11-05 06:43:24,039 - subp.py[DEBUG]: Running command ['snap', 'refresh'] with allowed return codes [0] (shell=False, capture=True)
2025-11-05 06:43:24,135 - performance.py[DEBUG]: Running ['snap', 'refresh'] took 0.095 seconds
2025-11-05 06:43:24,136 - helpers.py[DEBUG]: update-sources already ran (freq=once-per-instance)
2025-11-05 06:43:24,137 - subp.py[DEBUG]: Running command ['apt-cache', 'pkgnames'] with allowed return codes [0] (shell=False, capture=True)
2025-11-05 06:43:24,613 - performance.py[DEBUG]: Running ['apt-cache', 'pkgnames'] took 0.477 seconds
2025-11-05 06:43:24,661 - apt.py[DEBUG]: Waiting for APT lock
2025-11-05 06:43:24,662 - apt.py[DEBUG]: APT lock available
2025-11-05 06:43:24,662 - subp.py[DEBUG]: Running command ['eatmydata', 'apt-get', '--option=Dpkg::Options::=--force-confold', '--option=Dpkg::options::=--force-unsafe-io', '--assume-yes', '--quiet', 'install', 'sl'] with allowed return codes [0] (shell=False, capture=False)
2025-11-05 06:43:30,507 - performance.py[DEBUG]: Running ['eatmydata', 'apt-get', '--option=Dpkg::Options::=--force-confold', '--option=Dpkg::options::=--force-unsafe-io', '--assume-yes', '--quiet', 'install', 'sl'] took 5.844 seconds
2025-11-05 06:43:30,509 - handlers.py[DEBUG]: finish: modules-final/config-package_update_upgrade_install: SUCCESS: config-package_update_upgrade_install ran successfully and took 73.302 seconds

最後に finish: modules-final が出力されcloud-initのすべての処理が完了します。

2025-10-30 10:34:03,097 - handlers.py[DEBUG]: finish: modules-final: SUCCESS: running modules for final

さいごに

本記事ではProxmox VEを使ってcloud-initの基本的な使い方から内部の仕組み、デバッグ方法までを解説しました。cloud-initは一見複雑に見えますが、各ステージの役割とログの見方を理解すれば、トラブルシューティングもスムーズに行えます。

Proxmox VEの標準機能だけでも十分便利ですが、user-dataやvendor-dataを活用することで、より高度なプロビジョニングの自動化が可能になります。ぜひ本記事を参考に、cloud-initを使った効率的なインフラ構築にチャレンジしてみてください。