Blog de Obi Madu
Volver a todos los artículos
InfrastructureDevOpsProjects

Implementación del flujo de trabajo central de Terraform en colaboración, a través de Github Pull Requests, Actions, Bot, Environments y un Backend Remoto.

Implemente un flujo de trabajo colaborativo de Terraform utilizando GitHub Actions, pull requests y un backend remoto para equipos de producción.

Implementación del flujo de trabajo central de Terraform en colaboración, a través de Github Pull Requests, Actions, Bot, Environments y un Backend Remoto.

¡Hola! y ¡¡bienvenidos!! En este proyecto vamos a crear una infraestructura simple con Terraform mientras implementamos el flujo de trabajo central de Terraform (que consiste en escribir, planificar y aplicar) en colaboración para un entorno de producción. En pocas palabras, vamos a implementar infraestructura con Terraform de una manera que permita a un equipo de ingenieros introducir cambios en el sistema, revisarlos en detalle, aprobarlos o rechazarlos y (si se aprueban) implementarlos en producción.

El sistema que vamos a construir en este proyecto será adecuado para las operaciones de algunas organizaciones pequeñas y medianas (con o sin la posible adición de algunas cosas más de las que hablaremos al final del proyecto).

Para los fines de este proyecto, usted es un Ingeniero de Infraestructura encargado de crear la infraestructura inicial para una empresa mediana. Comenzaremos creando nuestra infraestructura desde cero, trabajando con control de versiones (Git) en todo momento, iniciando nuestro sistema a través de un Backend Local en nuestra estación de trabajo, migrándolo a un Backend Remoto para permitir la colaboración y creando scripts para automatizar todo el proceso de Integración Continua y Despliegue Continuo.

Nosotros estaremos:

  • Creando una infraestructura básica con Terraform en DigitalOcean y Cloudflare
  • Configurando un Backend Remoto de Google Cloud Storage para permitir la colaboración
  • Configurando el acceso a nuestro Backend GCS a través de Github Environments y flujos de trabajo de Actions
  • Creando scripts de flujo de trabajo de Actions para Inicializar, Validar, Previsualizar y Aplicar nuestros cambios de infraestructura.
  • Haciendo uso de Github Pull Requests y el Bot de Github para las Revisiones (y Aprobaciones o Rechazos) de los cambios de infraestructura propuestos.

Aquí está el repositorio del proyecto en Github: https://github.com/obiMadu/terraform-github-workflow

Vamos a sumergirnos directamente 🚀

1. Crear nuestra Infraestructura a través de Terraform

Infrastructure

Vamos a crear la infraestructura muy simple que se muestra en el diagrama anterior. Un servidor web simple que ejecuta Nginx en Digital Ocean con una dirección ipv4 y un DNS A record en una zona de Cloudflare que apunta un subdominio hacia él. Lograremos esto creando solo dos recursos de Terraform.

Podrías cambiar fácilmente Cloudflare por cualquier otro Servicio de Nombres de Dominio de tu elección. Si administras tus dominios a través de Amazon Route 53, por ejemplo, no necesitas cambiar a Cloudflare para este tutorial. Simplemente crea las configuraciones apropiadas de Terraform necesarias para mapear la dirección IPv4 de nuestra instancia de servidor a un nombre de dominio (tampoco tiene que ser un subdominio, puedes mapear un dominio Apex, como prefieras).

Como era de esperar (probablemente), también se deduce que no necesitas crear tu servidor en Digital Ocean, podrías implementarlo en AWS o en cualquier otro proveedor de tu elección, siempre que obtengamos un servidor con Nginx instalado y una dirección IPv4.

Para hacer las cosas más divertidas, incluso podrías aprovisionar un servidor base y usar una herramienta como Ansible para implementar Nginx o cualquier otra aplicación web en él. Podrías construir tu propia imagen personalizada con herramientas como Packer y usarla también. Tú decides. Este proyecto tiene como objetivo ser rápido y fácil de seguir.

1.1 crear providers.tf

A continuación, vamos a crear un archivo providers.tf para configurar nuestros diferentes proveedores. Las restricciones de versión del proveedor están establecidas a las más recientes en el momento de escribir este artículo.

terraform {
  required_providers {
    digitalocean = {
      source = "digitalocean/digitalocean"
      version = "2.34.1"
    }
    cloudflare = {
      source = "cloudflare/cloudflare"
      version = "4.24.0"
    }
  }
}

