Wie mit Terraform und Gitlab Rancher konfiguriert werden kann
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.
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.ymldazu 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.