Open5

cloud-init

zulinx86zulinx86

cloud-init

  • クラウド環境のサーバー (cloud instance) の初期化処理を容易にするためのツールである。

起動ステージ (Boot Stages)

  • 起動は 5 つのステージに分けられる。
    1. Detect
    2. Local
    3. Network
    4. Config
    5. Final

Detect

  • ds-identify ツールを実行して、cloud instance が実行されている環境 (データソース) を検出する。
  • データソースの一覧はこちらを参照。

Local

  • systemd サービス: cloud-init-local.service

Network

  • systemd サービス: cloud-init.service
  • モジュール: /etc/cloud/cloud.cfgcloud_init_modules

Config

  • systemd サービス: cloud-config.service
  • モジュール: /etc/cloud/cloud.cfgcloud_config_modules

Final

  • systemd サービス: cloud-final.service
  • モジュール: /etc/cloud/cloud.cfgcloud_final_modules

初回起動検出 (First Boot Determination)

  • https://docs.cloud-init.io/en/latest/explanation/boot.html#first-boot-determination
  • cloud-init は、対象の cloud instance が初回起動か (イメージから作成されたか) 、既存のものを再起動したものかで、異なる処理を実行する。
  • 初回起動の場合は "per-instance" の設定を行い、以降の起動では "per-boot" の設定を行う。
  • cloud-init は、内部の状態をキャッシュに保存しており、キャッシュ内のインスタンス ID が現在のものと一致しているかどうかで、初回起動かどうかの判定を行う。この挙動を "check" モードという。
  • 上記の挙動がうまく動作しない場合のために、"trust" モードが存在しており、無条件にキャッシュのインスタンス ID 情報を信用させることもできる。ただし、インスタンス ID を再検出させたい場合はキャッシュディレクトリを手動で削除しなければいけない。
  • "check" モードと "trust" モードのどっちを利用するかは、設定ファイルで manual_cache_clean を設定することでできる。これは、デフォルトで false に設定されていて、"check" モードが選択される。true にした場合に "trust" モードになる。

キャッシュディレクトリのレイアウト (Cache Directory Layout)

  • ドキュメント: https://github.com/canonical/cloud-init/blob/main/doc/var-lib-cloud.txt
  • /var/lib/cloud/
    • data/: インスタンス ID、ホスト名、データソース等の情報
      • instance-id: cloud instance の識別子
      • result.json: データソースとエラーが発生したかどうか
      • status.json: データソース、各ステージの情報 (エラーが発生したか、開始時間、終了時間)
    • handlers/
    • instance/: instances/<instance-id>/ へのシンボリックリンク
    • instances/
      • <instance-id>/
    • scripts/: cloud-init 経由で実行したいスクリプト
      • per-boot/: 起動ごと
      • per-instance/: インスタンスごと
      • per-once/: 1 度だけ
      • vendor/: ベンダーごと (?)
    • seed/: 特定のデータソースに対して、ユーザーデータなどを注入できる (基本的に気にしないで良い)
    • sem/: 1回限りのスクリプトが実行されたかどうかを示すセマフォ (基本的に気にしないで良い)
  • cloud-init clean コマンドでクリーンアップできる。

設定ファイル (Configuration Files)

ログ (Logs)

ユーザーデータのフォーマット

cloud-config

ユーザーデータスクリプト

  • シェルスクリプトを実行するのに利用できるため、cloud-init で用意されていない任意の処理が実行できる。
  • #! (MIME アーカイブの場合は Content-Type: text/x-shellscript) から始まる必要がある。

MIME マルチパートアーカイブ

Content-Type: multipart/mixed; boundary="boundary_str"

This is the preamble. It is to be ignored.
--boundary_str
Content-Type: text/cloud-config
...

--boundary_str
Content-Type: text/x-shellscript
...

--boundary_str--
This is the epilogue. It is also to be ignored.

参考リンク (References)

zulinx86zulinx86

Hands-On

Amazon Linux 2023 AMI を使って、色々確認していく。

ちゃんとインストールされてる。

$ rpm -qa | grep cloud-init
cloud-init-cfg-ec2-22.2.2-1.amzn2023.1.12.noarch
cloud-init-22.2.2-1.amzn2023.1.12.noarch

RPM パッケージには、依存関係を含めると、かなり色んなファイルが入ってるが、重要そうなものだけここでは抜粋。cloud-init, cloud-id, cloud-init-per などの実行ファイルの他に、systemd 用のファイルや設定ファイルが入っている。

$ rpm -ql cloud-init
# snipped
/etc/cloud/cloud.cfg
/etc/cloud/cloud.cfg.d
/etc/cloud/cloud.cfg.d/05_logging.cfg
/etc/cloud/cloud.cfg.d/40_selinux-reboot.cfg
/etc/cloud/cloud.cfg.d/README
# snipped
/usr/bin/cloud-id
/usr/bin/cloud-init
/usr/bin/cloud-init-per
# snipped
/usr/lib/systemd/system-generators/cloud-init-generator
/usr/lib/systemd/system/cloud-config.service
/usr/lib/systemd/system/cloud-config.target
/usr/lib/systemd/system/cloud-final.service
/usr/lib/systemd/system/cloud-init-hotplugd.service
/usr/lib/systemd/system/cloud-init-hotplugd.socket
/usr/lib/systemd/system/cloud-init-local.service
/usr/lib/systemd/system/cloud-init.service
/usr/lib/systemd/system/cloud-init.target
# snipped
/usr/libexec/cloud-init
/usr/libexec/cloud-init/ds-identify
/usr/libexec/cloud-init/hook-hotplug
/usr/libexec/cloud-init/uncloud-init
/usr/libexec/cloud-init/write-ssh-key-fingerprints
# snipped
/var/lib/cloud

