Son numerosas la serie de ventajas que aporta el cloud computing a DevOps. Incluyendo la posibilidad de crear tanto entornos de prueba como entornos productivos, de manera automatizada y en poco tiempo. Sin embargo, uno de sus inconvenientes es el costo que tiene por su uso. Este costo económico suele implicarse incluso cuando no se está haciendo uso del entorno “levantado” en cloud. Dicho esto, sería una idea atractiva poder provisionar estos entornos únicamente cuando se requiera hacer uso del entorno y así ahorrar costes innecesarios, ¿no?
Desde Datadope hemos dado respuesta al anterior planteamiento, mediante el uso de Jenkins y Terraform. Entrando en detalle, disponemos de varios entornos en GCP (Google Cloud Platform) destinados a realizar pruebas internas y otros fines. En dichos entornos se utilizan diferentes servicios de GCP como GKE (Google Kubernetes Engine), Cloud SQL, Memorystore… Son entornos que suponen un costo económico durante el tiempo que están levantados, por lo que supone un ahorro mantener dichos entornos en pié únicamente cuando sea necesario.
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, si no 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
Ahora se podría empezar a desplegar objetos de kubernetes a través de ArgoCD. Es interesante desplegar solo las aplicaciones de Argo que apunten a las apps de las que se desee hacer uso en el momento en el que se requiere hacer uso de GKE. Ahorrando así costes de recursos innecesarios que no se van a utilizar. Para poder hacer esto, se parametriza el despliegue a través de parámetros booleanos 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:
Así, conseguimos automatizar el despliegue al completo de GKE, como del propio cluster como del despliegue de objetos. Además de poder seleccionar las aplicaciones que estarán corriendo:
En cuanto se deje de trabajar con el cluster, no será necesario que este siga levantado ya que supondría un costo innecesario. Por tanto, se elimina el cluster con otro job haciendo uso de Terraform:
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 inconveniente de esto, es que se pierde la persistencia de los datos. No obstante, existen alternativas como recurrir a almacenamiento externo a GKE, como buckets de GCS. En definitiva, esta dinámica es ideal para casos en los que solo se necesita el entorno levantado en momentos puntuales ya que permitirá ahorrar costes y de manera automatizada.