El cloud computing ha revolucionado la manera en que los equipos DevOps gestionan sus entornos. Una de sus mayores ventajas es la capacidad de crear entornos de desarrollo, pruebas y producción de forma rápida, automatizada y escalable. Sin embargo, este modelo también implica costes, incluso cuando los entornos no están en uso.
En Datadope, hemos solucionado este desafío implementando una arquitectura que permite provisionar entornos de GKE bajo demanda (on-demand). Utilizando herramientas como Jenkins, Terraform, ArgoCD y Kustomize, logramos optimizar el uso de infraestructura, reducir costes y mantener un alto grado de automatización.
¿Por Qué Aprovisionar GKE Bajo Demanda?
El aprovisionamiento de los entornos se realiza de forma completamente automatizada e incluso con posibilidad de ser programada (con un cron-job) a través de Jenkins. En este artículo vamos a tratar el ejemplo de automatizar el aprovisionamiento de un cluster de GKE y su destrucción automatizada mediante un job de Jenkins. Pero no solo eso, sino que también se trata en este artículo como poder desplegar tanto aplicaciones como entornos enteros en GKE a elección de manera automatizada.
Se hace posible el aprovisionamiento de GKE on-demand tanto del cluster como de sus aplicaciones con Jenkins, Terraform, ArgoCD y Kustomize:
- Jenkins es un servidor open source de automatización, permitiendo automatizar toda clase de tareas relacionadas con el building, testing y CI/CD. En nuestro caso, Jenkins será el ejecutor de cada uno de los distintos pasos.
- Terraform es una herramienta de IaC (Infrastructure as Code) que entre otras funcionalidades, nos permite definir en código la infraestructura que se requiere desplegar. Esto Terraform lo hace posible interactuando con la API de los distintos proveedores cloud, llamados providers en Terraform. En nuestro ejemplo, se utiliza Terraform para aprovisionar el cluster de GKE.
- ArgoCD es una herramienta de Continuous Deployment a través de GitOps para K8s. En nuestro caso se utiliza ArgoCD para desplegar las aplicaciones en GKE.
- Kustomize es una herramienta de transformación de configuración de K8s, que nos permite personalizar archivos YAML sin plantillas y dejar los archivos originales sin modificar. En nuestro caso, se utiliza Kustomize con ArgoCD para poder desplegar a elección el conjunto de objetos que conforman la aplicación o entorno.


