KennisbankCloud & infra

Een eigen cloud bouwen: de architectuur achter PlanetNode

30 juli 20269 min leestijd

Ons zusterbedrijf PlanetNode verkoopt cloud. De vraag die daarbij hoort is niet óf je virtualiseert, maar waarop. OpenStack kan het. OpenNebula ook. En nog een handvol andere platformen die grofweg hetzelfde beloven.

Het werd Proxmox VE — niet omdat de rest niet deugt, maar omdat elk van die platformen zijn eigen smaak heeft, en die van Proxmox past bij hoe wij werken. OpenStack is krachtig, maar je neemt er een stack bij die zelf een dagtaak aan onderhoud is; wij wilden een hypervisor met een API die doet wat je verwacht, en onze tijd steken in het product eromheen in plaats van in het platform eronder.

Want dáár zit het echte werk. Een hypervisor maakt nog geen cloud: er moet iets zijn dat weet welke images er zijn, welke IP-adressen vrij zijn, welke klant wat besteld heeft en hoe een verse machine erachter komt wie hij is. Dat is precies waar we zelf gebouwd hebben — bovenop componenten die we nooit zelf willen maken.

De verdeling werd: Proxmox VE voor virtualisatie, NetBox als bron van waarheid voor IPAM, Packer voor het bouwen van images en cloud-init voor de eerste boot in de gast. Allemaal open source, allemaal volwassen. Daaromheen bouwden we drie dingen zelf: een templatepipeline, een metadata-service en een provisioninglaag in ons klantportaal.

BOUWSTRAATPackerverzegeld imageKLANTPORTAALBron van waarheid · templatecatalogus · orders & facturatie · IPAM (NetBox)PROXMOX-NODETemplate tpl-…-v2026.07.1 Metadata-service 169.254.169.254 Virtuele server cloud-init clone 1234
  1. De bouwstraat levert hetzelfde verzegelde image aan elk cluster; de templatenaam is de syncsleutel.
  2. Na een betaalde order kloont het portaal de juiste template en configureert de machine via de Proxmox API.
  3. Bij de eerste boot vraagt de server zelf op wie hij is — hostnaam, sleutels, user-data.
  4. De metadata-service haalt het antwoord op bij het portaal over een ondertekend intern kanaal; er ligt nergens een kopie.
De drie zelfgebouwde lagen (bouwstraat, portaal, metadata-service) rond standaard open source componenten.

Dit is het verhaal van die drie lagen. Niet omdat iedereen een eigen cloud moet bouwen, maar omdat de afweging erin — wat koop je in, wat bouw je zelf — precies de afweging is die we ook voor klanten maken.

Wat een portaal moet kunnen wat een paneel niet doet

Er zijn kant-en-klare hostingpanelen, en die nemen je veel werk uit handen. Ze bepalen alleen ook je product: het paneel weet hoe een VPS eruitziet, hoe een order verloopt en welke velden een klant invult.

Wij wilden drie dingen die daar niet in passen. Eén netwerkontwerp naar eigen inzicht, want we hebben een eigen netwerk en houden IPAM op één plek. Eén administratie, waarin een order, een factuur en een draaiende machine hetzelfde proces zijn in plaats van drie systemen die je handmatig verzoent. En eigen tempo in het OS-aanbod: verschijnt er een nieuwe distributieversie, dan willen we die zelf kunnen bouwen, testen en uitrollen.

Laag 1: templates die je kunt vertrouwen

Elke virtuele server begint bij een template. Wij bouwen die met Packer op een aparte bouwmachine, bewust búiten de productieclusters. De uitkomst is een gewoon schijfimage, en dat is precies het punt: elk cluster krijgt exact hetzelfde artefact en behandelt het identiek — of er nu Ceph, ZFS of LVM onder ligt.

Een paar keuzes die het onderhoud draaglijk houden:

Eén build voor alle families. Er is niet een aparte pipeline voor Debian, Ubuntu, Rocky en Alpine. Er is één geparameteriseerde build; per familie leggen we alleen de officiële cloud image plus checksum vast. Nieuwe distributie erbij betekent een variabelenbestand, geen nieuwe pipeline.

Datumgebaseerde versies. Templates heten tpl-<familie>-v<jaar>.<maand>.<patch>. Die naam is de enige sleutel waarop clusters synchroniseren — interne ID's mogen per cluster verschillen, want daar wil je nooit van afhankelijk zijn. Distributie is idempotent: hij vervangt op naam, weigert alles aan te raken wat geen template is, en je kunt hem per cluster opnieuw draaien zonder na te denken.

