Kubernetes op Oxide: Hoe klantbehoeften onze integraties hebben gevormd

Eind 2024 waren klanten en potentiële klanten enthousiast om Kubernetes op Oxide te draaien, maar we hadden geen ondersteunde integraties om hen daarbij te helpen.

Kubernetes en Oxide zijn een natuurlijke match. Kubernetes definieert het infrastructuurgedrag dat het verwacht via standaard extensiepunten, terwijl Oxide de primitieve elementen die nodig zijn om dat gedrag te implementeren ontsluit via API's. De basis voor integratie was aanwezig; wat ontbrak was de software en een inzicht in welke integraties klanten daadwerkelijk nodig hadden.

Dat was de situatie toen ik bij Oxide in dienst trad als de eerste Solutions Software Engineer [1], met als focus het bouwen van software om problemen van klanten op te lossen. Mijn eerste opdracht was om het gemakkelijker te maken om Kubernetes op Oxide te implementeren en te beheren.

In mijn eerste week kreeg ik twee hulpmiddelen aangereikt om mee te beginnen:

  • Een door een klant ingediende pull-request voor een Rancher node-driver.
  • Een vroege conceptversie van RFD 493 Initial Kubernetes Integrations.

Wat begon met deze twee bronnen, groeide uit tot een teamprestatie die werd gevormd door een feedbackloop. In plaats van integraties in abstractie te ontwerpen, volgden we de problemen die klanten tegenkwamen terwijl ze bewogen van het provisioneren van clusters naar het beheren van workloads.

Dit artikel volgt deze problemen langs de Kubernetes-levenscyclus in plaats van in strikte chronologische volgorde. Verschillende provisioning-workflows leidden ons naar Rancher, Omni en Cluster API. Het draaien van clusters vereiste infrastructuurreconciliatie, het ontsluiten van applicaties legde netwerkkloven bloot, en stateful workloads maakten opslagbeperkingen duidelijk. In elke fase legden de workflows van klanten de volgende tekortkoming bloot, wat zowel de integraties die we bouwden als het resterende platformwerk vormgaf.

Hoe provisioneer ik een Kubernetes-cluster op Oxide?

Het eerste gat dat we aanpakten was provisioning. Ons directe doel was om de klant te helpen die de pull-request voor de Rancher node-driver had ingediend. Door hun use case uit te werken, zouden we ook firsthand ervaring opdoen met het maken van Kubernetes-clusters op Oxide en de volgende problemen ontdekken.

Omdat geen enkele provisioning-aanpak paste bij alle workflows van klanten, hebben we uiteindelijk drie integraties gepubliceerd.

Rancher Node-driver

Voordat we de door de klant ingediende integratie konden onderhouden, moesten we de workflow begrijpen die het ondersteunde. Aangezien ik nog nooit met Rancher of een node-driver had gewerkt, betekende het beoordelen van de bijdrage dat ik beide moest leren.

Een Rancher node-driver is een uitvoerbare plugin die Rancher leert hoe virtuele machines moeten worden gemaakt en beheerd op een specifiek infrastructuurplatform. De Oxide Rancher node-driver vertaalt deze operaties naar Oxide API-verzoeken. Eenmaal geïnstalleerd in Rancher, kunnen klanten Oxide-instanties provisioneren als nodes in Kubernetes-clusters die door Rancher worden beheerd.

Tests bevestigden dat de implementatie van de klant werkte. Ik heb de pull-request samengevoegd, CI/CD en documentatieverbeteringen toegevoegd en de eerste release gepubliceerd. Oxide had officieel zijn eerste Kubernetes-integratie — en een klant gebruikte deze al succesvol in productie.

Omni Infrastructuurprovider

Klanten toonden interesse in het gebruik van Sidero Labs' Omni om Kubernetes-clusters te provisioneren die draaien op Talos Linux. Omni verbindt met infrastructuurplatforms via infrastructuurproviders: programma's die Talos Linux-instanties maken en deze registreren bij Omni.