# Digital Ocean API credentials
provider "digitalocean" {
  token = var.do_token
}

provider "cloudflare" {
  api_token = var.cloudflare_api_token
}

Los anteriores son los argumentos mínimos absolutos requeridos por los diferentes proveedores para funcionar correctamente. Con este archivo creado, vamos a inicializar Git en el directorio de nuestro proyecto y hacer de este archivo nuestro primer commit mediante los siguientes comandos;

git init .
git add .
git commit -m "Initial commit"

Hemos convertido los valores de nuestros argumentos en variables para evitar codificar estos valores confidenciales en nuestros archivos de configuración. Recuerda también que puedes personalizar los mensajes de commit de Git como prefieras.

1.2 crear variables.tf

Dado que hemos asignado variables como valores a los argumentos en las configuraciones de nuestros proveedores, es hora de declarar esas variables. Lo hacemos en un archivo que llamaremos variables.tf, con el siguiente contenido.

variable "do_token" {
  type = string
}

variable "do_region" {
  type = string
}

variable "cloudflare_api_token" {
  type = string
}

variable cloudflare_zone_id" {
  type = string
}

Ahora podemos proceder a hacer un commit de este nuevo archivo a Git con un mensaje de commit adecuado.

1.3 Inicializar Terraform

Ahora que tenemos nuestra configuración base, es hora de ejecutar terraform init en el directorio de nuestro proyecto para inicializar Terraform.

Terraform Init

Su mensaje de éxito debería verse similar al anterior.

Ahora tenemos que hacer dos cosas más;

  • Agregar un archivo .gitignore adecuado para Terraform a nuestro proyecto
  • Proporcionar los valores para las diferentes variables que hemos configurado para que Terraform las use.

Podemos recuperar un archivo .gitignore adecuado para Terraform desde la siguiente dirección https://github.com/github/gitignore/blob/main/Terraform.gitignoretext

Ahora es el momento de seguir adelante y recuperar las diferentes claves API necesarias para nuestros proveedores desde sus respectivas plataformas. Necesitaremos;

  • un Token API con acceso de write de Digital Ocean, junto con un código corto de región (que especifica en qué región deseamos implementar nuestros recursos)
  • un Token API de Cloudflare, junto con el Zone ID para el dominio dentro del cual deseamos crear nuestro registro DNS.

No se cubrirá aquí cómo obtener exactamente estas credenciales (deberías poder adquirirlas).

Por simplicidad, puedes hacer uso de la región fra1 de DigitalOcean. Esto corresponde al Centro de Datos de Frankfurt.

Ahora llega el momento de proporcionar a Terraform los valores que hemos adquirido. Para ser directos, crearemos un archivo terraform.tfvars e introduciremos los valores de nuestras variables. Nuestro archivo debería verse como a continuación;

do_token = "value"
do_region = "fra1"

cloudflare_api_token = "value"
cloudflare_zone_id = "value"

RECUERDA: Nunca debes confirmar el archivo terraform.tfvars en Git. Este archivo contiene secretos de aplicación importantes que no deben compartirse con nadie. Si un actor de amenazas obtiene acceso a estos secretos, toda nuestra infraestructura corre el riesgo de verse comprometida. El único propósito de este archivo es para uso local. Emplearemos un método más seguro para proporcionar estos valores a Terraform cuando lleguemos al entorno CI/CD.

1.4 Crear los Recursos

Ahora es el momento de crear realmente los recursos para nuestra Infraestructura. Crearemos dos archivos: un archivo servers.tf y un archivo dns.tf.

  • El primer archivo, servers.tf, lo llenaremos con la configuración de recursos para crear nuestro servidor en DigitalOcean con la Imagen Oficial de Nginx de DigitalOcean. Así (estaremos creando un recurso digitalocean_droplet);
#create server with nginx image
resource "digitalocean_droplet" "server" {
  name     = "gate"
  size     = "s-1vcpu-1gb"
  image    = "nginx"
  region   = var.do_region
}
  • El segundo archivo, dns.tf, lo configuraremos así (estamos creando un recurso cloudflare_record);
# Add a record to the domain
resource "cloudflare_record" "server" {
  zone_id = var.cloudflare_zone_id
  name    = "server"
  value   = digitalocean_droplet.server.ipv4_address
  type    = "A"
  ttl     = 1
  proxied = true
}

