Вы запускаете инстанс в облаке или из своего шаблона KVM, и через минуту он уже с правильным hostname, вашим SSH-ключом, нужными пакетами и смонтированным диском. Никто руками туда не заходил. Это работа cloud-init - стандарта де-факто первичной инициализации виртуальных машин. В этом уроке разберём, откуда инстанс берёт свои настройки (datasources), как устроены user-data и meta-data, какие модули и в каком порядке отрабатывают, и главное - как чинить, когда инициализация молча не сработала.

Как это работает
cloud-init - это набор Python-программ и systemd-юнитов, который запускается на первой загрузке свежего инстанса. Его задача - привести идентичный для всех образ к уникальной рабочей конфигурации. Образ один, а инстансов из него тысячи, и каждому нужны свои hostname, ключи, сеть. cloud-init вытаскивает эти различия снаружи, а не зашивает их в образ.
Данные приходят из двух логических потоков. meta-data - это то, что про инстанс знает платформа: instance-id, регион, hostname, сетевые параметры, публичный IP. Их формирует гипервизор или облачный контроллер, администратор их обычно не пишет. user-data - это то, что задаёт пользователь: что установить, кого создать, что выполнить. Вот user-data вы и пишете руками.
Источник, откуда всё это читается, называется datasource. Их много: EC2 (метаданные по адресу 169.254.169.254), Azure, GCE, OpenStack, а для своих стендов - NoCloud, где данные лежат на примонтированном ISO с меткой cidata или передаются через ядро. На раннем этапе загрузки утилита ds-identify перебирает признаки (DMI-строки, метки разделов, ядровые параметры) и решает, в каком мы облаке. Результат кэшируется в /run/cloud-init/, чтобы не гадать заново.
Загрузка разбита на четыре стадии, привязанные к systemd-таргетам. Стадия local (cloud-init-local.service) поднимается до сети - тут определяется datasource и применяется сетевая конфигурация. Стадия network (cloud-init.service) - сеть уже есть, монтируются диски, готовится база. Стадия config (cloud-config.service) - основная масса модулей. Стадия final (cloud-final.service) - то, что должно идти последним: пакеты, пользовательские скрипты, runcmd. Разбиение нужно, чтобы, например, сеть встала до того, как мы полезем ставить пакеты из интернета.
Что именно отрабатывает на каждой стадии, задаёт /etc/cloud/cloud.cfg тремя списками: cloud_init_modules, cloud_config_modules, cloud_final_modules. Каждый модуль отвечает за свою область - users-groups, ssh, write-files, disk-setup, runcmd, packages. user-data в формате cloud-config просто передаёт этим модулям параметры в YAML.
Команды и примеры
Минимальный user-data, покрывающий типовые задачи администратора. Первая строка #cloud-config обязательна - по ней cloud-init понимает формат.
Код: Выделить всё
#cloud-config
hostname: web01
fqdn: web01.example.lan
users:
- name: deploy
groups: [sudo]
shell: /bin/bash
sudo: ['ALL=(ALL) NOPASSWD:ALL']
ssh_authorized_keys:
- ssh-ed25519 AAAAC3Nz... deploy@laptop
write_files:
- path: /etc/myapp/config.yml
permissions: '0640'
owner: root:root
content: |
env: prod
workers: 4
disk_setup:
/dev/vdb:
table_type: gpt
layout: true
fs_setup:
- device: /dev/vdb1
filesystem: ext4
label: data
mounts:
- [ /dev/vdb1, /srv/data, ext4, "defaults,nofail", "0", "2" ]
runcmd:
- [ systemctl, enable, --now, myapp.service ]
- curl -fsS https://example.lan/bootstrap | bashКод: Выделить всё
#cloud-config
package_update: true
package_upgrade: true
packages:
- nginx
- jq
- htopЛокальный стенд без облака - datasource NoCloud. Готовим seed-ISO и цепляем к ВМ:
Код: Выделить всё
# создаём два файла
printf 'instance-id: iid-001\nlocal-hostname: lab01\n' > meta-data
cp my-user-data user-data
# Debian/Ubuntu: пакет с genisoimage/xorriso
apt install cloud-image-utils xorriso
cloud-localds seed.iso user-data meta-data
# RHEL/Fedora: тот же ISO вручную
dnf install xorriso
xorriso -as mkisofs -output seed.iso -volid cidata -joliet -rock user-data meta-dataДиагностика. Главные команды, которые надо знать на экзамене и в бою:
Код: Выделить всё
cloud-init status --wait --long # ждёт завершения, печатает стадию и ошибки
cloud-init analyze blame # что дольше всего тормозило загрузку
cloud-init analyze show # таймлайн по стадиям
cloud-init schema --system # валидация применённого cloud-config
cloud-init query userdata # показать, что реально прилетело
cloud-init query ds.meta_data # разобранные meta-dataПерегенерация для повторного теста на том же образе:
Код: Выделить всё
cloud-init clean --logs --rebootЧастые грабли
- Забыли первую строку #cloud-config или поставили пробел перед ней - cloud-init воспримет user-data как обычный скрипт и YAML не применится. Ошибки при этом может и не быть в очевидном месте.
- Битый YAML: табы вместо пробелов, неэкранированные двоеточия в content. Прогоняйте cloud-init schema --system или валидатор YAML до деплоя.
- Модули по умолчанию отрабатывают только на первой загрузке (семафор per-instance). Поменяли user-data и ребутнули - ничего не произошло, пока не сделаете cloud-init clean или не смените instance-id.
- runcmd выполняется на стадии final, поздно. Если команде нужна сеть или пакет - убедитесь, что пакет в packages, а не ставится тут же руками без гарантии порядка.
- Метка seed-ISO не cidata - NoCloud не подхватится, инстанс молча уйдёт в дефолт. Проверяйте blkid.
- disk_setup и fs_setup не трогают уже размеченный диск без явного overwrite - защита от потери данных, но люди думают, что модуль не работает.
- Путаница логов: свои ошибки скриптов ищите в cloud-init-output.log, а не в основном cloud-init.log.
- Поднимите чистую ВМ из официального cloud-образа Debian 13 или Fedora 41 в локальном KVM.
- Напишите user-data: создайте пользователя с вашим SSH-ключом и sudo без пароля, установите пакет htop, через write_files положите /etc/motd.
- Соберите seed.iso через cloud-localds (или xorriso с volid cidata), прицепите к ВМ, загрузитесь.
- Зайдите по SSH созданным пользователем, проверьте htop и содержимое motd.
- Выполните cloud-init status --long и cloud-init analyze blame, найдите самый долгий модуль.
- Сломайте YAML намеренно (отступ табом), пересоберите ISO, сделайте cloud-init clean --reboot и поймайте ошибку в schema и логах.
- Восстановите конфиг, добавьте runcmd с записью даты в файл, убедитесь, что он отработал ровно один раз после повторного clean.
- Чем meta-data отличается от user-data и кто формирует каждый из них?
- Перечислите четыре стадии загрузки cloud-init и какому событию (сеть, диски) соответствует каждая.
- Как ds-identify определяет, что инстанс работает в datasource NoCloud, и какая метка тома для этого нужна?
- Почему изменённый user-data не применяется после простого reboot и как заставить cloud-init отработать заново?
- В каком файле искать ошибку команды из runcmd, а в каком - ошибку самого модуля?
- Какие три списка в /etc/cloud/cloud.cfg управляют набором модулей и чем отличается их время выполнения?