Appliquer les principes de GitOps à l'infrastructure: Introduction à tf-controller

Sommaire

Terraform est probablement l'outil "Infrastructure As Code" le plus utilisé pour construire, modifier et versionner les changements d'infrastructure Cloud. Il s'agit d'un projet Open Source développé par Hashicorp et qui utilise le langage HCL pour déclarer l'état souhaité de ressources Cloud. L'état des ressources créées est stocké dans un fichier d'état (terraform state).

On peut considérer que Terraform est un outil "semi-déclaratif" car il n'y a pas de fonctionnalité de réconciliation automatique intégrée. Il existe différentes approches pour répondre à cette problématique, mais en règle générale, une modification sera appliquée en utilisant terraform apply. Le code est bien décrit dans des fichiers de configuration HCL (déclaratif) mais l'exécution est faite de manière impérative. De ce fait, il peut y avoir de la dérive entre l'état déclaré et le réel (par exemple, un collègue qui serait passé sur la console pour changer un paramètre 😉).

❓❓ Alors, comment m'assurer que ce qui est commit dans mon repo git est vraiment appliqué. Comment être alerté s'il y a un changement par rapport à l'état désiré et comment appliquer automatiquement ce qui est dans mon code (GitOps) ?

C'est la promesse de tf-controller, un operateur Kubernetes Open Source de Weaveworks, étroitement lié à Flux (un moteur GitOps de la même société). Flux est l'une des solutions que je plébiscite, et je vous invite donc à lire un précédent article.

Info

L'ensemble des étapes décrites ci-dessous sont faites avec ce repo Git

🎯 Notre objectif

En suivant les étapes de cet article nous visons les objectifs suivant:

  • Déployer un cluster Kubernetes qui servira de "Control plane". Pour résumer il hébergera le controlleur Terraform qui nous permettra de déclarer tous les éléments d'infrastructure souhaités.
  • Utiliser Flux comme moteur GitOps pour toutes les ressources Kubernetes.

Concernant le controleur Terraform, nous allons voir:

  • Quelle est le moyen de définir des dépendances entre modules
  • Création de plusieurs ressources AWS: Zone route53, Certificat ACM, réseau, cluster EKS.
  • Les différentes options de reconciliation (automatique, nécessitant une confirmation)
  • Comment sauvegarder et restaurer un fichier d'état (tfstate)

🛠️ Installer le controleur Terraform

☸ Le cluster "Control Plane"

Afin de pouvoir utiliser le controleur Kubernetes tf-controller, il nous faut d'abord un cluster Kubernetes 😆. Nous allons donc créer un cluster control plane en utilisant la ligne de commande terraform et les bonnes pratiques EKS.

Warning

Il est primordial que ce cluster soit résiliant, sécurisé et supervisé car il sera responsable de la gestion de l'ensemble des ressources AWS créées par la suite.

Sans entrer dans le détail, le cluster "control plane" a été créé un utilisant ce code. Celà-dit, il est important de noter que toutes les opérations de déploiement d'application se font en utilisant Flux.

Info

En suivant les instructions du README, un cluster EKS sera créé mais pas uniquement!
Il faut en effet donner les permissions au controlleur Terraform pour appliquer les changements d'infrastructure. De plus, Flux doit être installé et configuré afin d'appliquer la configuration définie ici.

Au final on se retrouve donc avec plusieurs éléments installés et configurés:

  • les addons quasi indispensables que sont aws-loadbalancer-controller et external-dns
  • les roles IRSA pour ces mêmes composants sont installés en utilisant tf-controller
  • La stack de supervision Prometheus / Grafana.
  • external-secrets pour pouvoir récupérer des éléments sensibles depuis AWS secretsmanager.
  • Afin de démontrer tout cela au bout de quelques minutes l'interface web pour Flux est accessible via l'URL gitops-<cluster_name>.<domain_name>

Vérifier toute de même que le cluster est accessible et que Flux fonctionne correctement