Ahora guarda ambos archivos y confírmalos en Git con mensajes de commit adecuados.

En este punto, el árbol de su directorio debería verse exactamente de la siguiente manera;

Directory tree 1

2. Crear e Inicializar el Backend Remoto

Es hora de agregar un backend remoto a nuestro proyecto. Como soy un Google Cloud Engineer, trabajaré con un backend remoto gcs en este proyecto. Puedes cambiar esto a cualquier otro backend remoto de tu elección, como un backend s3.

Así que procedí a crear un Google Cloud Storage Bucket con el nombre terraform-github-workflow. Creé una cuenta de servicio bajo el mismo nombre y asigné al principal el permiso Storage Object Admin en el bucket gcs. Esto permitirá a la cuenta de servicio crear y administrar objetos en el bucket terraform-github-workflow. Luego procedí a crear una JSON service account key para el principal de la cuenta de servicio, la cual descargué y almacené en una ubicación segura en mi estación de trabajo local.

El paso final fue establecer la variable de entorno GOOGLE_APPLICATION_CREDENTIALS en la ruta absoluta de la clave JSON descargada en mi PC, así;

export GOOGLE_APPLICATION_CREDENTIALS=/path/to/json/key/file

Para ser seguro, siempre podrías crear una diferente service account key para usar cuando lleguemos al entorno CI/CD.

Finalmente, es hora de ejecutar terraform init para inicializar este nuevo backend. Dependiendo de tu backend elegido, deberías ver un mensaje de éxito muy similar al de abajo;

Initialize Remote Backend

3. Crear flujos de trabajo de Github Actions

Ahora que tenemos inicializado nuestro backend remoto de Terraform, es hora de crear los scripts de Github Actions para nuestra canalización CI/CD. Crearemos un total de 3 flujos de trabajo diferentes de la siguiente manera;

  • un flujo de trabajo validate que se activa en cada push a la rama plan de nuestro proyecto. Esto validará cualquier cambio nuevo en nuestra base de código utilizando el comando terraform validate.
  • un flujo de trabajo plan que se activa en cada Pull request a la rama main del proyecto. Este flujo de trabajo ejecutará una prueba preliminar de los próximos cambios de infraestructura y utilizará el Bot de Github para hacer de esos cambios un comentario en dicho Pull request.
  • un flujo de trabajo deploy que se activa en cada push/merge a la rama main. Esto despliega nuestros cambios de infraestructura aprobados.

3.1 crear workflows/validate.yml

Comenzaremos creando una carpeta .github/workflows en la raíz de nuestro directorio de proyecto. Dentro del directorio workflows crearemos un archivo validate.yml. Nuestro árbol de directorios en este punto debería verse así;

Tree 2

Aquí está el contenido del archivo de flujo de trabajo validate;

name: Validate

on:
    push: 
        branches: [plan]

env:
    GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}      

jobs:
  deploy:
    runs-on: ubuntu-latest
      
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true  # Fetch submodules (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_wrapper: true

      - name: Setup Backend Credentials
        id: backend
        run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

      - name: Terraform Init
        id: init
        run: terraform init

      - name: Terraform Validate
        id: terraform
        run: terraform validate -no-color

Este flujo de trabajo:

  • se activa en cada push a la rama plan
  • se ejecuta en la imagen latest Ubuntu
  • revisa nuestro código como primer paso
    • luego configura Terraform
    • configura las credenciales del backend gcs
    • ejecuta terraform init &
    • ejecuta terraform validate en nuestra base de código

Ahora confirmemos este archivo en Git y sigamos adelante.

3.2 crear workflows/deploy.yml

A continuación crearemos nuestro flujo de trabajo deploy con el siguiente contenido;

name: Deploy

on:
    push:
        branches: [main]

env:
    GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

jobs:
  deploy:
    runs-on: ubuntu-latest
      
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true  # Fetch submodules (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3

      - name: Setup Backend Credentials
        id: backend
        run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

      - name: Terraform Init
        id: init
        run: terraform init
        
      - name: Terraform Apply
        id: apply
        env:
          TF_VAR_do_token: ${{ secrets.do_token }}
          TF_VAR_do_region: ${{ secrets.do_region }}
          TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
          TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
        run: terraform apply -auto-approve