cloud-init 本体には、パスが通ってる。

$ which cloud-init
/usr/bin/cloud-init

systemd における依存関係を確認する。cloud-init.target が最終的なターゲットになる模様。

$ sudo systemctl list-dependencies
default.target
# snipped
● └─multi-user.target
# snipped
●   ├─cloud-init.target
●   │ ├─cloud-config.service
●   │ ├─cloud-final.service
●   │ ├─cloud-init-local.service
●   │ └─cloud-init.service
# snipped

systemd における順序関係を確認する。

$ sudo systemctl list-dependencies --after cloud-init.target
cloud-init.target
● ├─cloud-config.service
● ├─cloud-final.service
● ├─cloud-init-local.service
● └─multi-user.target
# snipped

$ sudo systemctl list-dependencies --after cloud-final.service
cloud-final.service
● ├─cloud-config.service
# snipped
● ├─network-online.target
● │ ├─cloud-init.service
● │ ├─systemd-networkd-wait-online.service
● │ └─network.target
● │   ├─systemd-networkd.service
● │   ├─systemd-resolved.service
● │   └─network-pre.target
● │     ├─cloud-init-local.service
● │     └─systemd-network-generator.service
# snipped

$ sudo systemctl list-dependencies --after cloud-config.service
cloud-config.service
# snipped
● ├─cloud-config.target
● │ ├─cloud-init-local.service
● │ └─cloud-init.service
● ├─network-online.target
● │ ├─cloud-init.service
● │ ├─systemd-networkd-wait-online.service
● │ └─network.target
● │   ├─systemd-networkd.service
● │   ├─systemd-resolved.service
● │   └─network-pre.target
● │     ├─cloud-init-local.service
● │     └─systemd-network-generator.service
# snipped

$ sudo systemctl list-dependencies --after cloud-config.target
cloud-config.target
● ├─cloud-init-local.service
● └─cloud-init.service

$ sudo systemctl list-dependencies --after cloud-init.service
cloud-init.service
● ├─cloud-init-local.service
● ├─system.slice
● ├─systemd-journald.socket
● └─systemd-networkd-wait-online.service

まとめると、順序的には、以下の通り。
cloud-init-local.service は、ネットワークの設定を始める前に実行し、cloud-final.service は逆にネットワークがオンラインになってから実行する。

cloud-init-local.service
          |
          V
  network-pre.target
          |
          V
  network.service
          |
          V
 cloud-init.service
          |
          V
 cloud-config.target  network-online.target
          |                     |
          +---------------------+
          V
 cloud-config.service
          |
          V
  cloud-final.service
          |
          V
   cloud-init.target

中で何が実行されているか見る。

Amazon Linux 2023 では、EC2 上だとファイルの削除だけで、cloud-init init --local は実行しないらしい。

$ sudo systemctl cat cloud-init-local | grep -B 2 ExecStart
[Service]
Type=oneshot
ExecStart=/usr/bin/cloud-init init --local
--
# Skip cloud-init-local if we're running in Amazon EC2
[Service]
ExecStart=
ExecStart=/bin/rm -vf /var/lib/cloud/instance/boot-finished /var/lib/cloud/data/no-net /var/lib/cloud/instance
$ sudo systemctl cat cloud-init.service | grep ExecStart
ExecStart=/usr/bin/cloud-init init
$ sudo systemctl cat cloud-config.service | grep ExecStart
ExecStart=/usr/bin/cloud-init modules --mode=config
$ sudo systemctl cat cloud-final.service | grep ExecStart
ExecStart=/usr/bin/cloud-init modules --mode=final

ヘルプメッセージを見てみる。

$ cloud-init --help
usage: /usr/bin/cloud-init [-h] [--version] [--file FILES] [--debug] [--force]
                           {init,modules,single,query,dhclient-hook,features,analyze,devel,collect-logs,clean,status,schema} ...

optional arguments:
  -h, --help            show this help message and exit
  --version, -v         Show program's version number and exit.
  --file FILES, -f FILES
                        Use additional yaml configuration files.
  --debug, -d           Show additional pre-action logging (default: False).
  --force               Force running even if no datasource is found (use at your own risk).

Subcommands:
  {init,modules,single,query,dhclient-hook,features,analyze,devel,collect-logs,clean,status,schema}
    init                Initialize cloud-init and perform initial modules.
    modules             Activate modules using a given configuration key.
    single              Run a single module.
    query               Query standardized instance metadata from the command line.
    dhclient-hook       Run the dhclient hook to record network info.
    features            List defined features.
    analyze             Devel tool: Analyze cloud-init logs and data.
    devel               Run development tools.
    collect-logs        Collect and tar all cloud-init debug info.
    clean               Remove logs and artifacts so cloud-init can re-run.
    status              Report cloud-init status or wait on completion.
    schema              Validate cloud-config files using jsonschema.
$ cloud-init init --help
usage: /usr/bin/cloud-init init [-h] [--local]

optional arguments:
  -h, --help   show this help message and exit
  --local, -l  Start in local mode (default: False).
$ cloud-init modules --help
usage: /usr/bin/cloud-init modules [-h] [--mode {init,config,final}]

optional arguments:
  -h, --help            show this help message and exit
  --mode {init,config,final}, -m {init,config,final}
                        Module configuration name to use (default: config).

journal ログを見てみる。