Met KubeCon North America 2025 in het vooruitzicht, zagen we een kans om samen te werken met Sidero Labs om een Oxide-infrastructuurprovider voor Omni te bouwen en te presenteren. We hadden zeven weken om dit te voltooien voor ons Oxide+Sidero event [2]. Het bouwen tegen een tweede provisioning-platform zou bovendien de API's van Oxide testen over verschillende klantworkflows.

Het integratiewerk legde enkele problemen bloot in Omni en Talos Linux. Ik heb deze problemen gemeld bij Sidero Labs (in siderolabs/omni#1633), waar hun team enthousiast was om met ons samen te werken — een mooie herinnering aan RFD 68 Partnership as Shared Values.

Het meest memorabele probleem was siderolabs/talos#11948. Oxide gebruikt een FAT12-bestandssysteem voor cloud-init user-data, niet ISO 9660. De filesystem probe van Talos probeerde echter alleen een ISO 9660 superblock te lezen van de NoCloud configuratieschijf. Wanneer die leesactie mislukte, stopte de probe in plaats van andere formaten zoals VFAT of MS-DOS te proberen. Als gevolg hiervan las Talos nooit de Oxide user-data die de configuratie bevatte die nodig was om lid te worden van Omni. De oplossing zou niet op tijd voor KubeCon worden uitgebracht, wat ons dwong tot een nogal grappige workaround: de user-data opvullen met commentaren om de grootte voldoende te vergroten zodat er een ISO 9660 superblock wordt gebruikt.

Tijdens KubeCon hielden we een Oxide+Sidero event om de Oxide-infrastructuurprovider voor Omni te demonstreren. Klanten kunnen deze provider nu gebruiken om Oxide-instanties met Talos Linux te provisioneren als nodes in door Omni beheerde Kubernetes-clusters.

Cluster API Provider

We wisten dat we een infrastructuurprovider wilden bouwen voor de Kubernetes Cluster API (CAPI) toen we RFD 493 Initial Kubernetes Integrations schreven. Cluster API bood iets wat onze eerste twee integraties niet boden: een upstream, provider-uitbreidbare API voor het beheren van clusters zonder dat daar een third-party platform zoals Rancher of Omni voor nodig is.

CAPI stelt operators in staat om declaratief Kubernetes-clusters te maken, te schalen, te upgraden en te verwijderen via Kubernetes custom resources. Infrastructuurproviders regelen het platformspecifieke werk, zoals het maken en verwijderen van virtuele machines. Het bouwen hiervan is een aanzienlijke investering. Destijds rechtvaardigden de klantvraag en de technische capaciteit deze investering nog niet, waardoor het project werd uitgesteld.

Uiteindelijk veranderde beide. Klanten vroegen om een CAPI-provider en het Solutions Software Engineering-team groeide. Mijn collega's Josh en Brandon namen het werk op zich en brachten Cluster API Provider Oxide (CAPOx) uit, waardoor klanten een Kubernetes-native manier hebben om clusters op Oxide te provisioneren.

De Cluster API-workflow maakt ook gebruik van verschillende andere integraties, waardoor we de end-to-end clusterworkflow kunnen "dogfooden" [3]. De Kubernetes Image Builder gebruikt onze Packer-plugin om CAPI-ready Oxide VM-images te maken, die CAPOx vervolgens gebruikt bij het provisioneren van instanties. Clusters die met CAPOx zijn geprovisioneerd, gebruiken ook de apart geïnstalleerde Oxide cloud controller manager (CCM) om Kubernetes tijdens runtime te integreren met Oxide.

Hoe houdt Kubernetes Oxide-instanties bij?

Provisioning-integraties maken en beheren Oxide-instanties, maar ze reconciliëren deze instanties niet met Kubernetes Node-objecten. Zonder die reconciliatie zou een cluster niet betrouwbaar kunnen bepalen of een onbereikbare Kubernetes node tijdelijk niet beschikbaar was, of dat de onderliggende Oxide-instantie was verwijderd.

We hadden een component nodig die in elk cluster draaide, communiceerde met de Oxide API en continu de Oxide-infrastructuur synchroniseerde met de Kubernetes-status. Kubernetes biedt hiervoor een standaard extensiepunt: de cloud controller manager (CCM). Een CCM stelt infrastructuurspecifieke controllers in staat om Kubernetes-resources te integreren met de API van een infrastructuurprovider zonder provider-specifieke code aan Kubernetes zelf toe te voegen.

We hebben de Oxide cloud controller manager gebouwd om Kubernetes met Oxide te verbinden. De node-controller houdt Kubernetes Node-objecten gesynchroniseerd met hun onderliggende Oxide-instanties, registreert details zoals instantie-ID's en netwerkadressen, en rapporteert of elke instantie draait, is uitgeschakeld of niet meer bestaat. Kubernetes gebruikt deze informatie om nodes te initialiseren en ze veilig te verwijderen wanneer de onderliggende instanties worden gewist.

De CCM maakt geen instanties aan en provisioneert geen clusters; dat blijft het werk van provisioning-integraties zoals de Rancher node-driver, de Omni infrastructuurprovider en CAPOx. In plaats daarvan biedt het een runtime-integratie die gedeeld wordt over deze workflows. Belangrijk is dat het bouwen van de CCM ons een duurzaam extensiepunt binnen elk cluster heeft gegeven. Naarmate Oxide evolueert, kunnen we nieuwe infrastructuurbewuste controllers aan de CCM toevoegen in plaats van elke provisioning-integratie afzonderlijk bij te werken.

Met dit runtime-extensiepunt op zijn plek konden we een andere laag van de Kubernetes-ervaring aanpakken: het ontsluiten van applicaties. De CCM-architectuur definieert ook een service-controller voor Kubernetes LoadBalancer services, wat ons een startpunt gaf voor het volgende klantprobleem.

Hoe gebruik ik LoadBalancer services?

Een van de functionaliteiten die klanten verwachten van cloud-geïntegreerde Kubernetes is ondersteuning voor Service-objecten van het type LoadBalancer. Wanneer een gebruiker er een aanmaakt, vraagt Kubernetes de service-controller van de cloudprovider om de nodige infrastructuur te provisioneren en het adres in de Service-status te publiceren. Er was echter één probleem: Oxide bood nog geen native load balancer aan.

Oxide had echter wel floating IP's. Floating IP's zijn adressen uit de externe IP-pools van een rack die kunnen worden gekoppeld aan en losgekoppeld van instanties, waardoor deze instanties bereikbaar zijn van buiten hun VPC's. Het gebruik van floating IP's bood een manier om LoadBalancer services mogelijk te maken. Een floating IP zou verkeer naar één Kubernetes node leiden, waarna de Kubernetes Service dataplane dit verkeer kon distribueren naar de juiste pods.

Om dit werkend te krijgen, moest er rekening worden gehouden met hoe Oxide floating IP's verschijnen voor een instantie. Ze zijn op twee belangrijke manieren transparant voor de guest:

  1. Oxide vertaalt het bestemmingsadres van inkomend verkeer naar het interne IP van de instantie voordat het verkeer naar de instantie wordt gestuurd.
  2. De instantie heeft geen netwerkinterface geconfigureerd met het floating IP.

De resulterende verkeersstroom ziet er als volgt uit:

Client (Request naar floating IP: 45.154.216.233:80) $\downarrow$ Oxide networking (Vertaalt bestemming naar intern IP: 172.30.0.5:80) $\downarrow$ Kubernetes node (Pakket arriveert op intern IP: 172.30.0.5:80) $\downarrow$ Kubernetes Service dataplane (Selecteert een Service endpoint) $\downarrow$ Pod (Ontvangt verkeer op de doelpoort)

Deze adresvertaling zorgde voor een subtiel integratieprobleem. De Kubernetes Service dataplane moest het interne IP van de node behandelen als een Service-frontend, omdat dat het bestemmingsadres was dat pakketten daadwerkelijk droegen wanneer ze de guest bereikten. De service-controller publiceert daarom twee vermeldingen in status.loadBalancer.ingress [4]:

  • Het gekoppelde floating IP in Proxy mode.
  • Het interne IP van de node in VIP mode.

De statusvermeldingen zien er zo uit:

status:
  loadBalancer:
    ingress:
      - ip: 45.154.216.233
        ipMode: Proxy
      - ip: 172.30.0.5
        ipMode: VIP

Als gevolg hiervan ziet de kubectl output er een beetje ongebruikelijk uit:

$ kubectl get service nginx
NAME    TYPE           CLUSTER-IP       EXTERNAL-IP                 PORT(S)        AGE
nginx   LoadBalancer   10.106.122.233   45.154.216.233,172.30.0.5   80:30605/TCP   37h

Gebruikers zien zowel het floating IP als het interne IP van de node in de kolom EXTERNAL-IP, hoewel alleen het floating IP extern bereikbaar is. Dit is een imperfecte abstractie, maar het stelt ons in staat om een veelvoorkomende Kubernetes-workflow te ondersteunen terwijl we wachten op een native Oxide load balancer.

Deze implementatie ondersteunt momenteel externalTrafficPolicy: Cluster [5], wat de geselecteerde node in staat stelt om verkeer door te sturen naar een Service endpoint overal in het cluster. Als die node verdwijnt, verplaatst de CCM het floating IP naar een andere geschikte node en werkt het interne adres in de Service-status bij.

Wanneer Oxide een native load-balancing service introduceert, kunnen we de service-controller updaten om deze te gebruiken zonder de Kubernetes-interface te wijzigen. Klanten blijven dezelfde LoadBalancer services aanmaken en alleen de infrastructuur erachter verandert.

Hoe gebruik ik Oxide-opslag in Kubernetes?

Nu clusters geprovisioneerd waren, gesynchroniseerd met Oxide en bereikbaar van buiten hun VPC's, was opslag voor stateful workloads de volgende laag om aan te pakken. Kubernetes-gebruikers vragen persistente opslag aan via PersistentVolumeClaim-objecten en verwachten dat een Container Storage Interface (CSI) driver de onderliggende volumes maakt, koppelt en monteert.

Oxide had schijven, maar Kubernetes had geen native manier om hun levenscyclus te beheren. Zonder een Oxide CSI-driver konden klanten een third-party Kubernetes-opslagsysteem zoals Longhorn implementeren. Longhorn biedt zijn eigen CSI-driver en repliceert data over schijven die aan Kubernetes workers zijn gekoppeld. Het gebruik van Longhorn betekende echter dat de replica's werden ondersteund door Oxide distributed disks, die zelf al drie replica's op verschillende sleds opslaan.

Het stapelen van twee gerepliceerde opslagsystemen kan leiden tot aanzienlijke write fan-out. Wanneer een Longhorn-volume met drie replica's wordt ondersteund door three-way-replicated Oxide distributed disks, kan één applicatieschrijfactie resulteren in maar liefst negen fysieke schijfschrijfacties. De exacte write amplification hangt af van de workload en configuratie, maar klanten wilden deze dubbele replicatie vermijden.

De introductie van Oxide lokale schijven bood een manier om de tweede laag van replicatie te verwijderen. Lokale schijven hebben geen ingebouwde replicatie en blijven gebonden aan hun sled, waardoor ze zeer geschikt zijn voor systemen zoals Longhorn die data over Kubernetes-nodes repliceren. Onze Rancher showcase maakt vandaag de dag gebruik van deze aanpak. Dit voorkomt het stapelen van twee gerepliceerde opslagsystemen, hoewel Longhorn nog steeds de opslaglevenscyclus beheert in plaats van een native Oxide-integratie.

Voor een native integratie schreef mijn collega Luiz RFD 595 Oxide CSI Plugin. Op papier leek de workflow eenvoudig: wanneer een gebruiker een PersistentVolumeClaim aanmaakt, maakt de CSI-controller een Oxide distributed disk aan. Nadat Kubernetes de pod plant, koppelt de controller die schijf aan de geselecteerde Oxide-instantie, waarna de CSI node-plugin deze formateert en monteert voor de pod. Als de pod naar een andere node wordt verplaatst, ontkoppelt de controller de schijf en koppelt deze aan de nieuwe node.

Het prototypen van die workflow legde onmiddellijk een blokkade bloot. Oxide vereist dat een instantie is gestopt voordat er een schijf kan worden gekoppeld of ontkoppeld. Kubernetes verwacht echter dat een CSI-driver opslag koppelt aan een draaiende worker na het plannen van een pod. Het stoppen van de worker zou elke andere workload op die node verstoren en zou kunnen leiden tot cascaderende scheduling- en koppelingsoperaties.

Voordat we onze CSI-plugin kunnen uitbrengen, moeten we ondersteuning voor disk hot-plug toevoegen in de gehele Oxide stack, van de hypervisor tot aan de API. Wat begon als een Kubernetes-integratie is uitgegroeid tot een project dat meerdere lagen van de Oxide softwarestack beslaat.

Disk hot-plug en de Oxide CSI-plugin zijn op het moment van schrijven nog in actieve ontwikkeling. In de tussentijd kunnen klanten software zoals Longhorn gebruiken met Oxide lokale schijven voor dynamisch geprovisioneerde persistente opslag zonder dubbele replicatielagen. Wanneer de native CSI-plugin wordt uitgebracht, kunnen klanten vertrouwde Kubernetes storage API's gebruiken die direct worden ondersteund door Oxide distributed disks met ingebouwde replicatie en duurzaamheid.

Wat is nu de volgende stap?

Het resultaat is niet één enkele Kubernetes-integratie, maar een groeiend ecosysteem. Rancher, Omni en Cluster API bieden verschillende paden voor provisioning, terwijl de Oxide CCM een gedeelde runtime-integratie biedt voor node-reconciliatie en LoadBalancer services. Klanten gebruiken sommige van deze integraties al in productie, en we "dogfooden" er verschillende in onze eigen productieworkloads. Samen vormen ze een solide basis om op voort te bouwen.

Onze volgende stap is om ons dogfooding uit te breiden met de onlangs uitgebrachte Cluster API provider. Door dit te gebruiken voor het provisioneren en beheren van meer van onze clusters, kunnen we testen hoe deze integraties dagelijks samenwerken.

Er is nog veel te bouwen en polijsten. Ons werk op korte termijn omvat het voltooien van disk hot-plug en het uitbrengen van de CSI-plugin, het toevoegen van ondersteuning voor autoscaling, en het uitbreiden van de CCM service-controller om externe subnets te ondersteunen. Op langere termijn zullen we onze Kubernetes-integraties uitbreiden om gebruik te maken van resource tagging, OIDC-ondersteuning en native load balancing zodra deze beschikbaar komen.

Het bouwen van deze integraties heeft laten zien hoe de architecturen van Kubernetes en Oxide elkaar aanvullen. Kubernetes biedt infrastructuurproviders standaard extensiepunten, terwijl Oxide infrastructuurprimitieven ontsluit via API's. Het hardware- en software-codesign van Oxide stelt ons in staat om integratieblokkades aan te pakken op de laag waar ze thuishoren en de nodige wijzigingen door de volledige stack te voeren.

Dit werk stelt ons ook in staat om onze SDK's en API's vanuit het perspectief van onze klanten te testen en wrijving bij de klant om te zetten in productverbeteringen. Die feedbackloop is hoe we dit ecosysteem verder zullen laten groeien. Klantbehoeften hebben elke integratie in dit artikel vormgegeven, en dat zullen ze ook doen voor de volgende.

***

Voetnoten:

  1. Er is nu een team! Kijk het Oxide and Friends-episode Solutions Software Engineering with Matthew Sanabria.
  2. Ons Oxide+Sidero event was op 12 november 2025. Het werk aan de Omni infrastructuurprovider begon op 24 september.
  3. Dogfooding is de praktijk van het gebruiken van je eigen producten of diensten. Oxide heeft een rack in het kantoor genaamd "dogfood" dat specifiek hiervoor is gewijd.
  4. Kubernetes gebruikt ipMode om aan te geven of verkeer de node bereikt met het load-balancer adres als bestemming (VIP) of nadat de bestemming is vertaald (Proxy).
  5. Ondersteuning voor externalTrafficPolicy: Local zou vereisen dat het floating IP nodes volgt met lokale Service endpoints naarmate pods worden verplaatst, wat zou resulteren in meer koppelings- en ontkoppelingsoperaties.