Entrando en detalle sobre el job de creación, en la primera stage de Jenkins se hace uso de Terraform para aprovisionar la infraestructura, en este caso, el cluster de GKE. Lo hace aplicando los ficheros de terraform que detallan la configuración del clúster. Para que pueda interaccionar con GCP, necesita una serviceaccount que tenga permisos de IAM pertinentes sobre los servicios correspondientes.
En nuestro ejemplo, se necesitan permisos sobre GKE, por lo que la service account que se utiliza tiene permisos de ‘Administrador de Kubernetes Engine’. En el final del stage se obtiene el context del cluster para poder interactuar con el cluster de GKE en los siguientes stages del job:
stage('Terraform GKE Cluster') {
withCredentials(
[file(credentialsId: 'foo',
variable: 'GOOGLE_APPLICATION_CREDENTIALS')]
) {
// Create cluster 1
sh 'terraform init -backend-config="prefix=terraform/state-dev-cluster-1" -reconfigure -no-color'
sh 'terraform apply -var gke_cluster_name="dev-cluster-1" -auto-approve -no-color'
// Get GKE cluster contexts
tokenC1 = readFile(file: 'token-dev-cluster-1')
enpointC1 = readFile(file: 'endpoint-dev-cluster-1')
ctxC1 = "--certificate-authority=ca-dev-cluster-1.pem --server=https://${enpointC1} --token=${tokenC1}"
}
}
Una vez teniendo el cluster de GKE “up&running” el siguiente paso será aprovisionar de herramientas o utilidades que sean necesarias, como es el caso de los CRDs (Custom Resources Definitions).
En este caso se comienza por el CRD de sealed secrets tratado en este blog anteriormente:
stage('Install k8s Sealed-secrets') {
wrap([$class: "MaskPasswordsBuildWrapper",
varPasswordPairs: [[password: tokenC1]]
]) {
// Install pre-defined key and certificate
withCredentials(
[file(credentialsId: 'foo',
variable: 'K8S_SEALED_SECRETS_KEY')]
) {
sh "kubectl ${ctxC1} apply -f ${K8S_SEALED_SECRETS_KEY}"
}
// Install Sealed-secrets
sh "kubectl ${ctxC1} apply -f sealed-secrets-installer.yml"
}
}
Y el CRD de ArgoCD:
stage('Install ArgoCD') {
wrap([$class: "MaskPasswordsBuildWrapper",
varPasswordPairs: [[password: tokenC1]]
]) {
// Se aplica el NS de argoCD
sh "kubectl ${ctxC1} apply -f argocd-ns.yml"
// Se despliega el CRD de Argo
sh "kubectl ${ctxC1} apply -n argocd -f argocd-installer.yaml"
}
Una vez se cuenta con ArgoCD instalado en el cluster, entra en juego Kustomize. Utilizamos la estructura de directorios base-overlays de Kustomize.
Los directorios base contienen todo el stock de aplicaciones disponibles, en su interior se encuentran todos los objetos a desplegar, donde se detallan en un fichero llamado kustomization.yaml.
En overlays se indican los directorios base que se desean desplegar. Permitiendo así, utilizar ciertas aplicaciones concretas en función del entorno en las necesidades del mismo.
Se muestra un ejemplo de la estructura de folders base-overlays.
Se tiene el folder base/logstash que contiene un deployment, un configmap y un kustomization.yaml:

Este es el contenido del fichero base/logstash/kustomization.yaml:
apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization resources: - deployment.yaml - configmap.yaml
Y en el overlay de logstash se tiene el siguiente kustomization:
apiVersion: kustomize.config.k8s.io/v1beta1 kind: Kustomization bases: - ../../../base/logstash
ArgoCD despliega objetos de K8S acorde a lo indicado en sus apps. En dichas apps se indican detalles como el repositorio, el path del repositorio o el branch al que apuntar.
En este ejemplo se tienen las siguientes tres apps de ArgoCD que apuntan cada una a una overlay de un repo en el que hay deployments, entre otros objetos, de un Filebeat, un Logstash y un Elasticsearch:
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: filebeat
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
project: default
source:
path: overlays/filebeat
repoURL: https://gitfoo/repofoo
targetRevision: master
syncPolicy:
automated:
prune: true
selfHeal: true
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: logstash
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
project: default
source:
path: overlays/logstash
repoURL: https://gitfoo/repofoo
targetRevision: master
syncPolicy:
automated:
prune: true
selfHeal: true
---
apiVersion: argoproj.io/v1alpha1
kind: Application
metadata:
name: elasticsearch
namespace: argocd
spec:
destination:
server: https://kubernetes.default.svc
project: default
source:
path: overlays/elasticsearch
repoURL: https://gitfoo/repofoo/
targetRevision: master
syncPolicy:
automated:
prune: true
selfHeal: true
A partir de este punto, ya es posible desplegar objetos de Kubernetes mediante ArgoCD. Una práctica recomendada es desplegar únicamente las aplicaciones gestionadas por Argo que estén relacionadas con los servicios necesarios en el momento en que se requiera utilizar GKE.
Esto permite optimizar el uso de recursos y reducir costes al evitar mantener activos componentes que no se van a utilizar.
Para lograr este comportamiento selectivo, el despliegue se parametriza mediante variables booleanas definidas en el job de Jenkins.
stage('Filebeat') {
if(params.filebeat){
// Deploy filebeat
sh "kubectl ${ctxC1} apply -f filebeat-app.yml"
}
}
stage('Logstash') {
if(params.logstash){
// Deploy logstash
sh "kubectl ${ctxC1} apply -f logstash-app.yml"
}
}
stage('Elasticsearch') {
if(params.elasticsearch){
// Deploy elasticsearch
sh "kubectl ${ctxC1} apply -f elasticsearch-app.yml"
}
}
Quedando de la siguiente manera los parámetros booleanos en el job de Jenkins:



Y al ejecutar el job se puede seleccionar la app que se desee desplegar a elección:

De este modo, se consigue automatizar por completo el despliegue en GKE, abarcando tanto la creación del clúster como el despliegue de los objetos dentro de él. Además, se permite seleccionar de forma controlada qué aplicaciones estarán en ejecución, según las necesidades del momento.

Una vez finalizado el trabajo con el clúster, no es necesario mantenerlo activo, ya que esto generaría un coste innecesario. Por ello, el clúster se elimina mediante otro job que utiliza Terraform para su destrucción de forma automatizada.
stage('Destroy Terraform GKE Clusters') {
withCredentials([file(credentialsId: 'foo', variable: 'GOOGLE_APPLICATION_CREDENTIALS')]) {
// Destroy cluster
sh 'terraform init -backend-config="prefix=terraform/state-dev-cluster-1" -reconfigure -no-color'
sh 'terraform apply -auto-approve -no-color -destroy'
}
}

El principal inconveniente de este enfoque es la pérdida de persistencia de datos al eliminar el clúster. Sin embargo, existen alternativas para mitigar este problema, como el uso de almacenamiento externo a GKE, por ejemplo, mediante buckets de Google Cloud Storage (GCS). En resumen, esta dinámica resulta ideal para escenarios en los que el entorno solo se necesita en momentos puntuales, ya que permite reducir costes de forma automatizada y eficiente.