$ journalctl -u cloud-init-local.service
Feb 09 09:57:45 localhost systemd[1]: Starting cloud-init-local.service - Initial cloud-init job (pre-networking)...
Feb 09 09:57:45 localhost systemd[1]: Finished cloud-init-local.service - Initial cloud-init job (pre-networking).
$ journalctl -u cloud-init-local.service
Feb 09 09:57:45 localhost systemd[1]: Starting cloud-init-local.service - Initial cloud-init job (pre-networking)...
Feb 09 09:57:45 localhost systemd[1]: Finished cloud-init-local.service - Initial cloud-init job (pre-networking).
[ec2-user@ip-172-31-73-4 ~]$ journalctl -u cloud-init.service
Feb 09 09:57:46 localhost systemd[1]: Starting cloud-init.service - Initial cloud-init job (metadata service crawler)...
Feb 09 09:57:47 localhost cloud-init[9594]: Cloud-init v. 22.2.2 running 'init' at Fri, 09 Feb 2024 09:57:47 +0000. Up 8.61 seconds.
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +++++++++++++++++++++++++++++++++++++++Net device info+++++++++++++++++++++++++++++++++++++++
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +--------+------+------------------------------+---------------+--------+-------------------+
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: | Device |  Up  |           Address            |      Mask     | Scope  |     Hw-Address    |
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +--------+------+------------------------------+---------------+--------+-------------------+
# snipped
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +--------+------+------------------------------+---------------+--------+-------------------+
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: ++++++++++++++++++++++++++++++Route IPv4 info++++++++++++++++++++++++++++++
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +-------+-------------+-------------+-----------------+-----------+-------+
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: | Route | Destination |   Gateway   |     Genmask     | Interface | Flags |
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +-------+-------------+-------------+-----------------+-----------+-------+
# snipped
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +-------+-------------+-------------+-----------------+-----------+-------+
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +++++++++++++++++++Route IPv6 info+++++++++++++++++++
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +-------+-------------+---------+-----------+-------+
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: | Route | Destination | Gateway | Interface | Flags |
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +-------+-------------+---------+-----------+-------+
# snipped
Feb 09 09:57:47 localhost cloud-init[9594]: ci-info: +-------+-------------+---------+-----------+-------+
Feb 09 09:57:48 ip-172-31-73-4.ec2.internal useradd[9672]: new group: name=ec2-user, GID=1000
Feb 09 09:57:48 ip-172-31-73-4.ec2.internal useradd[9672]: new user: name=ec2-user, UID=1000, GID=1000, home=/home/ec2-user, shell=/bin/bash, from=none
Feb 09 09:57:48 ip-172-31-73-4.ec2.internal useradd[9672]: add 'ec2-user' to group 'adm'
Feb 09 09:57:48 ip-172-31-73-4.ec2.internal useradd[9672]: add 'ec2-user' to group 'wheel'
Feb 09 09:57:48 ip-172-31-73-4.ec2.internal useradd[9672]: add 'ec2-user' to group 'systemd-journal'
Feb 09 09:57:48 ip-172-31-73-4.ec2.internal useradd[9672]: add 'ec2-user' to shadow group 'adm'
Feb 09 09:57:48 ip-172-31-73-4.ec2.internal useradd[9672]: add 'ec2-user' to shadow group 'wheel'
Feb 09 09:57:48 ip-172-31-73-4.ec2.internal useradd[9672]: add 'ec2-user' to shadow group 'systemd-journal'
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: Generating public/private ed25519 key pair.
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: Your identification has been saved in /etc/ssh/ssh_host_ed25519_key
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: Your public key has been saved in /etc/ssh/ssh_host_ed25519_key.pub
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: The key fingerprint is:
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: SHA256:isDkkiBPlB1KF2bxgJ3eA59MryM3yQNG61fvtsOPYDA root@ip-172-31-73-4.ec2.internal
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: The key's randomart image is:
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: +--[ED25519 256]--+
# snipped
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: +----[SHA256]-----+
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: Generating public/private ecdsa key pair.
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: Your identification has been saved in /etc/ssh/ssh_host_ecdsa_key
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: Your public key has been saved in /etc/ssh/ssh_host_ecdsa_key.pub
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: The key fingerprint is:
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: SHA256:eXNPlizwki4+6nShA7H8LGFnpfeV6IsMAP+yCP0MKwQ root@ip-172-31-73-4.ec2.internal
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: The key's randomart image is:
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: +---[ECDSA 256]---+
# snipped
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9594]: +----[SHA256]-----+
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal systemd[1]: Finished cloud-init.service - Initial cloud-init job (metadata service crawler).
$ journalctl -u cloud-config.service
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal systemd[1]: Starting cloud-config.service - Apply the settings specified in cloud-config...
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9728]: Cloud-init v. 22.2.2 running 'modules:config' at Fri, 09 Feb 2024 09:57:49 +0000. Up 10.54 seconds.
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal systemd[1]: Finished cloud-config.service - Apply the settings specified in cloud-config.
$ journalctl -u cloud-final.service
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal systemd[1]: Starting cloud-final.service - Execute cloud user/final scripts...
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9747]: Cloud-init v. 22.2.2 running 'modules:final' at Fri, 09 Feb 2024 09:57:49 +0000. Up 10.78 seconds.
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9750]: -----BEGIN SSH HOST KEY FINGERPRINTS-----
# snipped
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9755]: -----END SSH HOST KEY FINGERPRINTS-----
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9756]: #############################################################
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal cloud-init[9747]: Cloud-init v. 22.2.2 finished at Fri, 09 Feb 2024 09:57:49 +0000. Datasource DataSourceEc2.  Up 10.90 seconds
Feb 09 09:57:49 ip-172-31-73-4.ec2.internal systemd[1]: Finished cloud-final.service - Execute cloud user/final scripts.

/var/lib/cloud の階層構造を見ておく。