Images worden verzegeld. Vóór het afsluiten gaan de SSH-hostkeys eruit, wordt /etc/machine-id geleegd, de cloud-init-state gewist en de bouwgebruiker verwijderd. Dat is geen netheid maar noodzaak, en het is de moeite waard om te weten wat er precies misgaat als je het overslaat:

  • Gedeelde hostkeys geven elke server uit die template dezelfde SSH-identiteit. Een klant die verbinding maakt met de verkeerde machine krijgt geen waarschuwing, want de vingerafdruk klopt — en wie het image ooit in handen krijgt, kan zich voordoen als elke server die eruit gekloond is.
  • Een gedeelde machine-id lekt door tot in je netwerklaag: systemd leidt er standaard de DHCPv6-DUID uit af, dus twee machines vragen met dezelfde identiteit een lease aan en betwisten elkaars adres. Daarnaast gebruiken journald en de meeste monitoring-agents het als unieke sleutel — je ziet twee servers als één host.
  • Achtergebleven cloud-init-state is het stilste probleem: cloud-init ziet bij de eerste boot dat hij "al gedraaid" heeft en slaat de hele configuratiestap over. De machine start dan op met de hostnaam en sleutels van het bouwmoment, zonder één foutmelding.

Controles vóór uitrol. De build valideert zichzelf, controleert ín het image of onze cloud-init-datasource daadwerkelijk laadt, en kan een testkloon starten die via de guest agent nagaat of alles werkt zoals bedoeld. Een template die daar niet doorheen komt, wordt niet uitgerold.

De catalogus zelf leeft in het klantportaal. Nieuwe templates verschijnen daar als concept; iemand zet ze bewust actief en wijst een standaard aan. Verouderde versies krijgen een opvolger toegewezen in plaats van te verdwijnen — bestaande machines blijven onaangeroerd, alleen nieuwe installaties volgen de opvolgerketen. Zo is "de nieuwe Debian uitrollen" een besluit, geen bijwerking van een geslaagde build.

Laag 2: een eigen metadata-service, en waarom

Hier wordt het interessant. Als een virtuele server voor het eerst opstart, moet hij weten wie hij is: hostnaam, SSH-sleutels, eventuele opstartscripts. In de cloudwereld gebeurt dat via een metadata-service — het adres 169.254.169.254 dat je bij elke grote provider vindt. De machine vraagt zelf op wie hij is.

Proxmox heeft daar een eigen mechanisme voor, maar dat levert vooral netwerkgegevens: IP, gateway, DNS. Voor hostnaam, sleutels en user-data moet je uitwijken naar bestanden op de hypervisor. Dat werkt, maar het betekent dat klantgegevens gekopieerd op je nodes komen te staan — en dat het bijwerken ervan bestandsbeheer op productiehosts wordt. Voor gegevens die per klant verschillen en gaandeweg wijzigen is dat precies de verkeerde plek.

Dus bouwden we onze eigen metadata-service: een kleine daemon in Rust op elke hypervisornode. Naar de gast toe spreekt hij het bekende protocol dat elke cloud-image al kent. Naar binnen toe bewaart hij niets — hij vraagt het antwoord op bij het klantportaal, over een intern kanaal dat per node ondertekend en tegen replay beschermd is. Het portaal blijft de enige bron van waarheid; een wijziging is direct actief omdat er nergens een kopie ligt die kan verouderen.

Het belangrijkste ontwerpprincipe zit in hoe de daemon bepaalt wíe er belt: op basis van waar de verbinding vandaan komt, nooit op basis van wat de machine over zichzelf beweert. Zou je de gast laten zeggen "ik ben server X", dan kan server A de sleutels van server B opvragen. Identiteit hoort uit de infrastructuur te komen, niet uit de vraag.

Twee lessen uit cloud-init die uren kostten

Wie zelf een cloud-init-datasource schrijft, loopt gegarandeerd tegen deze twee aan. Ze staan nergens duidelijk in de documentatie, dus bij dezen:

1. Het detectiescript kent jouw datasource niet. Cloud-init draait bij de start een detectiestap die bepaalt welke bronnen überhaupt geprobeerd worden. Die kent alleen de ingebouwde bronnen; de jouwe wordt stilzwijgend uit de lijst gegooid, zonder foutmelding. Zodra hij bovendien een Proxmox-configuratieschijf ziet, herschrijft hij de lijst naar alleen die bron. De oplossing is niet je datasource beter maken, maar de lijst zo configureren dat de detectiestap zichzelf uitschakelt.

2. De eerste bootfase legt je keuze vast. Cloud-init draait in fases: eerst één vóór het netwerk, daarna één erna. De datasource die in de eerste fase gekozen wordt, geldt voor de hele boot — een bron die zich pas ná het netwerk meldt, wordt nooit meer bekeken. Dat betekent dat je datasource zich in de vroege fase moet registreren, en dus zélf tijdelijk netwerk moet opbrengen om de metadata-service te bereiken. De ingebouwde EC2-variant doet exact hetzelfde; dat je dat moet nadoen, ontdek je alleen door het mis te zien gaan.

Beide lessen leverden hetzelfde inzicht op: onze datasource haalt het netwerk uit wat Proxmox meegeeft — dat blijft Proxmox' verantwoordelijkheid — en de identiteit uit onze eigen service. Valt de metadata-service weg, dan boot de machine gewoon door op wat er al lag. Geen enkele component mag single point of failure zijn voor "komt de server op".

Laag 3: van order naar draaiende server

De laatste laag is de provisioning, en die leeft in het klantportaal — een Laravel-applicatie die tegelijk winkel, administratie en bedieningspaneel is.

