Een virtuele machine die opnieuw opstart, hoort daarna precies te doen wat hij daarvoor deed. Zelfde packages, zelfde SSH-host keys, zelfde instellingen. Een herstart is saai, en dat is de bedoeling.

Op sommige virtuele machines gebeurde na een herstart te veel: packages geüpgraded, host-keys opnieuw gegenereerd, alsof het een volledig nieuwe server is. Dit is hoe we hebben uitgezocht waar dat vandaan kwam.

Een herstart die dingen overdeed

Wat we zagen: stappen die normaal alleen bij de allereerste start van een VM horen te gebeuren, werden na een herstart opnieuw uitgevoerd.

Dat is om twee redenen vervelend. Een package-upgrade op een willekeurig moment verwacht niemand. En nieuwe host-keys betekenen dat SSH bij de volgende verbinding begint te mekkeren dat de 'identiteit' van de server is veranderd. Dat levert verwarring op.

Na flink wat testen zagen we het patroon: het gebeurde nadat een VM naar een andere hypervisor was verhuisd. Zo'n verhuizing is bij ons routine, bijvoorbeeld voor onderhoud of het vervangen van hardware. De VM merkt daar normaal niets van.

Wat Cloud-init doet

Even een stap terug: wat is Cloud-init eigenlijk? Cloud-init configureert een VM bij de eerste start, zodat je dat niet handmatig hoeft te doen. Denk aan het instellen van de hostname, netwerkinstellingen, users, SSH-keys, DNS-resolvers, en een eerste ronde package-upgrades.

Dat is eenmalig werk. Daarna doet Cloud-init - onder normale omstandigheden - niets meer.

Maar hoe weet Cloud-init eigenlijk wanneer een VM voor het eerst start, en die dus hoort te draaien? Cloud-init houdt een file bij met de instance ID: de identiteit van deze machine. Kent Cloud-init die ID al, dan is dit niet de eerste start en slaat het de eenmalige stappen over. Is de ID onbekend, dan concludeert Cloud-init: nieuwe machine, alles opnieuw doen.

Daar zit onze bug.

Een ID die geen ID is

Je zou verwachten dat een instance ID vastligt zodra een machine wordt aangemaakt, en daarna nooit meer verandert. Maar dat zit anders. Proxmox, de virtualisatie-software die Cloud-init als het ware aanstuurt, zet de instance ID op de hash van de Cloud-init-configuratie:

sub nocloud_gen_metadata {
    my ($user, $network) = @_;

    my $uuid_str = Digest::SHA::sha1_hex($user . $network);
    return nocloud_metadata($uuid_str);
}

De user-config en de netwerkconfig worden achter elkaar geplakt en door het hashing-algoritme SHA-1 gehaald. Eén teken verschil in een van beide configuraties, levert een compleet andere hash op. En een andere hash is wat Cloud-init betreft een andere machine.

De configuratie van onze VM's verandert niet wanneer ze van hypervisor naar hypervisor verhuizen. Waarom is de hash dan toch anders?

De boosdoener: de resolvers

De DNS-resolvers die een VM meekrijgt, staan in Proxmox standaard op 'use host settings'. De VM erft dan de resolver-instellingen van de hypervisor waarop hij draait. Handig, tot je hypervisors onderling gaan afwijken.

En dat deden ze. Door kleine wijzigingen door de jaren heen was de resolv.conf op onze hypervisors subtiel uit elkaar gaan lopen. Zolang een VM op één node bleef staan, merkte niemand daar iets van. Verhuisde hij, dan kwam er een andere resolver-regel in de netwerkconfig, en daarmee een andere hash, en daarmee een andere instance ID.

De directe oplossing is simpel: de resolvers expliciet configureren in Cloud-init, zodat de resolv.conf van de node er niet meer toe doet. Klaar, zou je zeggen.

De echte fix: de instance ID vastzetten

Maar wat als er iets anders in de configuratie verandert? Voeg een netwerkinterface toe, wissel van resolvers, pas iets aan in de user-config: elke keer heb je hetzelfde probleem.

