cloud-init, cloud image e Cloud Hypervisor

Três nomes que aparecem juntos e fazem coisas diferentes. Confundi-los é a causa mais comum de uma VM que arranca e não faz nada — ou que não arranca de todo.

Os três em uma frase

PeçaO que éQuando aparece
cloud image (cloud-img) Um disco .qcow2 publicado por uma distro, já instalado e pronto a arrancar. Não tem utilizador, nem password, nem chave SSH. O FROM de um VMfile, e o disco base de vm create.
cloud-init O programa que corre DENTRO dessa imagem no primeiro arranque e a personaliza: hostname, utilizadores, chaves, rede, comandos. É o que torna a cloud image utilizável. Sem ele, ficas com um disco genérico onde não consegues entrar.
Cloud Hypervisor Um VMM — o programa do HOST que executa a VM. Alternativa ao QEMU/libvirt, feito para microVMs. --backend cloud-hypervisor, e obrigatório para type: microvm.

Analogia com containers, que ajuda mais do que qualquer definição: a cloud image é a imagem, o cloud-init é o ENTRYPOINT da primeira execução, e o Cloud Hypervisor é o runtime — o equivalente ao runc.

1 · Cloud image — o disco

Cada distro publica um qcow2 pré-instalado, pequeno (algumas centenas de MB) e com o cloud-init já lá dentro. O motor sabe descarregar três famílias e verifica sempre o checksum — nunca aceita um download sem o confrontar.

# Num VMfile
FROM ubuntu:24.04        # cloud-images.ubuntu.com
FROM debian:bookworm     # cloud.debian.org
FROM rocky:9             # dl.rockylinux.org

# Ou directamente, sem VMfile nenhum
delonix vm create dev --url-img https://.../imagem.qcow2

Os três publicam checksums de maneiras diferentes, e isso está tratado: Ubuntu usa SHA256SUMS no formato GNU, Debian publica só SHA512SUMS (não há SHA256 nenhum), e Rocky usa um .CHECKSUM por ficheiro no formato BSD SHA256 (ficheiro) = hash. Com --url-img, o motor procura um <url>.sha256 ao lado; se não existir, diz que está a confiar só no TLS em vez de calar.

O disco vem pequeno de propósito — tipicamente 2 GB. Cresce-o antes de instalares seja o que for, com SIZE 20G no VMfile: crescer depois de um RUN ter enchido o disco é tarde.

2 · cloud-init — a personalização do primeiro arranque

Uma cloud image acabada de descarregar não tem conta nenhuma. O cloud-init procura um datasource no arranque, lê de lá o user-data, e aplica-o. O Delonix usa o datasource NoCloud: gera um ISO por instância e liga-o à VM.

# O motor gera o seed sozinho quando lhe dás qualquer um destes
delonix vm create dev --hostname dev-01 --ssh-key ~/.ssh/id_ed25519.pub
delonix vm create dev --user-data ./user-data.yaml
delonix vm create dev --seed ./o-meu-seed.iso   # ISO já feito por ti

Um user-data mínimo e completo:

#cloud-config
hostname: dev-01
users:
  - name: delonix
    groups: [sudo]
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... o-teu-email@exemplo

package_update: true
packages: [nginx]

runcmd:
  - [systemctl, enable, --now, nginx]

final_message: "pronto em $UPTIME segundos"

Duas camadas, e a distinção importa. O CLOUDINIT de um VMfile é assado NA IMAGEM (fica em /etc/cloud/cloud.cfg.d) — é o comportamento por omissão de todas as VMs feitas a partir dela. O --user-data do vm create é POR INSTÂNCIA e assenta por cima. Uma é a receita da imagem, a outra é a configuração daquela VM.

Armadilha real, já paga: um kind: VirtualMachine sem seed nenhum fazia o cloud-init saltar a fase de rede, e a VM ficava sem IP e sem rota (Network is unreachable lá dentro). Por isso o motor gera sempre um seed mínimo, mesmo quando não pedes nada — não é opcional.

E o cloud-init só corre uma vez: guarda um marcador em /var/lib/cloud. Um vm restart não o volta a executar — para reaplicar, ou limpas esse estado dentro da VM ou crias outra.