$ tree /var/lib/cloud
/var/lib/cloud
├── data
│   ├── instance-id
│   ├── previous-datasource
│   ├── previous-hostname
│   ├── previous-instance-id
│   ├── python-version
│   ├── result.json
│   ├── set-hostname
│   └── status.json
├── handlers
├── instance -> /var/lib/cloud/instances/i-XXXXXXXX
├── instances
│   └── i-XXXXXXXX
│       ├── boot-finished
│       ├── cloud-config.txt
│       ├── datasource
│       ├── handlers
│       ├── obj.pkl
│       ├── scripts
│       ├── sem
│       │   ├── config_ca_certs
│       │   ├── config_disk_setup
│       │   ├── config_install_hotplug
│       │   ├── config_keyboard
│       │   ├── config_keys_to_console
│       │   ├── config_locale
│       │   ├── config_mcollective
│       │   ├── config_mounts
│       │   ├── config_ntp
│       │   ├── config_package_update_upgrade_install
│       │   ├── config_phone_home
│       │   ├── config_power_state_change
│       │   ├── config_puppet
│       │   ├── config_reset_rmc
│       │   ├── config_rightscale_userdata
│       │   ├── config_rsyslog
│       │   ├── config_runcmd
│       │   ├── config_salt_minion
│       │   ├── config_scripts_per_instance
│       │   ├── config_scripts_user
│       │   ├── config_scripts_vendor
│       │   ├── config_seed_random
│       │   ├── config_selinux
│       │   ├── config_set_passwords
│       │   ├── config_ssh
│       │   ├── config_ssh_authkey_fingerprints
│       │   ├── config_timezone
│       │   ├── config_users_groups
│       │   ├── config_write_files
│       │   ├── config_write_files_deferred
│       │   ├── config_write_metadata
│       │   ├── config_yum_add_repo
│       │   ├── config_yum_variables
│       │   └── consume_data
│       ├── user-data.txt
│       ├── user-data.txt.i
│       ├── vendor-data.txt
│       ├── vendor-data.txt.i
│       ├── vendor-data2.txt
│       └── vendor-data2.txt.i
├── scripts
│   ├── per-boot
│   ├── per-instance
│   ├── per-once
│   └── vendor
├── seed
└── sem
    └── config_scripts_per_once.once
$ cat /var/lib/cloud/instance/datasource
DataSourceEc2: DataSourceEc2

デフォルトの /etc/cloud/cloud.cfg を見ておく。

$ cat /etc/cloud/cloud.cfg
# The top level settings are used as module
# and system configuration.
# A set of users which may be applied and/or used by various modules
# when a 'default' entry is found it will reference the 'default_user'
# from the distro configuration specified below
users:
   - default


# If this is set, 'root' will not be able to ssh in and they
# will get a message to login instead as the default $user
disable_root: true

mount_default_fields: [~, ~, 'auto', 'defaults,nofail', '0', '2']
resize_rootfs: noblock
resize_rootfs_tmp: /dev
ssh_pwauth:   false

# This will cause the set+update hostname module to not operate (if true)
preserve_hostname: false

# If you use datasource_list array, keep array items in a single line.
# If you use multi line array, ds-identify script won't read array items.
# Example datasource config
# datasource:
#    Ec2:
#      metadata_urls: [ 'blah.com' ]
#      timeout: 5 # (defaults to 50 seconds)
#      max_wait: 10 # (defaults to 120 seconds)



# The modules that run in the 'init' stage
cloud_init_modules:
 - migrator
 - seed_random
 - bootcmd
 - write-files
 - write-metadata
 - growpart
 - resizefs
 - disk_setup
 - mounts
 - set_hostname
 - update_hostname
 - update_etc_hosts
 - ca-certs
 - rsyslog
 - selinux
 - users-groups
 - ssh

# The modules that run in the 'config' stage
cloud_config_modules:
 - ssh-import-id
 - keyboard
 - locale
 - set-passwords
 - yum-variables
 - yum-add-repo
 - ntp
 - timezone
 - disable-ec2-metadata
 - runcmd

# The modules that run in the 'final' stage
cloud_final_modules:
 - package-update-upgrade-install
 - write-files-deferred
 - puppet
 - chef
 - mcollective
 - salt-minion
 - reset_rmc
 - refresh_rmc_and_interface
 - rightscale_userdata
 - 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

# System and/or distro specific settings
# (not accessible to handlers/transforms)
system_info:
   # This will affect which distro class gets used
   distro: amazon
   # Default user name + that default users groups (if added/used)
   default_user:
     name: ec2-user
     lock_passwd: True
     gecos: EC2 Default User
     groups: [wheel, adm, systemd-journal]
     sudo: ["ALL=(ALL) NOPASSWD:ALL"]
     shell: /bin/bash
   # Other config here will be given to the distro class and/or path classes
   paths:
      cloud_dir: /var/lib/cloud/
      templates_dir: /etc/cloud/templates/
   ssh_svcname: sshd
zulinx86zulinx86

Hands-On (Cont.)

ブートレコードを確認しておく。

$ cloud-init analyze show
-- Boot Record 01 --
The total time elapsed since completing an event is printed after the "@" character.
The time the event takes is printed after the "+" character.

Starting stage: init-network
|`->no cache found @00.04600s +00.00000s
|`->found network data from DataSourceEc2 @00.06900s +00.14000s
|`->setting up datasource @00.24400s +00.00000s
|`->reading and applying user-data @00.39400s +00.00200s
|`->reading and applying vendor-data @00.39600s +00.00000s
|`->reading and applying vendor-data2 @00.39600s +00.00000s
|`->activating datasource @00.56600s +00.00200s
|`->config-migrator ran successfully @00.61700s +00.00000s
|`->config-seed_random ran successfully @00.61700s +00.00100s
|`->config-bootcmd ran successfully @00.61800s +00.00000s
|`->config-write-files ran successfully @00.61800s +00.00100s
|`->config-write-metadata ran successfully @00.62000s +00.00300s
|`->config-growpart ran successfully @00.62300s +00.52100s
|`->config-resizefs ran successfully @01.14400s +00.00200s
|`->config-disk_setup ran successfully @01.14600s +00.00200s
|`->config-mounts ran successfully @01.14800s +00.00200s
|`->config-set_hostname ran successfully @01.15000s +00.00000s
|`->config-update_hostname ran successfully @01.15000s +00.00300s
|`->config-update_etc_hosts ran successfully @01.15300s +00.00000s
|`->config-ca-certs ran successfully @01.15400s +00.00100s
|`->config-rsyslog ran successfully @01.15500s +00.00100s
|`->config-selinux ran successfully @01.15600s +00.01800s
|`->config-users-groups ran successfully @01.17500s +00.40000s
|`->config-ssh ran successfully @01.57500s +00.05600s
Finished stage: (init-network) 01.63100 seconds