Dus de enige echte oplossing is: de instance ID zelf zetten op een waarde die nooit verandert. Proxmox laat je de Cloud-init-configs overschrijven via cicustom. Dat deden we al voor de zogenaamde 'user config', maar nog niet voor de 'meta config', en juist daar zit de instance ID in. Het stappenplan:

  • Genereer zelf een SHA-1 hex, op dezelfde manier als Proxmox dat doet.
  • Schrijf per VM een meta-config met die ID erin.
  • Verwijs naar die meta-config in cicustom, zodat Proxmox onze instance ID gebruikt, in plaats van zijn eigen (die dus bij iedere wijziging verandert).

Vanaf dat moment verandert de ID alleen als wij dat willen. Dezelfde aanpak wordt op het Proxmox-forum en in de bugtracker aangeraden.

De fix die leek te falen

Dat leek niet te werken. Neem VM 236 op onze testomgeving. In cicustom staat netjes een meta-config, in die meta-config staat netjes een instance ID, en toch rapporteert Proxmox een heel andere:

root@pve-test:/mnt/pve/CIFS_Test# cat snippets/2-236-meta.yaml
#cloud-config
instance-id: 579229cbcd5cb55cdff9cdc1539d48931f1d7723

root@pve-test:/mnt/pve/CIFS_Test# qm cloudinit dump 236 meta
instance-id: 6e94e23bd69a9c5268d9dadd6b484543d4f117a2

Maar je wordt hier op een dwaalspoor gezet. De verklaring: qm cloudinit dump laat niet de configuratie zien die de VM daadwerkelijk krijgt, maar de configuratie die Proxmox zélf zou genereren. Precies wat wij overschrijven. Niet bepaald intuïtief.

Wat de VM echt gebruikt, lees je binnenin de VM:

cat /run/cloud-init/.instance-id

Daar staat onze eigen ID. De fix werkt dus gewoon.

Het addertje: de VM's die al draaien

Voor een nieuwe VM mag je een willekeurige instance ID zetten. Voor VM's die al draaien niet: die moeten hun huidige ID houden. Zetten we er een nieuwe in, dan doet de uitrol van deze fix precies wat we wilden voorkomen.

Proxmox heeft een API-endpoint dat de huidige instance ID teruggeeft: /nodes/{node}/qemu/{vmid}/cloudinit/dump. Daar zitten twee verrassingen in.

De eerste: dat endpoint geeft ook een instance ID terug voor VM's die helemaal geen Cloud-init gebruiken. Of een VM Cloud-init überhaupt gebruikt, moet je eerst apart opvragen via /nodes/{node}/qemu/{vmid}/cloudinit.

De tweede kennen jullie inmiddels: hetzelfde probleem als bij qm cloudinit dump. Dat endpoint hasht de configs die Proxmox zelf genereert, en negeert onze cicustom-overrides.

Wat dit zegt

Dat een instance ID uit een configuratie wordt afgeleid, is een twijfelachtige keuze, en we zijn niet de enigen met die visie. Er is een bugtracker-item voor, en Yuri Konotopov heeft een 'patch' gemaakt die het mogelijk maakt de instance ID vast te pinnen. Die patch zit nog niet in Proxmox.

Tegelijk heeft het gedrag een reden die vanuit de historie prima te verklaren is: Debian Stretch werkte de netwerkconfiguratie niet bij zolang de instance ID gelijk bleef. Een ID die meebeweegt met de config was toen dus een oplossing. Maar Debian Stretch stamt uit 2017, en zo wordt een workaround voor een antiek besturingssysteem een bug.

Wat deze zaak typeert: het symptoom is klein en het gevolg is groot. Niemand belt ons over een veranderde instance ID. Een klant ziet hooguit dat er ineens packages zijn geüpgraded, of dat een deploy struikelt over een host-key die veranderd is.

Dat is ons werk. Onzichtbaar als het goed gaat, en daarom de moeite waard. Kom een keer praten met een van onze engineers.