1aws eks update-kubeconfig --name controlplane-0 --alias controlplane-0
2Updated context controlplane-0 in /home/smana/.kube/config
 1flux check
 2...
 3✔ all checks passed
 4
 5flux get kustomizations
 6NAME                    REVISION                SUSPENDED       READY   MESSAGE
 7flux-config             main@sha1:e2cdaced      False           True    Applied revision: main@sha1:e2cdaced
 8flux-system             main@sha1:e2cdaced      False           True    Applied revision: main@sha1:e2cdaced
 9infrastructure          main@sha1:e2cdaced      False           True    Applied revision: main@sha1:e2cdaced
10security                main@sha1:e2cdaced      False           True    Applied revision: main@sha1:e2cdaced
11tf-controller           main@sha1:e2cdaced      False           True    Applied revision: main@sha1:e2cdaced
12...

📦 Le chart Helm et Flux

Maintenant que notre cluster "controlplane" est opérationnel, l'ajout le contrôleur Terraform consiste à utiliser le chart Helm.

Il faut tout d'abord déclarer la source:

source.yaml

1apiVersion: source.toolkit.fluxcd.io/v1beta2
2kind: HelmRepository
3metadata:
4  name: tf-controller
5spec:
6  interval: 30m
7  url: https://weaveworks.github.io/tf-controller

Et définir la HelmRelease:

release.yaml

 1apiVersion: helm.toolkit.fluxcd.io/v2beta1
 2kind: HelmRelease
 3metadata:
 4  name: tf-controller
 5spec:
 6  releaseName: tf-controller
 7  chart:
 8    spec:
 9      chart: tf-controller
10      sourceRef:
11        kind: HelmRepository
12        name: tf-controller
13        namespace: flux-system
14      version: "0.12.0"
15  interval: 10m0s
16  install:
17    remediation:
18      retries: 3
19  values:
20    resources:
21      limits:
22        memory: 1Gi
23      requests:
24        cpu: 200m
25        memory: 500Mi
26    runner:
27      serviceAccount:
28        annotations:
29          eks.amazonaws.com/role-arn: "arn:aws:iam::${aws_account_id}:role/tfcontroller_${cluster_name}"

Lorsque ce changement est écrit dans le repo Git, la HelmRelease sera déployée et le contrôlleur tf-controller démarera

1kubectl get hr -n flux-system
2NAME            AGE   READY   STATUS
3tf-controller   67m   True    Release reconciliation succeeded
4
5kubectl get po -n flux-system -l app.kubernetes.io/instance=tf-controller
6NAME                             READY   STATUS    RESTARTS   AGE
7tf-controller-7ffdc69b54-c2brg   1/1     Running   0          2m6s

Dans le repo de demo il y a déjà un certain nombre de ressources AWS déclarées. Par conséquent, au bout de quelques minutes, le cluster se charge de la création de celles-cis:

asciicast

Info

Bien que la majorité des tâches puisse être réalisée de manière déclarative ou via les utilitaires de ligne de commande tels que kubectl et flux, un autre outil existe qui offre la possibilité d'interagir avec les ressources terraform : tfctl

🚀 Appliquer un changement

Parmis les bonnes pratiques avec Terraform, il y a l'usage de modules.
Un module est un ensemble de ressources Terraform liées logigement afin d'obtenir une seule unité réutilisable. Cela permet d'abstraire la complexité, de prendre des entrées, effectuer des actions spécifiques et produire des sorties.

Il est possible de créer ses propres modules et de les mettre à disposition dans des Sources ou d'utiliser les nombreux modules partagés et maintenus par les communautés.
Il suffit alors d'indiquer quelques variables afin de l'adapter au contexte.

Avec tf-controller, la première étape consiste donc à indiquer la Source du module. Ici nous allons configurer le socle réseau sur AWS (vpc, subnets...) avec le module terraform-aws-vpc.

