2023-10-23

Terraform Kubernetes Provider: Kiezen tussen generieke en versie-specifieke bronnen

Bij het orkestreren van infrastructuur met behulp van Terraform's Kubernetes-provider duikt een terugkerend dilemma op: ofwel de generieke resources gebruiken of configuraties vastzetten op een specifieke Kubernetes API-versie. Beide benaderingen hebben hun eigen voor- en nadelen.

Generieke bronnen, zoals {% c-line %}kubernetes_namespace{% c-line-end %}, brengen een bepaald niveau van abstractie met zich mee. Ze verbergen de details van de Kubernetes API-versie. Deze abstractie impliceert dat je configuraties naadloos zullen aanpassen aan de standaard Kubernetes API-versie die door de provider wordt ondersteund. In een dynamische omgeving waar je wilt dat je infrastructuur automatisch de voordelen van bijgewerkte Kubernetes-versies gebruikt, lijkt dit een aantrekkelijke optie. Deze flexibiliteit kent echter ook nadelen. Afhankelijk zijn van de standaardversies van de provider kan onverwachte gedragingen introduceren, vooral als nieuwere Kubernetes-versies brekende veranderingen bevatten. Bovendien loop je met een generieke opzet mogelijk de onmiddellijke voordelen mis die nieuwere API-versies kunnen bevatten.

Aan de andere kant bieden versie-specifieke bronnen, zoals {% c-line %}kubernetes_namespace_v1{% c-line-end %}, een andere reeks voordelen. Door je configuraties vast te zetten op een specifieke versie, zorg je voor een omgeving met stabiliteit en voorspelbaarheid. Zo'n aanpak is van onschatbare waarde, vooral voor productiewerkbelastingen waar de kleinste onverwachte verandering kan uitmonden in grotere problemen. Naast stabiliteit is er een inherente duidelijkheid in het gebruik van versie-specifieke bronnen. Ze geven duidelijk aan op welke versie van de Kubernetes API je verwacht. Bovendien stellen deze bronnen je doorgaans in staat om op een meer gedetailleerde manier gebruik te maken van functies die uniek zijn voor hun overeenkomstige API-versie. Maar, zoals altijd, zijn er compromissen. Vastleggen op een specifieke versie kan toekomstige migraties bemoeilijken en nauwgezette updates van Terraform-configuraties vereisen wanneer een verschuiving in versie wordt overwogen.

Complexe vs eenvoudige resources

Het voorbeeld hierboven is een eenvoudige namespace binnen een cluster. Dit is een resource die weinig gaan veranderen, en de API hiervan is stabiel te noemen. Hiervoor zou je kunnen overwegen om toch de generieke versie te gebruiken. Wanneer je echter een mix van al-dan-niet {% c-line %}_v1{% c-line-end %} types gaat introduceren, kom je al snel in een onduidelijke situatie. Best om dus eenduidig te zijn, en de keuze te maken voor een all-or-nothing aanpak.

Migreren tussen generieke en versie-specifieke bronnen

Wanneer je overweegt om tussen verschillende brontypes in Terraform te migreren, vooral bij een reeds toegepaste workload, zijn er twee methoden om te overwegen: het gebruik van {% c-line %}terraform state mv{% c-line-end %} om state-objecten te muteren of door deze te verwijderen en vervolgens opnieuw te importeren.

Gebruik van terraform state mv

Het commando {% c-line %}terraform state mv{% c-line-end %} stelt je in staat om bronnen binnen je staat te verplaatsen. Deze methode zorgt ervoor dat de onderliggende bron niet wordt vernietigd en opnieuw wordt gemaakt, watvooral cruciaal is voor productiewerkbelastingen.

Bijvoorbeeld, als je van een generieke {% c-line %}kubernetes_namespace{% c-line-end %} naar een versie-specifieke {% c-line %}kubernetes_namespace_v1{% c-line-end %} verhuist, zou je het volgende uitvoeren:

$ terraform state mv kubernetes_namespace.example kubernetes_namespace_v1.example

Hier is kubernetes_namespace.example het adres van de initiele resource en kubernetes_namespace_v1.example het adres van de nieuwe. Pas de adressen aan volgens je daadwerkelijke resources.

Deze methode gebruik ik het meest bij eenvoudigere objecten: Namespaces, RBAC definities, storage-omschrijvingen, ...