Este flujo de trabajo:

  • se activa en cada push/merge a la rama main
  • se ejecuta en la imagen latest Ubuntu
  • revisa nuestro código como primer paso
    • luego configura Terraform
    • configura las credenciales del backend gcs
    • ejecuta terraform init &
    • ejecuta terraform apply con -auto-approve para implementar nuestra infraestructura

Una vez más, confirma este flujo de trabajo en Git con un mensaje adecuado y crearemos nuestro flujo de trabajo final.

3.3 crear workflows/plan.yml

El último (pero definitivamente no menos importante) flujo de trabajo que crearemos es el flujo de trabajo plan. Estamos creando este flujo de trabajo en último lugar porque es dentro de él donde integraremos nuestro Bot de Github para ayudar a proporcionar las salidas de nuestras pruebas preliminares para que nuestras revisiones de Pull-request sean más fáciles y valiosas.

Así que crearemos primero el flujo de trabajo con las funcionalidades necesarias y luego integraremos el Bot en nuestro próximo paso. A continuación está el código de nuestro flujo de trabajo plan;

name: Plan

on:
    pull_request: 
        branches: [main]

env:
  GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions: 
      pull-requests: write
      
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true  # Fetch submodules (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_wrapper: true

      - name: Setup Backend Credentials
        id: backend
        run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

      - name: Terraform Init
        id: init
        run: terraform init

      - name: Terraform fmt
        id: fmt
        run: terraform fmt -check
        continue-on-error: true

      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color
        
      - name: Terraform Plan
        id: plan
        env:
            TF_VAR_do_token: ${{ secrets.do_token }}
            TF_VAR_do_region: ${{ secrets.do_region }}
            TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
            TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
        run: terraform plan -no-color

Este flujo de trabajo:

  • se activa en cada pull-request a la rama main
  • se ejecuta en la imagen latest Ubuntu
  • adquiere permisos de escritura en Pull requests
  • revisa nuestro código como primer paso
    • luego configura Terraform
    • configura las credenciales del backend gcs
    • ejecuta terraform init
    • ejecuta terraform fmt
    • ejecuta terraform validate (nuevamente)
    • ejecuta terraform plan con una prueba preliminar de nuestra infraestructura

¡Perfecto! Ahora podemos seguir adelante y agregar el Bot en la siguiente sección. Recuerda confirmar tu nuevo flujo de trabajo plan en Git.

4. Configurar el Bot de Github

Agregar el Bot de Github a nuestro flujo de trabajo es bastante sencillo. Es solo un paso adicional en nuestro flujo de trabajo de plan.