sources/terraform-aws-vpc.yaml

 1apiVersion: source.toolkit.fluxcd.io/v1
 2kind: GitRepository
 3metadata:
 4  name: terraform-aws-vpc
 5  namespace: flux-system
 6spec:
 7  interval: 30s
 8  ref:
 9    tag: v5.0.0
10  url: https://github.com/terraform-aws-modules/terraform-aws-vpc

Nous pouvons ensuite créer la ressource Terraform qui en fait usage:

vpc/dev.yaml

 1apiVersion: infra.contrib.fluxcd.io/v1alpha2
 2kind: Terraform
 3metadata:
 4  name: vpc-dev
 5spec:
 6  interval: 8m
 7  path: .
 8  destroyResourcesOnDeletion: true # You wouldn't do that on a prod env ;)
 9  storeReadablePlan: human
10  sourceRef:
11    kind: GitRepository
12    name: terraform-aws-vpc
13    namespace: flux-system
14  vars:
15    - name: name
16      value: vpc-dev
17    - name: cidr
18      value: "10.42.0.0/16"
19    - name: azs
20      value:
21        - "eu-west-3a"
22        - "eu-west-3b"
23        - "eu-west-3c"
24    - name: private_subnets
25      value:
26        - "10.42.0.0/19"
27        - "10.42.32.0/19"
28        - "10.42.64.0/19"
29    - name: public_subnets
30      value:
31        - "10.42.96.0/24"
32        - "10.42.97.0/24"
33        - "10.42.98.0/24"
34    - name: enable_nat_gateway
35      value: true
36    - name: single_nat_gateway
37      value: true
38    - name: private_subnet_tags
39      value:
40        "kubernetes.io/role/elb": 1
41        "karpenter.sh/discovery": dev
42    - name: public_subnet_tags
43      value:
44        "kubernetes.io/role/elb": 1
45  writeOutputsToSecret:
46    name: vpc-dev

Si l'on devait résumer grossièrement: le code terraform provenant de la source terraform-aws-vpc est utilisé avec les variables vars.

Il y a ensuite plusieurs paramètres qui influent sur le fonctionnement de tf-controller. Les principaux paramètres qui permettent de contrôler la façon dont sont appliquées les modifications sont .spec.approvePlan et .spec.autoApprove

🚨 Détection de la dérive

Définir spec.approvePlan avec une valeur à disable permet uniquement de notifier que l'état actuel des ressources a dérivé par rapport au code Terraform. Cela permet notamment de choisir le moment et la manière dont l'application des changements sera effectuée.

Note

De mon point de vue il manque une section sur les notifications: La dérive, les plans en attentes, les problèmese de réconcilation. J'essaye d'identifier les méthodes possibles (de préférence avec Prometheus) et de mettre à jour cet article dès que possible.

🔧 Application manuelle

L'exemple donné précédemment (vpc-dev) ne contient pas le paramètre .spec.approvePlan et hérite donc de la valeur par défaut qui est false. Par conséquent, l'application concrète des modifications (apply), n'est pas faite automatiquement.

Un plan est exécuté et sera en attente d'une validation:

1tfctl get
2NAMESPACE       NAME                            READY   MESSAGE                                                                                                                 PLAN PENDING    AGE
3...
4flux-system     vpc-dev                         Unknown Plan generated: set approvePlan: "plan-v5.0.0-26c38a66f12e7c6c93b6a2ba127ad68981a48671" to approve this plan.      true            2 minutes

Je conseille d'ailleurs de configurer le paramètre storeReadablePlan à human. Cela permet de visualiser simplement les modifications en attente en utilisant tfctl:

 1tfctl show plan vpc-dev
 2
 3Terraform used the selected providers to generate the following execution
 4plan. ressource actions are indicated with the following symbols:
 5  + create
 6
 7Terraform will perform the following actions:
 8
 9  # aws_default_network_acl.this[0] will be created