Verwijderen en Opnieuw Importeren

Deze methode omvat het verwijderen van de bron uit de staat en vervolgens opnieuw importeren onder het nieuwe brontype. Het is iets riskanter omdat er een periode is waarin de bron niet wordt bijgehouden, maar het werkt goed voor bronnen die importeren ondersteunen. Het voorbeeld hieronder gebruikt een resource in een module, maar is in functionaliteit niet anders.

Stappen:

​Haal het resource-ID op van de huidige resource.

$ terraform state show module.mongodb.kubernetes_namespace.mongodb
# module.mongodb.kubernetes_namespace.mongodb:
resource "kubernetes_namespace" "mongodb" {
    id                               = "mongodb"
    wait_for_default_service_account = false

    metadata {
        annotations      = {}
        generation       = 0
        labels           = {}
        name             = "mongodb"
        resource_version = "37735496"
        uid              = "7e55d60b-b647-4226-9569-b47cfb92119a"
    }
}

In het veld "ID" zie je de waarde van de resource-ID, "mongodb" in ons geval.

Verwijder de oude resource uit de state (vernietigt de daadwerkelijke resource NIET, haalt enkel de representatie weg uit terraforms state):

$ terraform state rm module.mongodb.kubernetes_namespace.mongodb

Pas je Terraform-configuratie aan om de gewenste resource te gebruiken ({% c-line %}kubernetes_namespace_v1{% c-line-end %} in dit geval).

Importeer de bron opnieuw in de state:

$ terraform import module.mongodb.kubernetes_namespace_v1.mongodb <resource-ID>


Vervang {% c-line %}<resource-ID>{% c-line-end %} door de juiste identifier voor je resource. Je moet de Kubernetes-documentatie of je eigen infrastructuurgegevens raadplegen om deze ID te krijgen.

Deze methode pas ik liever toe bij complexere definities, zoals bvb een {% c-line %}Deployment{% c-line-end %}. Mogelijk ga ik die eerst wel proberen met een eenvoudige {% c-line %}state mv{% c-line-end %}, maar als er teveel verschillen opduiken, is het soms handig om een re-import te gaan doen.

Welke optie kiezen

​De kubernetes-provider is een uitzondering in de veelgebruikte providers; die biedt zowel de generieke als de versie-specifieke resources aan. Providers als AWS, Azure en GCP doen dit bijvoorbeeld niet.

Bij Krane Labs kiezen we normaal voor versie-specifieke variant. Hoewel de aantrekkingskracht van generieke bronnen en hun aanpasbaarheid groot is, geeft het gebruik van versie-specifieke bronnen vooral stabiliteit op lange termijn, wat bij ons het doel is. In productieomgevingen is het verstandig om voorspelbaarheid te omarmen. Configuraties vastzetten op een specifieke Kubernetes API-versie zorgt ervoor dat het opnieuw toepassen van Terraform-workloads in de toekomst geen onverwachte aanpassingen gaat doen. Het is een weloverwogen keuze, die gecontroleerde evolutie verkiest boven onbedoelde mutaties, en zorgt voor een basis waar productieklare infrastructuur ongestoord blijft.

Wanneer je een workload wil aligneren op een bepaalde versie, is de aanpak van {% c-line %}terraform state mv{% c-line-end %} over het algemeen veiliger en eenvoudiger omdat deze ervoor zorgt dat de eigenlijke onderliggende bron onaangetast blijft. Echter, als {% c-line %}state mv{% c-line-end %} om de een of andere reden niet haalbaar is, is de methode van verwijderen en opnieuw importeren een alternatief. Ongeacht de methode, maak altijd een back-up van je Terraform-staat voordat je wijzigingen aanbrengt. Indien mogelijk, test deze bewerkingen eerst in een veilige, niet-productieomgeving om ervoor te zorgen dat je het proces en de mogelijke implicaties begrijpt, en de handelingen in de vingers krijgt, mocht je dit ooit moeten toepassen op productie.

De keuze tussen de ene of de andere aanpak is niet nieuw. Deze ligt in lijn met de discussies rond het al dan niet version-pinnen van deployments ({% c-line %}:latest{% c-line-end %} of {% c-line %}:specifieke-tag{% c-line-end %}), het auto-updaten van niet-security OS pakketten, of het vast of los definieren van versies van bvb providers en terraform zelf.

Onze laatste blogposts