Trunk Based Development, Kubernetes y GCP: Un pipeline con la ayuda de Gitlab, Kaniko, Skaffold y Terraform
Esta es la segunda entrega del articulo k8s desde dev hasta prod con Gitlab, Skaffold, Kustomize y Kaniko donde arme el skeleton base para esta segunda entrega, al igual que el anterior al final del artículo estará el enlace al repositorio con todo el código
Premisa
Ya tenemos un pipeline que cumple con las siguientes pasos:
Test
test suites ejecutadas porPHPUnit
Build
conKaniko
construimos las imágenes, las tagueamos y las enviamos a nuestro registro de imágenes (utilizaremos el mismo de gitlab en este caso),Kaniko
se encarga de guardar una capa de cache en el mismo registro.Deploy
conSkaffold
se construyen dinámicamente los manifiestos junto con las imágenes construidas en el paso anterior generando un manifiesto final que es aplicado en el clusterDestroy
con Skaffold se puede también eliminar del manifiesto aplicado actualmente.
Lo que queremos ahora es, eliminar un stage Destroy
, y agregar un par de pasos más para aprovisionamiento de infraestructura con Terraform
, el ciclo quedaría más o menos así:
Vamos a ello
Como esta es la continuación de la primera parte , no los voy a aburrir en la creación del pipeline, sino que nos enfocaremos en la mejora del del mismo y de los archivos de instrucciones de Terraform
.
Estructura de Archivos
Vamos a crear una nueva carpeta infrastructure
y una sub carpeta terraform
a nivel del root dir, donde irán colocados todos los archivos de terraform
Luego de esto vamos a crear los archivos declarativos para aprovisionar un cluster en nuestra infraestructura (en mi caso estoy utilizando Google Cloud
). La idea principal es que adicionalmente a los clusters permanentes (staging y producción)
estos archivos de terraform se encarguen de crear cluster temporales
que permitan que los equipos cuenten dinámicamente con un cluster para probar sus features y que este mismo cluster temporal pueda eliminarse una vez se haga el merge de esa branch a la rama main del repositorio.
Para esto vamos a crear los archivos de terraform
y aparte vamos a colocar un webhook
en GitLab
al momento del merge request para eliminar dicho cluster temporal y el environment temporal de gitlab.
Archivos de Skaffold y Kustomize para ambientes review
En un ínterin, como estamos utilizando skaffold
para los ciclos de CI/CD, vamos a crear rápidamente un nuevo profile llamado review
para nuestras aplicaciones de ambientes temporales/dinámicos para el manejo de los manifiestos de kubernetes
para esto podemos hacer una copiar del folder staging
que esta dentro de deployments/k8s/environments
teniendo en cuenta cambiar los keys de staging
a review
Vamos con Terraform
En lugar de hacer el típico archivo main.tf
en este caso y para mejor visualización vamos a separarlos por su función.
Empecemos con el 0-provider.tf
donde, como su nombre lo indica, vamos a generar las instrucciones para que terraform
sepa con que proveedor de servicios en la nube estamos trabajando (en nuestro caso Google Cloud
), previamente necesitarás haber creado previamente tus credenciales de service account para que puedas comunicarte con tu proveedor (GCP -> IAM & Admin > Service Accounts, y luego hacer click en Create Service Account.)
Los valores var.*
serán definidos mas adelante en un archivo de terraform
conocido como tfvars
esto nos permitirá en ese archivo escribir las variables dinámicas antes de inicializar el plan y aplicarlo.
# Google Cloud Platform Provider
# https://registry.terraform.io/providers/hashicorp/google/latest/docs
provider "google" {
credentials = file("../configs/service-account.json")
project = var.project_id
region = var.region
}
terraform {
required_providers {
google = {
source = "hashicorp/google"
version = "3.5.0"
}
}
}
En tu consola para ir probando localmente puedes ejecutar el siguiente comando para inicializar terraform
, esto descargara las primeras dependencias y las instalará en un folder .terraform
del mismo directorio de trabajo y creará un archivo .lock.hcl
que, como buena practica, deberías guardarlo en tu repo.
$: terraform init
Adicionalmente a esto vamos a crear un archivo 1-cluster.tf
para definir las instrucciones de terraform
para crear un cluster en nuestra infraestructura de Google Cloud Platform
junto con la instrucción para crear una VPC para este cluster.
resource "google_compute_network" "vpc_network" {
name = var.cluster_network
}
resource "google_container_cluster" "gke-cluster" {
name = var.cluster_name
network = var.cluster_network
location = var.region
initial_node_count = 1
}
Luego estarán los dos últimos archivos, vars.tf
y terraform.tfvars
dónde estarán las variables globales y sus valores y placeholders para que sean accesibles para terraform
.
variable "project_id" {
type = string
}
variable "region" {
type = string
}
variable "cluster_name" {
type = string
}
variable "network_name" {
type = string
}
project_id = "YOUR_GCP_PROJECT_ID"
region = "us-central-1c"
cluster_name = "PLACEHOLDER_CLUSTER"
network_name = "PLACEHOLDER_NETWORK"
Luego de esto nos queda completar nuestro pipeline de GitLab
con los jobs
que debemos sumar para terraform
(incluye 3 validate, init, plan y apply) y para el deploy
del ambiente dinámico tomando en cuenta que solo debe crear dichos jobs
si el branch es un feature branch
y que no sea la rama main
.
Si ves en detalle, hemos creado una variable de CI/CD de GitLab
que contiene las credenciales de la service-account
( SERVICE_ACCOUNT_GCP) de Google Cloud para que pueda estar accesible para el pipeline y pasarla a los jobs de terraform en forma de archivo json.
.terraform:
image:
name: hashicorp/terraform:1.0.5
entrypoint:
- '/usr/bin/env'
- 'PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin'
before_script:
- cd infrastructure && mkdir -p configs && cd configs
- echo $SERVICE_ACCOUNT_GCP | base64 -d > service-account.json
- cd ../terraform
- sed -i "s/\PLACEHOLDER_CLUSTER/$CI_COMMIT_REF_SLUG" terraform.tfvars
- sed -i "s/\PLACEHOLDER_NETWORK/$CI_COMMIT_REF_SLUG" terraform.tfvars
- terraform init
cache:
key: terraform-cache
paths:
- .terraform
rules:
- if: '$CI_COMMIT_BRANCH =~ /feature/'
- if: '$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
validate:
extends: .terraform
stage: provision
script:
- terraform validate
plan:
extends: .terraform
stage: provision
script:
- terraform plan -out plan.tfplan
needs:
- validate
artifacts:
paths:
- plan.tfplan
apply:
extends: .terraform
stage: provision
script:
- terraform apply -input=false plan.tfplan
needs:
- plan
rules:
- if: '$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
when: manual
apply:
extends: .terraform
stage: provision
script:
- cp $CI_PROJECT_DIR/planfile ./
- terraform apply -input=false "planfile"
needs:
- plan
rules:
- if: '$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
when: manual
deploy:feature:
extends: .skaffold
stage: deploy
environment:
name: "$CI_COMMIT_REF_NAME"
url: $CI_COMMIT_SHORT_SHA.features.equatonlabs.io
needs:
- build:api
script:
- skaffold deploy -f deployment/skaffold.yaml -p feature -a latest-build.json --status-check=true
rules:
- if: '$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
when: manual
destroy:feature:
extends: .terraform
stage: destroy
environment:
name: $CI_COMMIT_REF_NAME
url: $CI_COMMIT_SHORT_SHA.features.equatonlabs.io
action: stop
script:
- terraform destroy
rules:
- if: $CI_MERGE_REQUEST_APPROVED
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
Ahora juntemos todo
Una vez tengamos todos nuestros archivos en lugar es momento de hacer nuestro commit & push
al repositorio y seguir la ejecución del pipeline de GitLab
y sus salidas para asegurarnos que esta todo en orden.
Tendríamos este escenario:
- Tener 2 nuevos stages
provision
ydeploy
cuando la rama comienza confeature/
- Dinámicamente nuestro
cluster
y lavpc
tendrán el nombre de la variable runtimeCI_COMMIT_REF_SLUG
(ver mas acá ) - En
GitLab
se creara unenvironment
dinámico basado en la variableCI_COMMIT_REF_NAME
- Una vez aprobado el
MR
a la rama main, se dispara unjob
que elimina el cluster temporal que se utilizo para desarrollar y probar lafeature
y así no incurrir en costos innecesarios. - Una vez aprobado el MR a la rama
main
se eliminará también el environment creado enGitLab
- En la rama
main
no debe aparecer el stage deprovision
ya que esos cluster no son permanentes en el tiempo y se manejan fuera de estepipeline
.
Pipeline en Features
Environment dinámico y deploy dashboard
Google Cloud con el Cluster Creado
Ahora nos queda probar, para completar el circuito, que al aprobar el MR
, se ejecute el paso de destroy:feature
que elimine el cluster y el environment de el deploy dashboard de GitLab
Pipeline con el MR aprobado
Y esto es todo por ahora amigos, como siempre compartan, que compartir hace bien. Les dejo más abajo el repo con todos los files que vimos hoy para que los puedan ojear mas a detalles.