10  + ressource "aws_default_network_acl" "this" {
11      + arn                    = (known after apply)
12      + default_network_acl_id = (known after apply)
13      + id                     = (known after apply)
14      + owner_id               = (known after apply)
15      + tags                   = {
16          + "Name" = "vpc-dev-default"
17        }
18      + tags_all               = {
19          + "Name" = "vpc-dev-default"
20        }
21      + vpc_id                 = (known after apply)
22
23      + egress {
24          + action          = "allow"
25          + from_port       = 0
26          + ipv6_cidr_block = "::/0"
27          + protocol        = "-1"
28          + rule_no         = 101
29          + to_port         = 0
30        }
31      + egress {
32...
33Plan generated: set approvePlan: "plan-v5.0.0@sha1:26c38a66f12e7c6c93b6a2ba127ad68981a48671" to approve this plan.
34To set the field, you can also run:
35
36  tfctl approve vpc-dev -f filename.yaml

Après revue des modifications ci-dessus, il suffit donc d'ajouter l'identifiant du plan à valider et de pousser le changement sur git comme suit:

1apiVersion: infra.contrib.fluxcd.io/v1alpha2
2kind: Terraform
3metadata:
4  name: vpc-dev
5spec:
6...
7  approvePlan: plan-v5.0.0-26c38a66f1
8...

En quelques instants un runner sera lancé qui se chargera d'appliquer les changements:

1kubectl logs -f -n flux-system vpc-dev-tf-runner
22023/07/01 15:33:36 Starting the runner... version  sha
3...
4aws_vpc.this[0]: Creating...
5aws_vpc.this[0]: Still creating... [10s elapsed]
6...
7aws_route_table_association.private[1]: Creation complete after 0s [id=rtbassoc-01b7347a7e9960a13]
8aws_nat_gateway.this[0]: Still creating... [10s elapsed]

La réconciliation éffectuée, la ressource passe à l'état READY: True

1kubectl get tf -n flux-system vpc-dev
2NAME      READY   STATUS                                                                  AGE
3vpc-dev   True    Outputs written: v5.0.0@sha1:26c38a66f12e7c6c93b6a2ba127ad68981a48671   17m

🤖 Application automatique

Nous pouvons aussi activer la réconciliation automatique. Pour ce faire il faut déclarer le paramètre .spec.autoApprove à true.

Toutes les ressources IRSA sont configurées de la sorte:

external-secrets.yaml

 1piVersion: infra.contrib.fluxcd.io/v1alpha2
 2kind: Terraform
 3metadata:
 4  name: irsa-external-secrets
 5spec:
 6  approvePlan: auto
 7  destroyResourcesOnDeletion: true
 8  interval: 8m
 9  path: ./modules/iam-role-for-service-accounts-eks
10  sourceRef:
11    kind: GitRepository
12    name: terraform-aws-iam
13    namespace: flux-system
14  vars:
15    - name: role_name
16      value: ${cluster_name}-external-secrets
17    - name: attach_external_secrets_policy
18      value: true
19    - name: oidc_providers
20      value:
21        main:
22          provider_arn: ${oidc_provider_arn}
23          namespace_service_accounts: ["security:external-secrets"]

Donc si je fais le moindre changement sur la console AWS par exemple, celui-ci sera rapidement écrasé par celui géré par tf-controller.

Info

La politique de suppression d'une ressource Terraform est définie par le paramètre destroyResourcesOnDeletion. Par défaut elles sont conservées et il faut donc que ce paramètre ait pour valeur true afin de détruire les éléments crées lorsque l'objet Kubernetes est supprimé.

Ici nous voulons la possibilité de supprimer les rôles IRSA. Ils sont en effet étroitement liés aux clusters.

🔄 Entrées et sorties: dépendances entre modules

Lorsque qu'on utilise Terraform, on a souvent besoin de passer des données d'un module à l'autre. Généralement ce sont les outputs du module qui exportent ces informations. Il faut donc un moyen de les importer dans un autre module.

Reprenons encore l'exemple donné ci-dessus (vpc-dev). Nous notons en bas du YAML la directive suivante:

1...
2  writeOutputsToSecret:
3    name: vpc-dev

Lorsque cette ressource est appliquée nous aurons un message qui confirme que les outputs sont disponibles ("Outputs written"):

1kubectl get tf -n flux-system vpc-dev
2NAME      READY   STATUS                                                                  AGE
3vpc-dev   True    Outputs written: v5.0.0@sha1:26c38a66f12e7c6c93b6a2ba127ad68981a48671   17m

En effet ce module exporte de nombreuses informations (126):

1kubectl get secrets -n flux-system vpc-dev
2NAME      TYPE     DATA   AGE
3vpc-dev   Opaque   126    15s
4
5kubectl get secret -n flux-system vpc-dev --template='{{.data.vpc_id}}' | base64 -d
6vpc-0c06a6d153b8cc4db

Certains de ces éléments d'informations sont ensuite utilisés pour créer un cluster EKS de dev:

vpc/dev.yaml

1...
2  varsFrom:
3    - kind: Secret
4      name: vpc-dev
5      varsKeys:
6        - vpc_id
7        - private_subnets
8...

💾 Sauvegarder et restaurer un tfstate

Dans mon cas je ne souhaite pas recréer la zone et le certificat à chaque destruction du controlplane. Voici un exemple des étapes à mener pour que je puisse restaurer l'état de ces ressources lorsque j'utilise cette demo.

Note

Il s'agit là d'une procédure manuelle afin de démontrer le comportement de tf-controller par rapport aux fichiers d'état. Par défaut ces tfstates sont stockés dans des secrets mais on préferera configurer un backend GCS ou S3

La création initiale de l'environnement de démo m'a permis de sauvegarder les fichiers d'état (tfstate) de cette façon.

1WORKSPACE="default"
2STACK="route53-cloud-hostedzone"
3BACKUPDIR="${HOME}/tf-controller-backup"
4
5mkdir -p ${BACKUPDIR}
6
7kubectl get secrets -n flux-system tfstate-${WORKSPACE}-${STACK} -o jsonpath='{.data.tfstate}' | \
8base64 -d | gzip -d > ${BACKUPDIR}/${WORKSPACE}-${STACK}.tfstate

Lorsque le cluster est créé à nouveau, tf-controller essaye de créer la zone car le fichier d'état est vide.

 1tfctl get
 2NAMESPACE       NAME                            READY   MESSAGE                                                                                                                 PLAN PENDING    AGE
 3...
 4flux-system     route53-cloud-hostedzone        Unknown Plan generated: set approvePlan: "plan-main@sha1:345394fb4a82b9b258014332ddd556dde87f73ab" to approve this plan.        true            16 minutes
 5
 6tfctl show plan route53-cloud-hostedzone
 7
 8Terraform used the selected providers to generate the following execution
 9plan. resource actions are indicated with the following symbols:
10  + create
11
12Terraform will perform the following actions:
13
14  # aws_route53_zone.this will be created
15  + resource "aws_route53_zone" "this" {
16      + arn                 = (known after apply)
17      + comment             = "Experimentations for blog.ogenki.io"
18      + force_destroy       = false
19      + id                  = (known after apply)
20      + name                = "cloud.ogenki.io"
21      + name_servers        = (known after apply)
22      + primary_name_server = (known after apply)
23      + tags                = {
24          + "Name" = "cloud.ogenki.io"
25        }
26      + tags_all            = {
27          + "Name" = "cloud.ogenki.io"
28        }
29      + zone_id             = (known after apply)
30    }
31
32Plan: 1 to add, 0 to change, 0 to destroy.
33
34Changes to Outputs:
35  + domain_name = "cloud.ogenki.io"
36  + nameservers = (known after apply)
37  + zone_arn    = (known after apply)
38  + zone_id     = (known after apply)
39
40Plan generated: set approvePlan: "plan-main@345394fb4a82b9b258014332ddd556dde87f73ab" to approve this plan.
41To set the field, you can also run:
42
43  tfctl approve route53-cloud-hostedzone -f filename.yaml

La procédure de restauration consiste donc à créer le secret à nouveau:

 1gzip ${BACKUPDIR}/${WORKSPACE}-${STACK}.tfstate
 2
 3cat <<EOF | kubectl apply -f -
 4apiVersion: v1
 5kind: Secret
 6metadata:
 7  name: tfstate-${WORKSPACE}-${STACK}
 8  namespace: flux-system
 9  annotations:
10    encoding: gzip
11type: Opaque
12data:
13  tfstate: $(cat ${BACKUPDIR}/${WORKSPACE}-${STACK}.tfstate.gz | base64 -w 0)
14EOF

Il faudra aussi relancer un plan de façon explicite pour mettre à jour l'état de la ressource en question

1tfctl replan route53-cloud-hostedzone
2 Replan requested for flux-system/route53-cloud-hostedzone
3Error: timed out waiting for the condition

Nous pouvons alors vérifier que le fichier d'état a bien été mis à jour

1tfctl get
2NAMESPACE       NAME                            READY   MESSAGE                                                                                                                 PLAN PENDING    AGE
3flux-system     route53-cloud-hostedzone        True    Outputs written: main@sha1:d0934f979d832feb870a8741ec01a927e9ee6644                                                     false           19 minutes

🔍 Focus sur certaines fonctionnalités de Flux

Oui j'ai un peu menti sur l'agenda 😝. Il me semblait nécessaire de mettre en lumière 2 fonctionnalités que je n'avais pas exploité jusque là et qui sont fort utiles!

Substition de variables

Lorsque Flux est initiliasé un certain nombre de Kustomization spécifique à ce cluster sont créés. Il est possible d'y indiquer des variables de substitution qui pourront être utilisées dans l'ensemble des ressources déployées par cette Kustomization. Cela permet d'éviter un maximum la déduplication de code.

J'ai découvert l'efficacité de cette fonctionnalité très récemment. Je vais décrire ici la façon dont je l'utilise:

Le code terraform qui crée un cluster EKS, génère aussi une ConfigMap qui contient les variables propres au cluster. On y retrouvera, bien sûr, le nom du cluster, mais aussi tous les paramètres qui varient entre les clusters et qui sont utilisés dans les manifests Kubernetes.

flux.tf

 1resource "kubernetes_config_map" "flux_clusters_vars" {
 2  metadata {
 3    name      = "eks-${var.cluster_name}-vars"
 4    namespace = "flux-system"
 5  }
 6
 7  data = {
 8    cluster_name      = var.cluster_name
 9    oidc_provider_arn = module.eks.oidc_provider_arn
10    aws_account_id    = data.aws_caller_identity.this.account_id
11    region            = var.region
12    environment       = var.env
13    vpc_id            = module.vpc.vpc_id
14  }
15  depends_on = [flux_bootstrap_git.this]
16}

Comme spécifié précedemment, les variables de substition sont définies dans les Kustomization. Prenons un exemple concret. Ci-dessous on définie la Kustomization qui déploie toutes les ressources qui sont consommées par tf-controller
On déclare ici la ConfigMap eks-controlplane-0-vars qui avait été généré à la création du cluster EKS.

infrastructure.yaml

 1apiVersion: kustomize.toolkit.fluxcd.io/v1
 2kind: Kustomization
 3metadata:
 4  name: tf-custom-resources
 5  namespace: flux-system
 6spec:
 7  prune: true
 8  interval: 4m0s
 9  path: ./infrastructure/controlplane-0/terraform/custom-resources
10  postBuild:
11    substitute:
12      domain_name: "cloud.ogenki.io"
13    substituteFrom:
14      - kind: ConfigMap
15        name: eks-controlplane-0-vars
16      - kind: Secret
17        name: eks-controlplane-0-vars
18        optional: true
19  sourceRef:
20    kind: GitRepository
21    name: flux-system
22  dependsOn:
23    - name: tf-controller

Enfin voici un exemple de ressource Kubernetes qui en fait usage. Cet unique manifest peut être utilisé par tous les clusters!.

external-dns/helmrelease.yaml

 1apiVersion: helm.toolkit.fluxcd.io/v2beta1
 2kind: HelmRelease
 3metadata:
 4  name: external-dns
 5spec:
 6...
 7  values:
 8    global:
 9      imageRegistry: public.ecr.aws
10    fullnameOverride: external-dns
11    aws:
12      region: ${region}
13      zoneType: "public"
14      batchChangeSize: 1000
15    domainFilters: ["${domain_name}"]
16    logFormat: json
17    txtOwnerId: "${cluster_name}"
18    serviceAccount:
19      annotations:
20        eks.amazonaws.com/role-arn: "arn:aws:iam::${aws_account_id}:role/${cluster_name}-external-dns"

Cela élimine totalement les overlays qui consistaient à ajouter les paramètres spécifiques au cluster.

Web UI (Weave GitOps)

Dans mon précédent article sur Flux, je mentionnais le fait que l'un des inconvénients (si l'on compare avec son principale concurrent: ArgoCD) est le manque d'une interface Web. Bien que je sois un adepte de la ligne de commande, c'est parfois bien utile d'avoir une vue synthétique et de pouvoir effectuer certaines opération en quelques clicks 🖱️

C'est désormais possible avec Weave Gitops! Bien entendu ce n'est pas comparable avec l'UI d'ArgoCD, mais l'essentiel est là: Mettre en pause la réconcilation, visualiser les manifests, les dépendances, les événements...

Weave-Gitops

Il existe aussi le plugin VSCode comme alternative.

💭 Remarques

Et voilà, nous arrivons au bout de notre exploration de cet autre outil de gestion d'infrastructure sur Kubernetes. Malgré quelques petits soucis rencontrés en cours de route, que j'ai partagé sur le repo Git du projet, l'expérience m'a beaucoup plu. tf-controller offre une réponse concrète à une question fréquente : comment gérer notre infra comme on gère notre code ?

J'aime beaucoup l'approche GitOps appliquée à l'infrastructure, j'avais d'ailleurs écrit un article sur Crossplane. tf-controller aborde la problématique sous un angle différent: utiliser du Terraform directement. Cela signifie qu'on peut utiliser nos connaissances actuelles et notre code existant. Pas besoin d'apprendre une nouvelle façon de déclarer nos ressources.
C'est un critère à prendre en compte car migrer vers un nouvel outil lorsque l'on a un existant représente un éffort non négligeable. Cependant j'ajouterais aussi que tf-controller s'adresse aux utilisateurs de Flux uniquement et, de ce fait, restreint le publique cible.

Aujourd'hui, j'utilise une combinaison de Terraform, Terragrunt et RunAtlantis. tf-controller pourrait devenir une alternative viable: Nous avons en effet évoqué l'intérêt de Kustomize associé aux substitions de variables pour la factorisation de code. Dans la roadmap du projet il y a aussi l'objectif d'afficher les plans dans les pull-requests. Autre problématique fréquente: la nécessité de passer des éléments sensibles aux modules. En utilisant une ressource Terraform, on peut injecter des variables depuis des secrets Kubernetes. Ce qui permet d'utiliser certains outils, tels que external-secrets, sealed-secrets ...

Je vous encourage donc à essayer tf-controller vous-même, et peut-être même d'y apporter votre contribution 🙂.

Note
  • La démo que j'ai faite ici utilise pas mal de ressources, dont certaines assez cruciales (comme le réseau). Donc, gardez en tête que c'est juste pour la démo ! Je suggère une approche progressive si vous envisagez de le mettre en ouvre: commencez par utiliser la détection de dérives, puis créez des ressources simples.
  • J'ai aussi pris quelques raccourcis en terme de sécurité à éviter absolument, notamment le fait de donner les droits admin au contrôleur.

Traductions: