仮想マシン(VM)や想マシンスケールセット(VMSS)はcloud-initという仕組みを使用すると、起動時に予め定義された処理を実行する事ができます。例えば、アプリケーションサーバをインストールしたり証明書を配布したりなどのオーケストレーションが可能です。このページではcloud-initの操作例をまとめます。
前提
公式ドキュメント
参考になる公式ドキュメントを以下に示します。
- cloud-initの概要
- チュートリアル – Azure での Linux 仮想マシンの初回の起動時に cloud-init を使用してカスタマイズする方法
- cloud-init を使用して Bash スクリプトを実行する
事前設定
以下、リソースグループを作成します。
az group create --name MyResourceGroup --location japaneast
cloud-initを利用した構築
cloud-initファイルの作成
cloud-initはJSON形式で起動時の処理を記述できます。以下は「チュートリアル – Azure での Linux 仮想マシンの初回の起動時に cloud-init を使用してカスタマイズする方法」で紹介された設定例です。チュートリアルで紹介された設定例と全く同じファイルを作成します。
なお、cloud-initはAzureの公式ドキュメントでは紹介されていない便利な使い方も色々ありますので、必要に応じて「Cloud config examples」などのcloud-initのマニュアルも必要に応じて参照ください。
cat << 'EOF' > cloud-init-nginx.txt #cloud-config package_upgrade: true packages: - nginx - nodejs - npm write_files: - owner: www-data:www-data path: /etc/nginx/sites-available/default content: | server { listen 80; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection keep-alive; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } } - owner: azureuser:azureuser path: /home/azureuser/myapp/index.js content: | var express = require('express') var app = express() var os = require('os'); app.get('/', function (req, res) { res.send('Hello World from host ' + os.hostname() + '!') }) app.listen(3000, function () { console.log('Hello world app listening on port 3000!') }) runcmd: - service nginx restart - cd "/home/azureuser/myapp" - npm init - npm install express -y - nodejs index.js EOF
仮想マシンの作成
引数custom-dataにcloud-initのファイルパスを指定して仮想マシンを作成します。このような指定をする事によって、仮想マシン作成後にcloud-initで記述された処理が実行されます。
az vm create \ --resource-group MyResourceGroup \ --name linux020 \ --image UbuntuLTS \ --size Standard_D1_v2 \ --priority Spot \ --admin-username azureuser \ --ssh-key-values ~/.ssh/authorized_keys \ --custom-data cloud-init-nginx.txt
動作確認
仮想マシン作成時のログは以下の通りです。仮想マシンに割り当てられたパブリックIPアドレスを確認します。
admin@mac19 ~ % az vm create \ --resource-group myResourceGroup \ --name linux020 \ --image UbuntuLTS \ --size Standard_D1_v2 \ --priority Spot \ --admin-username azureuser \ --ssh-key-values ~/.ssh/authorized_keys \ --custom-data cloud-init-nginx.txt It is recommended to use parameter "--public-ip-sku Standard" to create new VM with Standard public IP. Please note that the default public IP used for VM creation will be changed from Basic to Standard in the future. { "fqdns": "", "id": "/subscriptions/2e2f81a9-b030-410f-9784-c582580c932e/resourceGroups/myResourceGroup/providers/Microsoft.Compute/virtualMachines/linux020", "location": "japaneast", "macAddress": "00-22-48-E7-07-C3", "powerState": "VM running", "privateIpAddress": "10.0.0.4", "publicIpAddress": "52.253.108.202", "resourceGroup": "myResourceGroup", "zones": "" }
前述の操作で確認したパブリックIPアドレスをブラウザに入力し、nginxとnodejsによって作成されたWebページを確認します。なお、OS起動後にcloud-initが動作します。ですので、仮想マシンの作成完了とnginxとnodejsのインストール完了にはタイムラグがある事に注意してください。
ここでは初学者向けに「OS起動後」という曖昧な言い方をしていますが、cloud-initは処理によって実行されるタイミングが異なります。正確な情報は公式ドキュメントの「Boot Stages」を参照ください。
スクリプトを利用した構築
動作確認用スクリプトの作成
前述のcustome-dataにはjsonではなくスクリプトを指定する事もできます。例えば、既存の構築スクリプトがあり、技術負債を現行踏襲せざるを得ないその資産を有効活用する場合などが想定されます。
以下、postgresqlをインストールするスクリプトを適当なファイル名で作成します。
cat << 'EOF' > install-postgresql13.sh #!/bin/bash setenforce 0 sed -i 's/SELINUX=enforcing/SELINUX=disabled/' /etc/selinux/config systemctl disable firewalld.service --now dnf install -y https://download.postgresql.org/pub/repos/yum/reporpms/EL-8-x86_64/pgdg-redhat-repo-latest.noarch.rpm dnf -qy module disable postgresql dnf install -y postgresql13-server /usr/pgsql-13/bin/postgresql-13-setup initdb systemctl enable postgresql-13 --now EOF
仮想マシンの作成
引数custom-dataにスクリプトのファイルパスを指定して仮想マシンを作成します。
az vm create \ --resource-group MyResourceGroup \ --name linux030 \ --image procomputers:rocky-linux-8-minimal:rocky-linux-8-minimal:latest \ --size Standard_D1_v2 \ --priority Spot \ --admin-username azureuser \ --ssh-key-values ~/.ssh/authorized_keys \ --custom-data install-postgresql13.sh
動作確認
仮想マシンにsshでログインし、postgresqlが起動していることを確認します。
[azureuser@linux030 ~]$ sudo systemctl status postgresql-13.service ● postgresql-13.service - PostgreSQL 13 database server Loaded: loaded (/usr/lib/systemd/system/postgresql-13.service; enabled; vendor preset: disabled) Active: active (running) since Sat 2022-03-05 06:40:40 UTC; 1min 39s ago Docs: https://www.postgresql.org/docs/13/static/ Process: 7426 ExecStartPre=/usr/pgsql-13/bin/postgresql-13-check-db-dir ${PGDATA} (code=exited, status=0/SUCCESS) Main PID: 7432 (postmaster) Tasks: 8 (limit: 20655) Memory: 16.9M CGroup: /system.slice/postgresql-13.service ├─7432 /usr/pgsql-13/bin/postmaster -D /var/lib/pgsql/13/data/ ├─7433 postgres: logger ├─7435 postgres: checkpointer ├─7436 postgres: background writer ├─7437 postgres: walwriter ├─7438 postgres: autovacuum launcher ├─7439 postgres: stats collector └─7440 postgres: logical replication launcher Mar 05 06:40:40 linux030 systemd[1]: Starting PostgreSQL 13 database server... Mar 05 06:40:40 linux030 postmaster[7432]: 2022-03-05 06:40:40.309 UTC [7432] LOG: redirecting log output to logging collector process Mar 05 06:40:40 linux030 postmaster[7432]: 2022-03-05 06:40:40.309 UTC [7432] HINT: Future log output will appear in directory "log". Mar 05 06:40:40 linux030 systemd[1]: Started PostgreSQL 13 database server.
cloud-initのトラブルシューティング
トラブルの仕込み
Azureのチュートリアルで紹介されているnginxの構築例をRockyLinuxに適用してみましょう。この構築例はUbuntuを前提にしていますので、想定外のRockyLinuxに適用すると色々とトラブルが発生します。このトラブルを題材にして、色々と観察してみましょう。
az vm create \ --resource-group MyResourceGroup \ --name linux040 \ --image procomputers:rocky-linux-8-minimal:rocky-linux-8-minimal:latest \ --size Standard_D1_v2 \ --priority Spot \ --admin-username azureuser \ --ssh-key-values ~/.ssh/authorized_keys \ --custom-data cloud-init-nginx.txt
タイムラグ
cloud-initは仮想マシンの起動後に処理されます。したがって、sshでログイン可能な状態になったとしても、nginx等のインストールが完了しているとは限りません。
その証拠として、/var/log/messagesを観察してみましょう。06:48:54にOpenSSHが起動し、その後の06:49:45にcloud-initによるパッケージアップデートが開始されます。
[azureuser@linux040 ~]$ sudo cat /var/log/messages <omitted> Mar 5 06:48:54 linux040 systemd[1]: Started Permit User Sessions. Mar 5 06:48:54 linux040 systemd[1]: Started Command Scheduler. Mar 5 06:48:54 linux040 systemd[1]: Started Getty on tty1. Mar 5 06:48:54 linux040 systemd[1]: Started Job spooling tools. Mar 5 06:48:54 linux040 systemd[1]: Started Serial Getty on ttyS0. Mar 5 06:48:54 linux040 systemd[1]: Reached target Login Prompts. Mar 5 06:48:54 linux040 systemd[1]: Started OpenSSH server daemon. Mar 5 06:48:54 linux040 systemd[1]: Started System Logging Service. Mar 5 06:48:54 linux040 systemd[1]: Reached target Multi-User System. <omitted> Mar 5 06:49:39 linux040 kdumpctl[1447]: kdump: kexec: loaded kdump kernel Mar 5 06:49:39 linux040 kdumpctl[1447]: kdump: Starting kdump: [OK] Mar 5 06:49:39 linux040 systemd[1]: Started Crash recovery kernel arming. Mar 5 06:49:45 linux040 cloud-init[1451]: Red Hat CodeReady Linux Builder for RHEL 8 x86_ 2.8 MB/s | 6.4 MB 00:02 Mar 5 06:49:47 linux040 cloud-init[1451]: Microsoft Azure RPMs for Red Hat Enterprise Lin 2.6 kB/s | 2.4 kB 00:00 Mar 5 06:49:48 linux040 cloud-init[1451]: Last metadata expiration check: 0:00:01 ago on Sat 05 Mar 2022 06:49:47 AM UTC. Mar 5 06:49:52 linux040 cloud-init[1451]: Metadata cache created. Mar 5 06:49:54 linux040 cloud-init[1451]: Last metadata expiration check: 0:00:07 ago on Sat 05 Mar 2022 06:49:47 AM UTC. Mar 5 06:49:56 linux040 cloud-init[1451]: Dependencies resolved. Mar 5 06:49:56 linux040 cloud-init[1451]: ============================================================================================================= Mar 5 06:49:56 linux040 cloud-init[1451]: Package Arch Version Repository Size Mar 5 06:49:56 linux040 cloud-init[1451]: ============================================================================================================= Mar 5 06:49:56 linux040 cloud-init[1451]: Installing: Mar 5 06:49:56 linux040 cloud-init[1451]: kernel x86_64 4.18.0-348.12.2.el8_5 rhui-rhel-8-for-x86_64-baseos-rhui-rpms 7.0 M Mar 5 06:49:56 linux040 cloud-init[1451]: Upgrading: Mar 5 06:49:56 linux040 cloud-init[1451]: bpftool x86_64 4.18.0-348.12.2.el8_5 rhui-rhel-8-for-x86_64-baseos-rhui-rpms 7.7 M Mar 5 06:49:56 linux040 cloud-init[1451]: clevis x86_64 15-1.el8_5.1 rhui-rhel-8-for-x86_64-appstream-rhui-rpms 57 k Mar 5 06:49:56 linux040 cloud-init[1451]: clevis-luks x86_64 15-1.el8_5.1 rhui-rhel-8-for-x86_64-appstream-rhui-rpms 37 k Mar 5 06:49:56 linux040 cloud-init[1451]: cloud-init noarch 21.1-7.el8_5.3 rhui-rhel-8-for-x86_64-appstream-rhui-rpms 1.0 M
cloud-initの進捗を確認したい場合は、cloud-init-output.logをtailすると良いでしょう。cloud-init-output.logには、cloud-initの標準出力が記録されます。
[azureuser@linux040 ~]$ sudo tail -f /var/log/cloud-init-output.log Upgrading : polkit-0.115-13.el8_5.1.x86_64 18/157 Running scriptlet: polkit-0.115-13.el8_5.1.x86_64 18/157 Upgrading : libsmbclient-4.14.5-9.el8_5.x86_64 19/157 Upgrading : cyrus-sasl-gssapi-2.1.27-6.el8_5.x86_64 20/157 Upgrading : nss-softokn-freebl-3.67.0-7.el8_5.x86_64 21/157 Upgrading : nss-softokn-3.67.0-7.el8_5.x86_64 22/157 Upgrading : nss-sysinit-3.67.0-7.el8_5.x86_64 23/157 Upgrading : nss-3.67.0-7.el8_5.x86_64 24/157 Upgrading : systemd-udev-239-51.el8_5.3.x86_64 25/157 Running scriptlet: systemd-udev-239-51.el8_5.3.x86_64 25/157 Installing : kernel-core-4.18.0-348.12.2.el8_5.x86_64 26/157 Running scriptlet: kernel-core-4.18.0-348.12.2.el8_5.x86_64 26/157
エラー事例の観察
/var/log/cloud-init.logを表示し、cloud-initのwrite_filesモジュールのログに着目します。ログを見ると、www-dataというユーザとグループが存在しない事が分かります。www-dataユーザはUbuntuを前提としてますので、www-dataユーザが存在しないRockyLinuxではエラーとなります。
[azureuser@linux040 ~]$ sudo cat /var/log/cloud-init.log <omitted> 2022-03-05 06:48:52,120 - handlers.py[DEBUG]: finish: init-network/config-write-files: FAIL: running config-write-files with frequency once-per-instance 2022-03-05 06:48:52,120 - util.py[WARNING]: Running module write-files (<module 'cloudinit.config.cc_write_files' from '/usr/lib/python3.6/site-packages/cloudinit/config/cc_write_files.py'>) failed 2022-03-05 06:48:52,120 - util.py[DEBUG]: Running module write-files (<module 'cloudinit.config.cc_write_files' from '/usr/lib/python3.6/site-packages/cloudinit/config/cc_write_files.py'>) failed Traceback (most recent call last): File "/usr/lib/python3.6/site-packages/cloudinit/util.py", line 1381, in chownbyname uid = pwd.getpwnam(user).pw_uid KeyError: "getpwnam(): name not found: 'www-data'" The above exception was the direct cause of the following exception: Traceback (most recent call last): File "/usr/lib/python3.6/site-packages/cloudinit/stages.py", line 876, in _run_modules freq=freq) File "/usr/lib/python3.6/site-packages/cloudinit/cloud.py", line 54, in run return self._runners.run(name, functor, args, freq, clear_on_fail) File "/usr/lib/python3.6/site-packages/cloudinit/helpers.py", line 185, in run results = functor(*args) File "/usr/lib/python3.6/site-packages/cloudinit/config/cc_write_files.py", line 172, in handle write_files(name, files) File "/usr/lib/python3.6/site-packages/cloudinit/config/cc_write_files.py", line 212, in write_files util.chownbyname(path, u, g) File "/usr/lib/python3.6/site-packages/cloudinit/util.py", line 1385, in chownbyname raise OSError("Unknown user or group: %s" % (e)) from e OSError: Unknown user or group: "getpwnam(): name not found: 'www-data'"
/var/log/cloud-init-output.logを表示し、nodejsコマンドの実行結果に着目します。Ubuntuではnodejsパッケージインストールによってnodejsコマンドが使えるようになりますが、RockyLinuxではnodejsコマンドが使えません。よって、エラーが出力されます。
[azureuser@linux040 ~]$ sudo cat /var/log/cloud-init-output.log <omitted> /var/lib/cloud/instance/scripts/runcmd: line 6: nodejs: command not found Cloud-init v. 21.1-7.el8_5.3 running 'modules:final' at Sat, 05 Mar 2022 06:54:33 +0000. Up 933.93 seconds. 2022-03-05 06:54:38,699 - cc_scripts_user.py[WARNING]: Failed to run module scripts-user (scripts in /var/lib/cloud/instance/scripts) 2022-03-05 06:54:38,699 - util.py[WARNING]: Running module scripts-user (<module 'cloudinit.config.cc_scripts_user' from '/usr/lib/python3.6/site-packages/cloudinit/config/cc_scripts_user.py'>) failed Cloud-init v. 21.1-7.el8_5.3 finished at Sat, 05 Mar 2022 06:54:38 +0000. Datasource DataSourceAzure [seed=/dev/sr0]. Up 938.97 seconds