Añade el siguiente paso a tu archivo de flujo de trabajo plan.yml:

      - name: Comment plan on PR
        uses: actions/github-script@v6
        if: github.event_name == 'pull_request'
        env:
          PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
                // 1. Retrieve existing bot comments for the PR
                const { data: comments } = await github.rest.issues.listComments({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                })
                const botComment = comments.find(comment => {
                return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
                })

                // 2. Prepare format of the comment
                const output = ` ## Terraform Results
                #### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
                #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
                #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
                <details><summary>Validation Output</summary>

                \`\`\`\n
                ${{ steps.validate.outputs.stdout }}
                \`\`\`

                </details>

                #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

                <details><summary>Show Plan</summary>

                \`\`\`\n
                ${process.env.PLAN}
                \`\`\`

                </details>

                *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;

                // 3. If we have a comment, update it, otherwise create a new one
                if (botComment) {
                github.rest.issues.updateComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    comment_id: botComment.id,
                    body: output
                })
                } else {
                github.rest.issues.createComment({
                    issue_number: context.issue.number,
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    body: output
                })
                }

Todo tu archivo plan.yml ahora debería verse exactamente así:

name: Plan

on:
    pull_request: 
        branches: [main]

env:
  GOOGLE_APPLICATION_CREDENTIALS: ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

jobs:
  deploy:
    runs-on: ubuntu-latest
    permissions: 
      pull-requests: write
      
    steps:
      - uses: actions/checkout@v2
        with:
          submodules: true  # Fetch submodules (true OR recursive)
          fetch-depth: 0    # Fetch all history for .GitInfo and .Lastmod

      - name: Setup Terraform
        uses: hashicorp/setup-terraform@v3
        with:
          terraform_wrapper: true

      - name: Setup Backend Credentials
        id: backend
        run: echo '${{ secrets.GCS_KEY }}' > ${{ vars.GOOGLE_APPLICATION_CREDENTIALS }}

      - name: Terraform Init
        id: init
        run: terraform init

      - name: Terraform fmt
        id: fmt
        run: terraform fmt -check
        continue-on-error: true

      - name: Terraform Validate
        id: validate
        run: terraform validate -no-color
        
      - name: Terraform Plan
        id: plan
        env:
            TF_VAR_do_token: ${{ secrets.do_token }}
            TF_VAR_do_region: ${{ secrets.do_region }}
            TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
            TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
        run: terraform plan -no-color

      - name: Comment plan on PR
        uses: actions/github-script@v6
        if: github.event_name == 'pull_request'
        env:
          PLAN: "terraform\n${{ steps.plan.outputs.stdout }}"
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
                // 1. Retrieve existing bot comments for the PR
                const { data: comments } = await github.rest.issues.listComments({
                owner: context.repo.owner,
                repo: context.repo.repo,
                issue_number: context.issue.number,
                })
                const botComment = comments.find(comment => {
                return comment.user.type === 'Bot' && comment.body.includes('Terraform Format and Style')
                })

                // 2. Prepare format of the comment
                const output = ` ## Terraform Results
                #### Terraform Format and Style 🖌\`${{ steps.fmt.outcome }}\`
                #### Terraform Initialization ⚙️\`${{ steps.init.outcome }}\`
                #### Terraform Validation 🤖\`${{ steps.validate.outcome }}\`
                <details><summary>Validation Output</summary>

                \`\`\`\n
                ${{ steps.validate.outputs.stdout }}
                \`\`\`

                </details>

                #### Terraform Plan 📖\`${{ steps.plan.outcome }}\`

                <details><summary>Show Plan</summary>

                \`\`\`\n
                ${process.env.PLAN}
                \`\`\`

                </details>

                *Pusher: @${{ github.actor }}, Action: \`${{ github.event_name }}\`, Working Directory: \`${{ env.tf_actions_working_dir }}\`, Workflow: \`${{ github.workflow }}\`*`;

                // 3. If we have a comment, update it, otherwise create a new one
                if (botComment) {
                github.rest.issues.updateComment({
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    comment_id: botComment.id,
                    body: output
                })
                } else {
                github.rest.issues.createComment({
                    issue_number: context.issue.number,
                    owner: context.repo.owner,
                    repo: context.repo.repo,
                    body: output
                })
                }

5. Subir la Base de Código a Github

Una vez que hayas terminado de confirmar las nuevas adiciones mencionadas a Git, es hora de que publiquemos (publish) nuestra base de código de infraestructura a Github.

Ahora simplemente dirígete a Github y crea un repositorio público (o privado), agrega el repositorio como remoto origin para el proyecto en nuestra máquina local, y haz push.

Inmediatamente hagas esto, tu flujo de trabajo deploy debería comenzar a ejecutarse, pero no te preocupes porque va a fallar. Fallará porque el entorno de Github Actions no ha sido configurado con las credenciales adecuadas tanto para inicializar nuestro Backend de Terraform como para proporcionar los valores apropiados a las variables definidas en nuestra infraestructura.

The very first Deploy workflow fails
Failed Deploy workflow details

Ahora es el momento de configurar correctamente el Entorno CI/CD de Github Actions con las credenciales de acceso adecuadas para implementar nuestra carga de trabajo.

6. Configurar Github Environments para el Backend de Terraform y las Variables

Ahora, necesitamos dirigirnos a Github Environments y configurar los secrets y env vars necesarios para que nuestro flujo de trabajo tenga éxito.

Si te habías dado cuenta a lo largo de nuestros flujos de trabajo de Github Actions, para cada operación que afecta el estado de Terraform, hemos incluido las diferentes variables de entorno que Terraform necesita para ejecutarse. Un ejemplo de esto se encuentra en el comando terraform apply en el flujo de trabajo deploy. El fragmento de código es el siguiente;

      - name: Terraform Apply
        id: apply
        env:
          TF_VAR_do_token: ${{ secrets.do_token }}
          TF_VAR_do_region: ${{ secrets.do_region }}
          TF_VAR_cloudflare_api_token: ${{ secrets.cloudflare_api_token }}
          TF_VAR_cloudflare_zone_id: ${{ secrets.cloudflare_zone_id }}
        run: terraform apply -auto-approve