3 · Cloud Hypervisor — o VMM

Quem executa a VM. O Delonix tem dois backends por trás do mesmo trait, e a escolha não é cosmética:

Cloud Hypervisorlibvirt/QEMU
ArranqueMuito rápido (dezenas de ms) — feito para microVMs Segundos; máquina completa emulada
DispositivosSó virtio, mínimoTudo (TPM, vídeo, USB, …)
Redetap na SDN do holder — alcança os containers por IP directo virbr0, no netns do HOST — outra L2
Isolamento por namespaceSim Não — --namespace é recusado com erro, não aceite-e-ignorado
SnapshotsNão implementado (fail-closed, com erro que aponta para o libvirt) vm snapshot/restore: memória + disco
Arranque de cloud imagePrecisa de firmware (hypervisor-fw/EDK2) ou de kernel+initrd directos Arranca a cloud image tal como está
delonix vm create micro --backend cloud-hypervisor --firmware /usr/share/hypervisor-fw
delonix vm create pesada --backend libvirt          # default quando CH não está instalado

Qual usar, e quando

Se queres…Usa
Uma VM de trabalho, com snapshots e consola --backend libvirt (é o que a golden do Kubernetes exige — não arranca em CH)
Arranque em milissegundos, isolamento por namespace, ou alcançar containers por IP --backend cloud-hypervisor + firmware
Personalizar UMA VMcloud-init por instância: --hostname/--ssh-key/--user-data
Personalizar TODAS as VMs de um modeloUm VMfile com CLOUDINIT, e image vm build
Um disco à tua medida, publicávelvm init --vmfile → image vm build → vm push

Onde isto falha, e o que ver

SintomaQuase sempre é
A VM arranca e não entras por SSH Sem seed de cloud-init — não há utilizador nenhum. Passa --ssh-key.
IP <none> para sempre libvirt caiu em modo user (SLIRP), cujo IP é invisível. Junta-te ao grupo libvirt — o motor avisa-o no create.
A VM não arranca em Cloud Hypervisor Falta o firmware. CH não faz boot BIOS: precisa de --firmware ou de --kernel+--initrd.
O disco enche a meio do image vm build SIZE em falta, ou depois de um RUN. É propriedade da stage, e corre antes de tudo.
Mudaste o user-data e nada muda O cloud-init já correu naquela VM. Cria outra, ou limpa /var/lib/cloud lá dentro.

Referências

cloud-init, cloud image and Cloud Hypervisor

Three names that show up together and do different things. Mixing them up is the most common reason a VM boots and does nothing — or doesn't boot at all.

The three, in one sentence each

PieceWhat it isWhere it shows up
cloud image (cloud-img) A .qcow2 disk published by a distro, already installed and ready to boot. No user, no password, no SSH key. The FROM of a VMfile, and the base disk for vm create.
cloud-init The program that runs INSIDE that image on first boot and customizes it: hostname, users, keys, networking, commands. It's what makes the cloud image usable. Without it you're left with a generic disk you can't log into.
Cloud Hypervisor A VMM — the HOST-side program that runs the VM. An alternative to QEMU/libvirt, built for microVMs. --backend cloud-hypervisor, and required for type: microvm.

A container analogy helps more than any definition: the cloud image is the image, cloud-init is the ENTRYPOINT of the first run, and Cloud Hypervisor is the runtime — the equivalent of runc.

1 · Cloud image — the disk

Every distro publishes a small (a few hundred MB), pre-installed qcow2 with cloud-init already inside. The engine knows how to download three families and always verifies the checksum — it never accepts a download without checking it against one.

# In a VMfile
FROM ubuntu:24.04        # cloud-images.ubuntu.com
FROM debian:bookworm     # cloud.debian.org
FROM rocky:9             # dl.rockylinux.org

# Or directly, with no VMfile at all
delonix vm create dev --url-img https://.../image.qcow2

The three publish checksums differently, and that's handled: Ubuntu uses SHA256SUMS in GNU format, Debian publishes only SHA512SUMS (no SHA256 at all), and Rocky uses a per-file .CHECKSUM in BSD format (SHA256 (file) = hash). With --url-img, the engine looks for a <url>.sha256 next to it; if it doesn't exist, it says so — trusting TLS alone — instead of staying quiet.

