Habt ihr gewusst, dass Gitlab als Terraform remote Backend verwendet werden kann? Ich jedenfalls bis vor kurzem nicht. Wir versuchen bei uns grundsätzlich alles als Infrastructure as code zu beschreiben. Dabei setzen wir in der Regel auf Ansible. Da es aber für Rancher einen sehr ausführlichen Terraform Provider gibt, bietet sich die Kombination aus Gitlab und Terraform bestens an.

Infrastructure as code (IaC) is the process of managing and provisioning computer data centers through machine-readable definition files, rather than physical hardware configuration or interactive configuration tools.
Wittig, Andreas; Wittig, Michael (2016). Amazon Web Services in Action. Manning Press. p. 93. ISBN 978-1-61729-288-0.
Unsere Puzzle interne Rancher Installation wird mehrheitlich mit Hilfe von Ansible erstellt. Das beinhaltet die Provisionierung der benötigten VMs bei unserem IaaS Provider cloudscale.ch, der Installation von Rancher mit RKE und Helm sowie dem anschliessenden Hinzufügen eines Kubernetes Cluster basierend auf custom Nodes. Diverse weitere post-Installation Tasks sind zu diesem Zeitpunkt aber noch nicht vollständig automatisiert. Dazu gehört z.B. die Konfiguration des Authentication Provider für Rancher wie aber auch das Hinzufügen von diversen Applikationen wie z.B. der Nginx Ingress Controller oder Loki/Promtail/Grafana als Logging Stack.
Rancher Terraform Provider
Mit dem Rancher Terraform Provider lassen sich viele dieser Tasks sehr einfach automatisieren. Die Konfiguration des Rancher Terraform Provider ist mit wenigen Zeilen HCL Code erledigt:
provider "rancher2" { api_url = var.rancher2_api_url access_key = var.rancher2_access_key secret_key = var.rancher2_secret_key }
Der Rancher Terraform Provider stellt viele Terraform Ressourcen zur Verfügung um ganz unterschiedliche Bereiche von Rancher zu konfigurieren. Ein Beispiel dafür ist der Authentication Providers. Mit einigen weiteren Zeilen HCL Code und der rancher2_auth_config_keycloak Ressourcen können wir Keycloak als Authentication Backend verwenden:
resource "rancher2_auth_config_keycloak" "keycloak" { display_name_field = "displayName" groups_field = "memberOf" idp_metadata_content = file("${path.module}/resources/keycloak-metadata.xml") rancher_api_host = "<rancherapiurl>" sp_cert = file("${path.module}/resources/keycloak.cert") sp_key = data.vault_generic_secret.keycloak-cert.data["keycloak.key"] uid_field = "uuid" user_name_field = "uid" access_mode = "restricted" }
Ein weiteres Beispiel ist die rancher2_app Ressource. Damit kann, wie der Name schon vermuten lässt, eine Rancher App verwaltet werden. Am Beispiel unseres NGINX Ingress Controller sieht das so aus:
resource "rancher2_app" "nginx-ingress-public" { catalog_name = "helm" name = "nginx-ingress" project_id = data.rancher2_project.k8s-staging-cluster-infra.id template_name = "nginx-ingress" template_version = "1.41.1" target_namespace = "nginx-ingress-public" values_yaml = filebase64("${path.module}/values_yaml/infra-nginx-ingress-public.yaml") }
Die Helm Values für diese Rancher App werden als YAML File im Repository abgelegt. Ein Upgrade des Ingress Controller geschieht durch das Anpassen der Template Version oder der Values.
Gitlab CI/CD und Terraform Remote Backend
Damit Terraform Gitlab als remote Backend verwenden kann, muss dieses entsprechend konfiguriert werden. Durch das remote Backend, wird der Terraform State direkt im entsprechenden Gitlab Repository gespeichert und kann somit von überall verwendet werden. Die Initialisierung von Terraform sieht in etwa so aus:
terraform init \ -backend-config="address=https://gitlabhost/api/v4/projects/projectid/terraform/state/projectname" \ -backend-config="lock_address=https://gitlabhost/api/v4/projects/projectid/terraform/state/projectname/lock" \ -backend-config="unlock_address=https://gitlabhost/api/v4/projects/projectid/terraform/state/projectname/lock" \ -backend-config="username=apiuser" \ -backend-config="password=apitoken" \ -backend-config="lock_method=POST" \ -backend-config="unlock_method=DELETE" \ -backend-config="retry_wait_min=5
In einer Gitlab CI/CD Pipeline wird dann unseren Infrastructure as Code angewendet. Unser .gitlab-ci.yml
dazu sieht wie folgt aus:
image: name: hashicorp/terraform:light entrypoint: - '/usr/bin/env' - 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin' variables: GITLAB_TF_ADDRESS: ${CI_API_V4_URL}/projects/${CI_PROJECT_ID}/terraform/state/${CI_PROJECT_NAME} TF_ROOT: ${CI_PROJECT_DIR} PLAN: plan.tfplan PLAN_JSON: tfplan.json cache: paths: - .terraform before_script: - apk --no-cache add jq - alias convert_report="jq -r '([.resource_changes[]?.change.actions?]|flatten)|{\"create\":(map(select(.==\"create\"))|length),\"update\":(map(select(.==\"update\"))|length),\"delete\":(map(select(.==\"delete\"))|length)}'" - cd ${TF_ROOT} - terraform --version - terraform init -backend-config="address=${GITLAB_TF_ADDRESS}" -backend-config="lock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="unlock_address=${GITLAB_TF_ADDRESS}/lock" -backend-config="username=${GITLAB_USER_LOGIN}" -backend-config="password=${GITLAB_TF_PASSWORD}" -backend-config="lock_method=POST" -backend-config="unlock_method=DELETE" -backend-config="retry_wait_min=5" stages: - validate - build - test - deploy validate: stage: validate tags: - docker_cloudscale script: - terraform validate plan: stage: build tags: - docker_cloudscale script: - terraform plan -out=$PLAN - terraform show --json $PLAN | convert_report > $PLAN_JSON artifacts: name: plan paths: - $PLAN reports: terraform: $PLAN_JSON apply: stage: deploy tags: - docker_cloudscale environment: name: k8s-prod-cluster script: - terraform apply -auto-approve dependencies: - plan when: manual only: - master
Nach Erstellen eines Merge Request in Gitlab, wird automatisch ein terraform validate
und terraform plan
ausgeführt. Der Report daraus kann Gitlab direkt im Merge Request auswerten und anzeigen. Das sieht dann in etwa so aus:
Das komplette Log wird als Artifact dem Merge Request hinterlegt und kann natürlich auch geöffnet werden. Sobald der Merge Request gemerged wird, läuft automatisch ein terraform apply
um die entsprechende Änderung auch anzuwenden.