Así que a continuación hay una lista de todos los diferentes secrets y env vars que necesitamos configurar (verás que todos ellos se han incorporado en los diferentes flujos de trabajo en los lugares apropiados);

  • Variables de Terraform
    • TF_VAR_do_token
    • TF_VAR_do_region
    • TF_VAR_cloudflare_api_token
    • TF_VAR_cloudflare_zone_id
  • Secretos y variables del Backend Remoto
    • GCS_KEY - La clave de Google Cloud Storage.
    • GOOGLE_APPLICATION_CREDENTIALS - Ruta a la clave de Google Cloud Storage en el sistema de archivos

Recuerda crear una diferente GCS Key para el entorno de Github Actions, y desactivar la que usamos para Inicializar Terraform localmente; a partir de ahora, cualquier cambio nuevo en la infraestructura se procesará a través de nuestro flujo de trabajo. No debes usar tu clave ni siquiera para el despliegue inicial de recursos.

Muy bien, ahora dirígete a tu página Github Dashboard -> Settings -> Secrets and Variables -> Actions. Esta es la página que se muestra a continuación.

Create secrets and variables.

A continuación procederemos y completaremos los valores para los diferentes secretos y variables de entorno.

Como resultado del hecho de que solo estamos trabajando con un entorno único en este proyecto, los completaremos como secretos y variables de Repositorio. En una configuración más avanzada donde tendríamos entornos dev, stage, qa, prod, haríamos uso de la solución principal de Github Environments. Este proyecto no llega a ser tan avanzado, así que nos quedaremos con los secretos y variables del repositorio.

  1. Aparte de la variable GOOGLE_APPLICATION_CREDENTIALS, todo lo demás debería ser un secreto. Usa un valor de ./key.json para esta variable, para mantenerlo simple.

  2. Para cambiar entre secrets y variables haz uso de las Pestañas bajo los mismos nombres en la página.

Cuando todo esté configurado, deberíamos tener páginas que se vean como las siguientes:

All our repo secrets set
All our repo variables set

Ahora tenemos todos nuestros secretos y variables configurados. 🎉 🎉

7. Probar todo el flujo de trabajo

Finalmente, es hora de poner esta rueda gigante en movimiento.

7.1 crear la rama plan

El primer paso será crear la rama plan del proyecto. Nos hemos referido a ella mucho a lo largo de los pasos anteriores, es hora de crearla realmente.

Así que lograremos eso con el siguiente comando:

git checkout -b plan

Esto creará la nueva rama plan y nos moverá a ella en nuestro directorio de trabajo actual. A continuación, subiremos esta rama a Github y la configuraremos para que rastree una nueva rama origin bajo el mismo nombre plan.

git push -u origin plan

Este push debería desencadenar un flujo de trabajo validate en Github; si esto tiene éxito, entonces estamos en el camino correcto. Si no lo hace, entonces necesitas revisar para descubrir qué te pudiste haber perdido y arreglarlo. Echa un vistazo a los registros de errores del flujo de trabajo de Actions para entender cualquier error.

Sin embargo, si has seguido esta guía paso a paso religiosamente, deberías tener pantallas de éxito similares a las de abajo. Tu primer flujo de trabajo validate debería ser un éxito.

Successfull Validate workflow run
All steps executed successfully

7.2 preparar y crear el primer PR hacia main

Ahora, para iniciar nuestro flujo de trabajo, necesitamos crear nuestra primera Pull request a la rama main. En este punto eso será imposible porque nuestras ramas main y plan están sincronizadas. Para que funcione, necesitamos hacer un commit ficticio en la rama plan.

Para mantenerlo simple, solo agregaremos un comentario a uno de nuestros archivos de Infraestructura, confirmaremos el cambio y haremos un PR a main.

Puedes agregar un comentario a cualquier archivo de tu elección. Lo haré en el archivo backend.tf. Agregaré las siguientes líneas al principio del archivo

# Configure the remote gcs backend

Ahora guarda tu archivo modificado, confírmalo y súbelo a Github. Eso debería iniciar una nueva ejecución del flujo de trabajo validate en Github;

3rd workflow run