Als een bestelling betaald is, gaat er een opdracht de wachtrij in die zes stappen doorloopt: adres reserveren (uit NetBox, onder een slot zodat twee gelijktijdige orders nooit hetzelfde IP krijgen), identiteit genereren, machine klonen uit de juiste template van dát cluster, configureren, starten, en verifiëren dat de guest agent antwoordt.

Van order naar server
  1. Adresuit NetBox
  2. IdentiteitSMBIOS-UUID
  3. Klonenuit de template
  4. Configurerennetwerk & specs
  5. Starteneerste boot
  6. Verifiërenguest agent

Reserveren gebeurt onder een slot per prefix: twee gelijktijdige orders krijgen nooit hetzelfde IP.

Het machine-ID wordt vastgelegd vóórdat de machine bestaat — een crash laat hooguit een registratie zonder machine achter, nooit andersom.

Elke stap is idempotent: een nieuwe poging pakt op waar het misging in plaats van opnieuw te beginnen.

Zes idempotente stappen tussen een betaalde bestelling en een bereikbare server.

Twee eigenschappen maken het verschil tussen "werkt meestal" en "werkt altijd":

Elke stap is idempotent en losstaand herstartbaar. Klapt de opdracht er halverwege uit, dan pakt de nieuwe poging op waar het misging in plaats van opnieuw te beginnen. Bestellingen die half zijn uitgevoerd zijn de duurste soort storing die er is — die kosten handwerk én vertrouwen.

Boekhouding vóór actie. Een machine-ID wordt geclaimd en vastgelegd voordat de machine wordt aangemaakt. Crasht het proces tussen die twee momenten, dan bestaat er hooguit een registratie zonder machine — op te ruimen. Andersom, een machine die nergens geregistreerd staat, is een weesmachine die niemand meer terugvindt. Bij dit soort processen kies je altijd de foutrichting die je kunt opruimen.

De opruimlogica is expliciet: gereserveerde adressen worden pas definitief bij succes, en een periodieke controle ruimt reserveringen op die nergens meer bij horen.

Wat het oplevert

Van bestelling tot een draaiende, bereikbare server zonder dat er een mens aan te pas komt — met IPAM, facturatie en machinebeheer in één administratie. Nieuwe distributieversies zijn een geplande build plus één klik om ze actief te zetten. En omdat vrijwel alles standaard open source is, kunnen we elke laag zelf repareren, uitleggen en vervangen.

Dat laatste is geen ideologie maar een praktische keuze. Als je klanten infrastructuur toevertrouwen, moet je kunnen uitleggen hoe het werkt en moet je bij storing niet afhankelijk zijn van iemands supportafdeling. Alles wat we zelf bouwden — de pipeline, de metadata-service, de provisioning — is bewust dun gehouden: het is de lijm tussen volwassen componenten, niet een herbouw ervan.

Dezelfde aanpak, voor jouw omgeving

Deze architectuur is voor PlanetNode gebouwd, maar de bouwstenen komen uit hetzelfde gereedschap dat we voor klanten inzetten: software die koppelt wat niet vanzelf koppelt, netwerken die we zelf ontwerpen en beheren, en infrastructuur in Nederlandse datacenters.

De rode draad is steeds dezelfde vraag, dezelfde die ook bij onze webshop-integratie centraal stond: wat koop je standaard in, en waar bouw je zelf? Ons antwoord is consequent — koop het fundament, bouw de lijm. Standaardcomponenten waar het kan, eigen software precies daar waar jouw proces afwijkt van het gemiddelde.

Benieuwd wat dat voor jouw situatie betekent? Plan een technische intake — dan kijken we samen waar die grens hoort te liggen.

Veelgestelde vragen

Veelgestelde vragen

Waarom niet gewoon een kant-en-klaar hostingpaneel?

Panelen zijn snel klaar, maar bepalen wel hoe jouw product eruitziet. Zodra je iets wilt dat het paneel niet kent — een eigen netwerkontwerp, een afwijkende provisioningflow, koppelingen met je eigen administratie — bouw je eromheen in plaats van ermee. Wij wilden het product leidend laten zijn en de infrastructuur volgend, en dan kom je uit op eigen componenten bovenop standaard bouwstenen.

Is dit niet veel meer werk dan een hyperscaler gebruiken?

In het begin ja. Maar je koopt er iets voor terug: geen vendor lock-in, voorspelbare kosten, data die aantoonbaar in Nederland staat en volledige vrijheid in wat je aanbiedt. Bovendien is de onderhoudslast lager dan mensen denken, omdat vrijwel alles standaard open source is — de eigen code is de lijm, niet het fundament.

Kunnen jullie zoiets ook voor ons bouwen?

Ja, dat is precies waarom we deze case delen. Of het nu gaat om een virtualisatieplatform, een provisioningkoppeling of automatisering rond bestaande infrastructuur: dezelfde aanpak — open source als basis, eigen software als lijm, en het bedrijfsproces als uitgangspunt.

Antwoord niet gevonden?

Stel je vraag direct aan een engineer — we reageren doorgaans binnen één werkdag.