The disk comes small on purpose — typically 2 GB. Grow it before installing anything, with SIZE 20G in the VMfile: growing it after a RUN has already filled the disk is too late.

2 · cloud-init — first-boot customization

A freshly downloaded cloud image has no account at all. cloud-init looks for a datasource at boot, reads user-data from it, and applies it. Delonix uses the NoCloud datasource: it generates a per-instance ISO and attaches it to the VM.

# The engine generates the seed on its own when you give it any of these
delonix vm create dev --hostname dev-01 --ssh-key ~/.ssh/id_ed25519.pub
delonix vm create dev --user-data ./user-data.yaml
delonix vm create dev --seed ./my-seed.iso   # ISO you already built yourself

A minimal, complete user-data:

#cloud-config
hostname: dev-01
users:
  - name: delonix
    groups: [sudo]
    shell: /bin/bash
    sudo: ['ALL=(ALL) NOPASSWD:ALL']
    ssh_authorized_keys:
      - ssh-ed25519 AAAA... your-email@example

package_update: true
packages: [nginx]

runcmd:
  - [systemctl, enable, --now, nginx]

final_message: "ready in $UPTIME seconds"

Two layers, and the distinction matters. A VMfile's CLOUDINIT is baked INTO THE IMAGE (it lands in /etc/cloud/cloud.cfg.d) — it's the default behavior of every VM made from it. vm create's --user-data is PER INSTANCE and sits on top. One is the image's recipe, the other is that particular VM's configuration.

Real trap, already paid for: a kind: VirtualMachine with no seed at all made cloud-init skip the networking phase, leaving the VM with no IP and no route (Network is unreachable inside). That's why the engine always generates a minimal seed, even when you ask for nothing — it isn't optional.

And cloud-init only runs once: it keeps a marker in /var/lib/cloud. A vm restart doesn't re-run it — to reapply it, either clear that state inside the VM or create another one.

3 · Cloud Hypervisor — the VMM

The thing that runs the VM. Delonix has two backends behind the same trait, and the choice isn't cosmetic:

Cloud Hypervisorlibvirt/QEMU
BootVery fast (tens of ms) — built for microVMs Seconds; a full emulated machine
Devicesvirtio only, minimalEverything (TPM, video, USB, …)
Networkingtap on the holder's SDN — reaches containers by direct IP virbr0, in the HOST's netns — a different L2
Per-namespace isolationYes No — --namespace is refused with an error, never accepted-and-ignored
SnapshotsNot implemented (fail-closed, with an error pointing at libvirt) vm snapshot/restore: memory + disk
Booting a cloud imageNeeds firmware (hypervisor-fw/EDK2) or a direct kernel+initrd Boots the cloud image as-is
delonix vm create micro --backend cloud-hypervisor --firmware /usr/share/hypervisor-fw
delonix vm create heavy --backend libvirt          # default when CH isn't installed

Which to use, and when

If you want…Use
A workhorse VM, with snapshots and a console --backend libvirt (it's what the Kubernetes golden image needs — it won't boot in CH)
Millisecond boot, per-namespace isolation, or to reach containers by IP --backend cloud-hypervisor + firmware
To customize ONE VMPer-instance cloud-init: --hostname/--ssh-key/--user-data
To customize EVERY VM from one templateA VMfile with CLOUDINIT, and image vm build
Your own publishable diskvm init --vmfile → image vm build → vm push

Where this breaks, and what to check

SymptomAlmost always
The VM boots and you can't SSH in No cloud-init seed — there's no user at all. Pass --ssh-key.
IP <none> forever libvirt fell back to user mode (SLIRP), whose IP is invisible. Join the libvirt group — the engine warns about it at create.
The VM won't boot under Cloud Hypervisor Missing firmware. CH doesn't do BIOS boot: it needs --firmware or --kernel+--initrd.
The disk fills up mid-image vm build Missing SIZE, or set after a RUN. It's a stage property, and runs before everything else.
You changed user-data and nothing changes cloud-init already ran on that VM. Create another one, or clear /var/lib/cloud inside it.

References