Y debería tener éxito.

Success

Ahora podemos seguir adelante y crear nuestro primer Pull request a main. Si todo funciona bien, deberías obtener resultados similares a los que obtuve a continuación.

Plan workflow starts to run
Plan workflow succeeds

¡En todas partes debería verse verde en tu Pull Request ahora 🎉 🎉

7.3 examinar las salidas del plan a través del comentario del Bot

Ahora que tus flujos de trabajo se han ejecutado con éxito, deberías poder ver el nuevo comentario del Bot que contiene detalles muy importantes sobre tus cambios de infraestructura propuestos.

Github Bot comments Terraform Plan Output

Adelante y expande las diferentes secciones del comentario para obtener más detalles. Al hacer clic en el botón Show Plan, por ejemplo, se te mostrará el resultado del comando terraform plan.

Como puedes ver, debido a que solo tenemos dos recursos definidos en nuestra configuración, vemos un plan para crear dos recursos.

Plan: 2 to Add

7.4 revisar, iterar y aprobar (o rechazar) el plan

En este punto, utilizando las reglas nativas de Rama de Github, se puede requerir que un número mínimo de revisores examine los nuevos cambios de Infraestructura y los apruebe. El equipo puede cambiar lo que sea necesario, las nuevas confirmaciones en la rama plan se apilarán bajo esta pull request, desencadenando una repetición del plan workflow cada vez, asegurándose de que el equipo vea los cambios más actualizados a realizar en la siguiente aplicación.

Ahora, debido a que queremos ver este flujo de trabajo hasta el final y asegurarnos de que nuestra Infraestructura realmente se cree, vamos a asumir que al Equipo le encanta nuestra nueva Infraestructura y los responsables la han aprobado. Como resultado, seguiremos adelante y fusionaremos (Merge) esta Pull Request #1 y haremos que nuestra Infraestructura se implemente.

7.5 y... es Navidad

Confirm Merge Pull Request

Estoy haciendo un rebasing solo porque me gusta un historial de commits ordenado.

Pull Request #1 Merged

Esto debería desencadenar nuestro flujo de trabajo deploy en la rama main, y como se esperaba, debería funcionar perfectamente, así;

Workflow Deployed Successfully
All Deploy Steps Successful

¡¡¡LO HICIMOS!!! 🎉 🎉

Para confirmar que nuestra infraestructura está activa en el mundo real, podemos visitar su dirección web. La mía era server.obi.ninja y funcionó a la perfección.

All Deploy Steps Successful

Tengo https:// porque configuré el servicio SSL gratuito desde Cloudflare. Esto lo hice fuera de Terraform, cuando estaba configurando la zona de Cloudflare en sí, para mantener este proyecto y la guía simples.

8. Notas Adicionales

8.1 Reglas de Protección de Ramas de Github

Para hacer que este proyecto sea a prueba de balas para que funcione de manera confiable para tu organización, necesitas utilizar las Reglas de Protección de Ramas de Github. Con estas reglas podrás hacer cumplir cosas importantes como;

  • asegurarte de que las ramas main y plan no se puedan eliminar
  • permitir una fusión (merge) a main solo después de que los flujos de trabajo validate y plan sean exitosos
  • especificar el número mínimo de personas a las que se les permite aprobar cualquier cambio nuevo
  • etc.

8.2 Herramientas Adicionales

Es posible que desees expandir las diferentes partes de este proyecto con herramientas adicionales para mejorar la seguridad. Herramientas como:

  • Hashicorp Vault para la gestión de secretos
  • Sonarqube para análisis de calidad de código y estático adicional
  • etc.

8.3 Destruir la Infraestructura

Este es un flujo de trabajo en equipo, y es la columna vertebral de la infraestructura de la empresa, como resultado, cualquier destrucción de recursos debe llevarse a cabo eliminando o comentando el código de Terraform para dichos recursos y haciendo que dicho cambio pase por una revisión por parte del Equipo responsable. Nadie podrá simplemente ejecutar terraform destroy y destruir recursos, siempre y cuando los secretos de la infraestructura se manejen adecuadamente.

8.4 Conclusión

Me he divertido mucho ideando, ejecutando y documentando este proyecto. Espero que lo encuentres útil. No dudes en hacerme saber lo que piensas, cualquier error o margen de mejora en la sección de comentarios a continuación.