Starting stage: modules-config
|`->config-keyboard ran successfully @01.93300s +00.00300s
|`->config-locale ran successfully @01.93600s +00.00400s
|`->config-set-passwords ran successfully @01.94000s +00.01300s
|`->config-yum-variables ran successfully @01.95300s +00.00200s
|`->config-yum-add-repo ran successfully @01.95500s +00.00100s
|`->config-ntp ran successfully @01.95600s +00.00100s
|`->config-timezone ran successfully @01.95800s +00.00100s
|`->config-disable-ec2-metadata ran successfully @01.95900s +00.00000s
|`->config-runcmd ran successfully @01.95900s +00.00100s
Finished stage: (modules-config) 00.04500 seconds

Starting stage: modules-final
|`->config-package-update-upgrade-install ran successfully @02.19600s +00.00300s
|`->config-write-files-deferred ran successfully @02.19900s +00.00100s
|`->config-puppet ran successfully @02.20000s +00.00100s
|`->config-chef ran successfully @02.20100s +00.00000s
|`->config-mcollective ran successfully @02.20100s +00.00100s
|`->config-salt-minion ran successfully @02.20300s +00.00100s
|`->config-reset_rmc ran successfully @02.20400s +00.00100s
|`->config-refresh_rmc_and_interface ran successfully @02.20500s +00.00000s
|`->config-rightscale_userdata ran successfully @02.20500s +00.00100s
|`->config-scripts-vendor ran successfully @02.20600s +00.00100s
|`->config-scripts-per-once ran successfully @02.20700s +00.00100s
|`->config-scripts-per-boot ran successfully @02.20900s +00.00000s
|`->config-scripts-per-instance ran successfully @02.20900s +00.00100s
|`->config-scripts-user ran successfully @02.21000s +00.00100s
|`->config-ssh-authkey-fingerprints ran successfully @02.21100s +00.01100s
|`->config-keys-to-console ran successfully @02.22200s +00.01800s
|`->config-install-hotplug ran successfully @02.24100s +00.00100s
|`->config-phone-home ran successfully @02.24200s +00.00100s
|`->config-final-message ran successfully @02.24400s +00.00400s
|`->config-power-state-change ran successfully @02.24800s +00.00200s
Finished stage: (modules-final) 00.09200 seconds

Total Time: 1.76800 seconds

1 boot records analyzed
$ grep 'Running module' /var/log/cloud-init.log
2024-02-09 09:57:48,094 - modules.py[DEBUG]: Running module migrator (<module 'cloudinit.config.cc_migrator' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_migrator.py'>) with frequency always
2024-02-09 09:57:48,095 - modules.py[DEBUG]: Running module seed_random (<module 'cloudinit.config.cc_seed_random' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_seed_random.py'>) with frequency once-per-instance
2024-02-09 09:57:48,096 - modules.py[DEBUG]: Running module bootcmd (<module 'cloudinit.config.cc_bootcmd' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_bootcmd.py'>) with frequency always
2024-02-09 09:57:48,096 - modules.py[DEBUG]: Running module write-files (<module 'cloudinit.config.cc_write_files' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_write_files.py'>) with frequency once-per-instance
2024-02-09 09:57:48,097 - modules.py[DEBUG]: Running module write-metadata (<module 'cloudinit.config.cc_write_metadata' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_write_metadata.py'>) with frequency once-per-instance
2024-02-09 09:57:48,101 - modules.py[DEBUG]: Running module growpart (<module 'cloudinit.config.cc_growpart' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_growpart.py'>) with frequency always
2024-02-09 09:57:48,622 - modules.py[DEBUG]: Running module resizefs (<module 'cloudinit.config.cc_resizefs' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_resizefs.py'>) with frequency always
2024-02-09 09:57:48,624 - modules.py[DEBUG]: Running module disk_setup (<module 'cloudinit.config.cc_disk_setup' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_disk_setup.py'>) with frequency once-per-instance
2024-02-09 09:57:48,626 - modules.py[DEBUG]: Running module mounts (<module 'cloudinit.config.cc_mounts' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_mounts.py'>) with frequency once-per-instance
2024-02-09 09:57:48,628 - modules.py[DEBUG]: Running module set_hostname (<module 'cloudinit.config.cc_set_hostname' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_set_hostname.py'>) with frequency always
2024-02-09 09:57:48,628 - modules.py[DEBUG]: Running module update_hostname (<module 'cloudinit.config.cc_update_hostname' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_update_hostname.py'>) with frequency always
2024-02-09 09:57:48,631 - modules.py[DEBUG]: Running module update_etc_hosts (<module 'cloudinit.config.cc_update_etc_hosts' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_update_etc_hosts.py'>) with frequency always
2024-02-09 09:57:48,631 - modules.py[DEBUG]: Running module ca-certs (<module 'cloudinit.config.cc_ca_certs' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_ca_certs.py'>) with frequency once-per-instance
2024-02-09 09:57:48,633 - modules.py[DEBUG]: Running module rsyslog (<module 'cloudinit.config.cc_rsyslog' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_rsyslog.py'>) with frequency once-per-instance
2024-02-09 09:57:48,634 - modules.py[DEBUG]: Running module selinux (<module 'cloudinit.config.cc_selinux' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_selinux.py'>) with frequency once-per-instance
2024-02-09 09:57:48,652 - modules.py[DEBUG]: Running module users-groups (<module 'cloudinit.config.cc_users_groups' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_users_groups.py'>) with frequency once-per-instance
2024-02-09 09:57:49,053 - modules.py[DEBUG]: Running module ssh (<module 'cloudinit.config.cc_ssh' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_ssh.py'>) with frequency once-per-instance
2024-02-09 09:57:49,411 - modules.py[DEBUG]: Running module keyboard (<module 'cloudinit.config.cc_keyboard' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_keyboard.py'>) with frequency once-per-instance
2024-02-09 09:57:49,414 - modules.py[DEBUG]: Running module locale (<module 'cloudinit.config.cc_locale' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_locale.py'>) with frequency once-per-instance
2024-02-09 09:57:49,418 - modules.py[DEBUG]: Running module set-passwords (<module 'cloudinit.config.cc_set_passwords' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_set_passwords.py'>) with frequency once-per-instance
2024-02-09 09:57:49,431 - modules.py[DEBUG]: Running module yum-variables (<module 'cloudinit.config.cc_yum_variables' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_yum_variables.py'>) with frequency once-per-instance
2024-02-09 09:57:49,433 - modules.py[DEBUG]: Running module yum-add-repo (<module 'cloudinit.config.cc_yum_add_repo' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_yum_add_repo.py'>) with frequency once-per-instance
2024-02-09 09:57:49,434 - modules.py[DEBUG]: Running module ntp (<module 'cloudinit.config.cc_ntp' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_ntp.py'>) with frequency once-per-instance
2024-02-09 09:57:49,435 - modules.py[DEBUG]: Running module timezone (<module 'cloudinit.config.cc_timezone' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_timezone.py'>) with frequency once-per-instance
2024-02-09 09:57:49,437 - modules.py[DEBUG]: Running module disable-ec2-metadata (<module 'cloudinit.config.cc_disable_ec2_metadata' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_disable_ec2_metadata.py'>) with frequency always
2024-02-09 09:57:49,437 - modules.py[DEBUG]: Running module runcmd (<module 'cloudinit.config.cc_runcmd' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_runcmd.py'>) with frequency once-per-instance
2024-02-09 09:57:49,674 - modules.py[DEBUG]: Running module package-update-upgrade-install (<module 'cloudinit.config.cc_package_update_upgrade_install' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_package_update_upgrade_install.py'>) with frequency once-per-instance
2024-02-09 09:57:49,677 - modules.py[DEBUG]: Running module write-files-deferred (<module 'cloudinit.config.cc_write_files_deferred' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_write_files_deferred.py'>) with frequency once-per-instance
2024-02-09 09:57:49,678 - modules.py[DEBUG]: Running module puppet (<module 'cloudinit.config.cc_puppet' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_puppet.py'>) with frequency once-per-instance
2024-02-09 09:57:49,679 - modules.py[DEBUG]: Running module chef (<module 'cloudinit.config.cc_chef' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_chef.py'>) with frequency always
2024-02-09 09:57:49,679 - modules.py[DEBUG]: Running module mcollective (<module 'cloudinit.config.cc_mcollective' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_mcollective.py'>) with frequency once-per-instance
2024-02-09 09:57:49,680 - modules.py[DEBUG]: Running module salt-minion (<module 'cloudinit.config.cc_salt_minion' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_salt_minion.py'>) with frequency once-per-instance
2024-02-09 09:57:49,682 - modules.py[DEBUG]: Running module reset_rmc (<module 'cloudinit.config.cc_reset_rmc' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_reset_rmc.py'>) with frequency once-per-instance
2024-02-09 09:57:49,683 - modules.py[DEBUG]: Running module refresh_rmc_and_interface (<module 'cloudinit.config.cc_refresh_rmc_and_interface' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_refresh_rmc_and_interface.py'>) with frequency always
2024-02-09 09:57:49,683 - modules.py[DEBUG]: Running module rightscale_userdata (<module 'cloudinit.config.cc_rightscale_userdata' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_rightscale_userdata.py'>) with frequency once-per-instance
2024-02-09 09:57:49,684 - modules.py[DEBUG]: Running module scripts-vendor (<module 'cloudinit.config.cc_scripts_vendor' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_scripts_vendor.py'>) with frequency once-per-instance
2024-02-09 09:57:49,685 - modules.py[DEBUG]: Running module scripts-per-once (<module 'cloudinit.config.cc_scripts_per_once' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_scripts_per_once.py'>) with frequency once
2024-02-09 09:57:49,686 - modules.py[DEBUG]: Running module scripts-per-boot (<module 'cloudinit.config.cc_scripts_per_boot' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_scripts_per_boot.py'>) with frequency always
2024-02-09 09:57:49,687 - modules.py[DEBUG]: Running module scripts-per-instance (<module 'cloudinit.config.cc_scripts_per_instance' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_scripts_per_instance.py'>) with frequency once-per-instance
2024-02-09 09:57:49,688 - modules.py[DEBUG]: Running module scripts-user (<module 'cloudinit.config.cc_scripts_user' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_scripts_user.py'>) with frequency once-per-instance
2024-02-09 09:57:49,689 - modules.py[DEBUG]: Running module ssh-authkey-fingerprints (<module 'cloudinit.config.cc_ssh_authkey_fingerprints' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_ssh_authkey_fingerprints.py'>) with frequency once-per-instance
2024-02-09 09:57:49,700 - modules.py[DEBUG]: Running module keys-to-console (<module 'cloudinit.config.cc_keys_to_console' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_keys_to_console.py'>) with frequency once-per-instance
2024-02-09 09:57:49,719 - modules.py[DEBUG]: Running module install-hotplug (<module 'cloudinit.config.cc_install_hotplug' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_install_hotplug.py'>) with frequency once-per-instance
2024-02-09 09:57:49,720 - modules.py[DEBUG]: Running module phone-home (<module 'cloudinit.config.cc_phone_home' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_phone_home.py'>) with frequency once-per-instance
2024-02-09 09:57:49,721 - modules.py[DEBUG]: Running module final-message (<module 'cloudinit.config.cc_final_message' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_final_message.py'>) with frequency always
2024-02-09 09:57:49,726 - modules.py[DEBUG]: Running module power-state-change (<module 'cloudinit.config.cc_power_state_change' from '/usr/lib/python3.9/site-packages/cloudinit/config/cc_power_state_change.py'>) with frequency once-per-instance
zulinx86zulinx86

Source Code Reading

setup.py

cloud-init のエントリポイントは cloudinit.cmd.main:main.

setuptools.setup(
# snipped
    entry_points={
        "console_scripts": [
            "cloud-init = cloudinit.cmd.main:main",
# snipped
        ],
    },
)

cloudinit/cmd/main.py

複数のサブコマンドが用意されており、各種 main 関数に分岐していく。

  • init: main_init()
  • modules: main_modules()
  • single: main_single()
  • features: main_features()
  • etc...
def main(sysv_args=None):
# snipped
    parser = argparse.ArgumentParser(prog=sysv_args.pop(0))
# snipped
    subparsers = parser.add_subparsers(title="Subcommands", dest="subcommand")
# snipped
    parser_init = subparsers.add_parser(
        "init", help="Initialize cloud-init and perform initial modules."
    )
# snipped
    parser_init.set_defaults(action=("init", main_init))
# snipped
    parser_mod = subparsers.add_parser(
        "modules", help="Activate modules using a given configuration key."
    )
# snipped
    parser_mod.set_defaults(action=("modules", main_modules))
# snipped
    parser_single = subparsers.add_parser(
        "single", help="Run a single module."
    )
# snipped
    parser_single.set_defaults(action=("single", main_single))
# snipped
    parser_query = subparsers.add_parser(
        "query",
        help="Query standardized instance metadata from the command line.",
    )

    parser_features = subparsers.add_parser(
        "features", help="List defined features."
    )
    parser_features.set_defaults(action=("features", main_features))

    parser_analyze = subparsers.add_parser(
        "analyze", help="Devel tool: Analyze cloud-init logs and data."
    )

    parser_devel = subparsers.add_parser(
        "devel", help="Run development tools."
    )

    parser_collect_logs = subparsers.add_parser(
        "collect-logs", help="Collect and tar all cloud-init debug info."
    )

    parser_clean = subparsers.add_parser(
        "clean", help="Remove logs and artifacts so cloud-init can re-run."
    )

    parser_status = subparsers.add_parser(
        "status", help="Report cloud-init status or wait on completion."
    )

    parser_schema = subparsers.add_parser(
        "schema", help="Validate cloud-config files using jsonschema."
    )
# snipped
    args = parser.parse_args(args=sysv_args)

    # Subparsers.required = True and each subparser sets action=(name, functor)
    (name, functor) = args.action
# snipped
    with args.reporter:
        retval = util.log_time(
            logfunc=LOG.debug,
            msg="cloud-init mode '%s'" % name,
            get_uptime=True,
            func=functor,
            args=(name, args),
        )
zulinx86zulinx86

Source Code Reading (Cont.)

cloud-init.service

cloud-init init のコードパスを辿る。

コード中に、どんな処理をしてるのか書いてくれてる (ありがたい...) 。

def main_init(name, args):
# snipped
    # Cloud-init 'init' stage is broken up into the following sub-stages
    # 1. Ensure that the init object fetches its config without errors
    # 2. Setup logging/output redirections with resultant config (if any)
    # 3. Initialize the cloud-init filesystem
    # 4. Check if we can stop early by looking for various files
    # 5. Fetch the datasource
    # 6. Connect to the current instance location + update the cache
    # 7. Consume the userdata (handlers get activated here)
    # 8. Construct the modules object
    # 9. Adjust any subsequent logging/output redirections using the modules
    #    objects config as it may be different from init object
    # 10. Run the modules for the 'init' stage
# snipped
    init = stages.Init(ds_deps=deps, reporter=args.reporter)

Init クラスの実体が作成されている。
https://github.com/canonical/cloud-init/blob/main/cloudinit/stages.py

class Init:
# snipped
        self._cfg: Dict = {}

Stage 1

設定ファイル (/etc/cloud/cloud.cfg/etc/cloud/cloud.cfg.d/* など) を読み込む。

    # Stage 1
    init.read_cfg(extract_fns(args))

https://github.com/canonical/cloud-init/blob/main/cloudinit/stages.py

    def read_cfg(self, extra_fns=None):
        if not self._cfg:
            self._cfg = self._read_cfg(extra_fns)

    def _read_cfg(self, extra_fns):
# snipped
        merger = helpers.ConfigMerger(
# snipped
            base_cfg=fetch_base_config(instance_data_file=instance_data_file),
        )
        return merger.cfg

https://github.com/canonical/cloud-init/blob/main/cloudinit/stages.py

def fetch_base_config(*, instance_data_file=None) -> dict:
    return util.mergemanydict(
        [
            # builtin config, hardcoded in settings.py.
            util.get_builtin_cfg(),
            # Anything in your conf.d or 'default' cloud.cfg location.
            util.read_conf_with_confd(
                CLOUD_CONFIG, instance_data_file=instance_data_file
            ),
            # runtime config. I.e., /run/cloud-init/cloud.cfg
            read_runtime_config(),
            # Kernel/cmdline parameters override system config
            util.read_conf_from_cmdline(),
        ],
        reverse=True,
    )

https://github.com/canonical/cloud-init/blob/main/cloudinit/settings.py

# This is expected to be a yaml formatted file
CLOUD_CONFIG = "/etc/cloud/cloud.cfg"

Stage 4

--local が指定されない場合は、DSMODE_NETWORK (ネットワークデータソースモード) になり、初回起動確認の "trust" モードが使用される。

    # Stage 4
    path_helper = init.paths
    purge_cache_on_python_version_change(init)
    mode = sources.DSMODE_LOCAL if args.local else sources.DSMODE_NETWORK

    if mode == sources.DSMODE_NETWORK:
        existing = "trust"
        sys.stderr.write("%s\n" % (netinfo.debug_info()))
    else:
# snipped

Stage 5

データソースを取得する。

    # Stage 5
# snipped
    try:
        init.fetch(existing=existing)
# snipped
    except sources.DataSourceNotFoundException:
# snipped

Stage 6

/var/lib/cloud/instance/ 周りを更新してから、ネットワークの構成を適用して、ユーザーデータを保存する。

    # Stage 6
    iid = init.instancify()
# snipped
    init.apply_network_config(bring_up=bring_up_interfaces)
# snipped
    # update fully realizes user-data (pulling in #include if necessary)
    init.update()
    _maybe_set_hostname(init, stage="init-net", retry_stage="modules:config")

https://github.com/canonical/cloud-init/blob/main/cloudinit/stages.py

    def update(self):
        self._store_rawdata(self.datasource.get_userdata_raw(), "userdata")
        self._store_processeddata(self.datasource.get_userdata(), "userdata")
        self._store_raw_vendordata(
            self.datasource.get_vendordata_raw(), "vendordata"
        )
        self._store_processeddata(
            self.datasource.get_vendordata(), "vendordata"
        )
        self._store_raw_vendordata(
            self.datasource.get_vendordata2_raw(), "vendordata2"
        )
        self._store_processeddata(
            self.datasource.get_vendordata2(), "vendordata2"
        )

Stage 7

    # Stage 7
    try:
        # Attempt to consume the data per instance.
        # This may run user-data handlers and/or perform
        # url downloads and such as needed.
        (ran, _results) = init.cloudify().run(
            "consume_data",
            init.consume_data,
            args=[PER_INSTANCE],
            freq=PER_INSTANCE,
        )
        if not ran:
            # Just consume anything that is set to run per-always
            # if nothing ran in the per-instance code
            #
            # See: https://bugs.launchpad.net/bugs/819507 for a little
            # reason behind this...
            init.consume_data(PER_ALWAYS)

.cloudify() によって Cloud クラスの実体に変換される。

https://github.com/canonical/cloud-init/blob/main/cloudinit/stages.py

    def cloudify(self):
        # Form the needed options to cloudify our members
        return cloud.Cloud(
            self.datasource,
            self.paths,
            self.cfg,
            self.distro,
            helpers.Runners(self.paths),
            reporter=self.reporter,
        )

https://github.com/canonical/cloud-init/blob/main/cloudinit/cloud.py

class Cloud:
    def __init__(
        self,
        datasource: DataSource,
        paths: Paths,
        cfg: dict,
        distro: Distro,
        runners: Runners,
        reporter: Optional[events.ReportEventStack] = None,
    ):
# snipped

    def run(self, name, functor, args, freq=None, clear_on_fail=False):
        return self._runners.run(name, functor, args, freq, clear_on_fail)

https://github.com/canonical/cloud-init/blob/main/cloudinit/helpers.py

class Runners:
    def __init__(self, paths):
        self.paths = paths
        self.sems = {}
# snipped

    def run(self, name, functor, args, freq=None, clear_on_fail=False):
        sem = self._get_sem(freq)
        if not sem:
            sem = DummySemaphores()
        if not args:
            args = []
        if sem.has_run(name, freq):
            LOG.debug("%s already ran (freq=%s)", name, freq)
            return (False, None)
        with sem.lock(name, freq, clear_on_fail) as lk:
            if not lk:
                raise LockFailure("Failed to acquire lock for %s" % name)
            else:
                LOG.debug("Running %s using lock (%s)", name, lk)
                if isinstance(args, (dict)):
                    results = functor(**args)
                else:
                    results = functor(*args)
                return (True, results)

https://github.com/canonical/cloud-init/blob/main/cloudinit/stages.py

    def consume_data(self, frequency=PER_INSTANCE):
        # Consume the userdata first, because we need want to let the part
        # handlers run first (for merging stuff)
# snipped

Stage 8

Modules クラスの実体を作成する。

    # Stage 8 - re-read and apply relevant cloud-config to include user-data
    mods = Modules(init, extract_fns(args), reporter=args.reporter)

https://github.com/canonical/cloud-init/blob/main/cloudinit/config/modules.py

class Modules:
    def __init__(self, init: Init, cfg_files=None, reporter=None):
# snipped

Stage 10

    # Stage 10
    return (init.datasource, run_module_section(mods, name, name))
def run_module_section(mods: Modules, action_name, section):
    full_section_name = MOD_SECTION_TPL % (section)
    (which_ran, failures) = mods.run_section(full_section_name)
# snipped

https://github.com/canonical/cloud-init/blob/main/cloudinit/config/modules.py

    def run_section(self, section_name):
        """Runs all modules in the given section.

        section_name - One of the modules lists as defined in
          /etc/cloud/cloud.cfg. One of:
         - cloud_init_modules
         - cloud_config_modules
         - cloud_final_modules
        """
# snipped