Burricornio Dev

La Sinfonía DevOps: Orquestando una Aplicación Completa

  • Fecha: 21 de septiembre de 2025
  • Versión: 1.0
  • Autor: Rubén Darío Cabrera García

¿Alguna vez ha sentido que gestionar una aplicación moderna es como intentar dirigir una orquesta sin partitura? Entre un backend en Python, un frontend en React, una base de datos PostgreSQL y una cola de mensajes RabbitMQ, el caos potencial es inmenso. Multiplique esa complejidad por los distintos entornos que debe gestionar —local para desarrollo, lab para pruebas y prod para producción— y la cacofonía puede volverse ensordecedora. Sin una dirección clara, cada componente toca a su propio ritmo, llevando a inconsistencias, errores y despliegues fallidos.

El objetivo de esta entrega es darle esa partitura. Le guiaremos, paso a paso, en la construcción de un flujo de trabajo de despliegue completamente automatizado, consistente y fiable. Adoptaremos los principios de GitOps y un ecosistema de herramientas de primer nivel para transformar el caos en una sinfonía perfectamente coordinada. • Parte 1: La Fundación - Construiremos el escenario con Infraestructura como Código (IaC) usando Terraform y Terragrunt, garantizando que cada entorno sea idéntico y reproducible. • Parte 2: Domando el Caos - Con el escenario listo, subiremos a los tramoyistas. Usaremos Ansible para configurar nuestros servicios, asegurando que cada base de datos y cola de mensajes esté perfectamente preparada para la aplicación. • Parte 3: El Gran Final - Es la hora del espectáculo. Desplegaremos nuestra aplicación con un flujo GitOps completo, donde ArgoCD, Helm y GitLab CI se aseguran de que el estado en producción sea siempre un reflejo exacto de lo definido en Git.

Nota: Todo el código fuente está disponible en el repositorio todo-aws.

¿Qué es GitOps?

GitOps es una metodología DevOps que utiliza Git como la única fuente de la verdad para todo el estado de su sistema. No es simplemente una herramienta, sino un enfoque fundamental que transforma la manera en que gestionamos infraestructura, configuración y aplicaciones. En lugar de comandos manuales o scripts ad-hoc, GitOps establece que:

  • Todo cambio al sistema debe ser propuesto a través de Git
  • Git contiene la descripción declarativa de cómo debe ser el sistema
  • Un agente automatizado (como ArgoCD) se encarga de hacer que la realidad coincida con esa descripción

Este enfoque resuelve los problemas clásicos de los flujos de trabajo tradicionales:

Problema tradicional Solución GitOps
Despliegues manuales → Error humano, inconsistencias Despliegues automatizados → Solo Git decide qué se despliega
Deriva de configuración → Entornos divergen con el tiempo Reconciliación continua → Sistema se auto-repara constantemente
Rollbacks complejos → Requerir intervención manual experta Rollbacks instantáneos → Solo un git revert
Falta de auditoría → No se sabe quién hizo qué cambios Auditoría completa → Todo en el historial de Git
Accesos privilegiados → Credenciales en múltiples sistemas Seguridad mejorada → Solo Git necesita acceso al clúster
  flowchart LR
    A[Desarrollador hace cambios] --> B[Commit en Git]
    B --> C[GitLab CI ejecuta tests y builds]
    C --> D[Push a repositorio Git]
    D --> E[ArgoCD detecta cambio en Git]
    F[ArgoCD reconcilia clúster con estado deseado]
    F --> G[Clúster actualizado automáticamente]
    G --> H[Si alguien modifica manualmente el clúster<br>ArgoCD lo restaura al estado deseado]

Los 5 pilares fundamentales de GitOps

  1. Declarativo y Versionado: Todo el estado del sistema (infraestructura, configuración, aplicaciones) se describe de forma declarativa y se almacena en Git. Cada cambio es un commit auditable.

  2. Automatizado y Deseado: Los cambios que se aprueban y fusionan en Git se aplican automáticamente al sistema en vivo. No hay despliegues manuales.

  3. Reconciliación Continua: Un agente automatizado (como ArgoCD) se ejecuta en el clúster y compara continuamente el estado real con el estado deseado en Git, corrigiendo cualquier desviación o “deriva” de forma automática.

  4. Cerrado por Prueba: Todos los cambios al estado deseado se proponen a través de pull/merge requests. Esto impone una revisión por pares y permite la ejecución de validaciones automáticas antes de que un cambio sea aprobado.

  5. Pull-Based: Los cambios son “tirados” por el clúster desde Git, no “empujados” desde un sistema externo. Esto reduce riesgos de seguridad al eliminar credenciales de despliegue en los sistemas de CI/CD.

Introducción General a la Serie

En la era del desarrollo ágil y la nube, la complejidad de las aplicaciones modernas ha crecido exponencialmente. Hoy en día, una aplicación típica incluye elementos similares al de este proyecto:

  • Backend: Servicios en Python/Node.js con microservicios
  • Frontend: Aplicaciones React/Vue con SPA
  • Base de datos: PostgreSQL/MySQL con replicación
  • Cola de mensajes: RabbitMQ/Kafka para comunicación asíncrona
  • Infraestructura: Kubernetes en AWS/GCP con múltiples clústeres

La verdadera magia no está en las piezas individuales, sino en cómo se orquestan juntas de manera coherente. Vamos a dividir esta entrada en 3 partes importantes, transformaremos este caos en una sinfonía perfectamente coordinada usando:

  • Infraestructura como Código (IaC) con Terraform y Terragrunt
  • Automatización de configuración con Ansible y Molecule
  • GitOps completo con ArgoCD, Helm y GitLab CI

La aplicación que exponemos en este blog es una aplicación de ejemplo que utiliza las tecnologías mencionadas anteriormente. Esta aplicación está diseñada para demostrar cómo se pueden implementar estos principios en una aplicación real.

Nota: Normalmente la infraestructura, configuración y código fuente se gestionan en repositorios separados, pero para este ejemplo, se han combinado en un único repositorio para simplificar la demostración.

Este enfoque no solo garantiza consistencia entre entornos, sino que crea un sistema autoreparable, auditado por diseño y listo para escalar. Pero lo que realmente hace la diferencia es la integración de herramientas de testing en cada etapa del flujo de trabajo, permitiendo validar cambios antes de que lleguen a producción y asegurando que Git sea siempre una fuente de verdad confiable.

Testing en cada etapa: La clave del GitOps iterativo

GitOps no es solo sobre despliegue sino también desarrollo iterativo, es construir confianza en cada paso del ciclo de vida. Para lograr esto, cada herramienta tiene un compañero de testing especializado que permite validar cambios antes de que lleguen a producción. Este enfoque iterativo asegura que Git sea siempre una fuente de verdad confiable.

Nota: No con todas las herramientas que escogemos para nuestros flujos DevOps podremos hacer testing fácilmente, por suerte con las que hemos escogido si podemos

Terraform + LocalStack: Pruebas de infraestructura sin costo

  • ¿Qué hace? LocalStack emula servicios AWS en Docker, permitiendo probar Terraform sin gastar en la nube
  • Beneficio para GitOps: Validar cambios de infraestructura localmente antes de commit, asegurando que el código de IaC sea correcto
  • Flujo iterativo:
    1. Modificar módulo Terraform
    2. Probar con LocalStack revisa Anexo: Implementación de Terraform, Terragrunt y LocalStack
    3. Confirmar cambios en Git
    4. CI ejecuta validación en pipeline
  • Por qué es esencial: Evita errores costosos en la nube real y permite desarrollo offline

Ansible + Molecule: Configuración probada en contenedores

  • ¿Qué hace? Molecule ejecuta roles de Ansible en contenedores Docker aislados
  • Beneficio para GitOps: Cada cambio en configuración se prueba en entornos idénticos a producción antes de commit
  • Flujo iterativo:
    1. Actualizar rol Ansible
    2. Ejecutar molecule test localmente Anexo: Uso de Ansible y Molecule para Configuración
    3. Validar en CI con pipeline config-ci.yml
    4. Solo cambios probados llegan a Git
  • Por qué es esencial: Garantiza idempotencia y evita errores de configuración en producción

Helm + kind: Despliegues de aplicaciones en Kubernetes local

  • ¿Qué hace? kind crea clústeres Kubernetes en Docker para probar Helm charts
  • Beneficio para GitOps: Validar charts antes de sincronizar con ArgoCD, evitando despliegues fallidos
  • Flujo iterativo:
    1. Modificar Helm chart
    2. Desplegar en kind local
    3. Verificar funcionalidad
    4. Commit a Git
    5. ArgoCD sincroniza solo cambios validados
  • Por qué es esencial: Permite probar despliegues completos sin afectar clústeres reales

GitLab CI + gitlab-ci-local: Pipelines probadas antes del push

  • ¿Qué hace? gitlab-ci-local ejecuta pipelines de GitLab en tu máquina
  • Beneficio para GitOps: Evitar errores en CI antes de hacer push, manteniendo la calidad del repositorio
  • Flujo iterativo:
    1. Modificar .gitlab-ci.yml
    2. Probar con gitlab-ci-local --file ci/pipelines.yml
    3. Confirmar cambios solo si pasan pruebas
    4. GitLab CI ejecuta validaciones en remoto
  • Por qué es esencial: Reduce tiempo de espera en CI y asegura que los pipelines sean confiables desde el primer commit

💡 La magia de GitOps iterativo: Estas herramientas crean un ciclo de feedback rápido y seguro donde cada cambio se prueba en múltiples capas antes de convertirse en la fuente de verdad. La confianza en Git como única fuente de la verdad surge precisamente porque cada componente tiene su propio mecanismo de validación, desde el desarrollo local hasta los pipelines en la nube.


Parte 1: La Fundación - Infraestructura como Código que No Falla

1.1. Introducción: Construyendo el Escenario para Nuestra Aplicación

La infraestructura es la base sobre la que se construye toda la aplicación. Sin una fundación sólida, cada despliegue se convierte en un juego de azar. En el pasado, los ingenieros debían:

  1. Conectarse manualmente a servidores
  2. Instalar paquetes uno por uno
  3. Configurar servicios con archivos de texto
  4. Esperar que no hubiera errores en la configuración

El resultado era deriva de configuración - entornos que divergían con el tiempo, causando “funciona en mi máquina” y problemas impredecibles en producción.

Hoy, la solución es Infraestructura como Código (IaC). Al definir nuestra infraestructura en archivos de código versionables, logramos:

  • Reproducibilidad: Crear el mismo entorno en segundos
  • Consistencia: Todos los entornos idénticos
  • Auditoría: Cada cambio es un commit en Git
  • Seguridad: Revisión por pares antes de aplicar cambios

1.2. Los Arquitectos: Terraform, Terragrunt y la Magia de la Infraestructura Declarativa

Terraform es el motor declarativo que permite definir “qué” queremos (clúster EKS, base de datos RDS) en HCL (HashiCorp Configuration Language), y se encarga del “cómo” construirlo. El flujo de trabajo estándar es:

  flowchart LR
    A[terraform init] --> B[terraform plan]
    B --> C[Revisión del plan]
    C --> D[terraform apply]
    D --> E[Infraestructura creada]

Pero a medida que crece la complejidad, Terraform solo no basta. Es aquí donde Terragrunt entra en escena como el sistema de dirección que hace que la infraestructura sea manejable a escala.

Ventajas clave de Terragrunt:

  • DRY (Don’t Repeat Yourself): Configuraciones comunes definidas una vez y heredadas
  • Gestión de estado remoto: Automatiza el almacenamiento del estado en S3
  • Orquestación multi-entorno: Diferencias entre entornos gestionadas de forma limpia

Estructura de directorios optimizada:

  graph TD
    infra/ --> terraform/
    terraform/ --> modules/
    modules/ --> eks/
    modules/ --> rds/
    modules/ --> network/
    modules/ --> local-k8s/
    terraform/ --> environments/
    environments/ --> local/
    environments/ --> lab/
    environments/ --> prod/
    terraform/ --> terragrunt.hcl

1.3. Testing con LocalStack: Validación de Infraestructura sin Costo

La verdadera magia de GitOps iterativo surge cuando podemos probar cambios antes de que lleguen a producción. Para Terraform, LocalStack es nuestra herramienta clave para validar infraestructura sin gastar un céntimo en la nube real.

¿Qué hace LocalStack?

  • Emula servicios AWS en un contenedor Docker
  • Permite probar Terraform contra una versión local de AWS
  • Elimina la necesidad de credenciales de AWS reales para pruebas

Nota: Para poder usar comodamente todas las herramientas y flujos de trabajo, he creado un fichero Makefile y un directorio scripts con scripts auxiliares. Se hará referencia a estos en todo el documento.

Flujo iterativo de prueba:

  1. Modificar módulo Terraform
  2. Ejecutar make test-infra (que inicia LocalStack y aplica Terraform)
  3. Validar con awslocal comandos
  4. Confirmar cambios en Git solo si pasan las pruebas
  5. GitLab CI ejecuta validación en pipeline antes de merge
  flowchart TD
    A[Modificación en Terraform] --> B[Iniciar LocalStack]
    B --> C[Ejecutar terragrunt apply]
    C --> D[Verificar con awslocal]
    D --> E{¿Pruebas pasaron?}
    E -->|Sí| F[Commit en Git]
    E -->|No| G[Corregir y repetir]

Configuración típica de LocalStack:

# docker-compose.yml
services:
  localstack:
    container_name: localstack_main
    image: localstack/localstack:latest
    ports:
      - "4566:4566"
      - "4510-4559:4510-4559"
    environment:
      - SERVICES=s3,cloudformation,mq,ec2,eks,iam,rds
      - DEFAULT_REGION=eu-central-1
      - LOCALSTACK_HOST=localhost.localstack.cloud
    volumes:
      - "localstack_data:/var/lib/localstack"
      - "/var/run/docker.sock:/var/run/docker.sock"

volumes:
  localstack_data:

Ejemplo de prueba en Makefile:

# Makefile
test-infra: ## Run the full infrastructure test suite against LocalStack
	@echo "--- Starting LocalStack for infrastructure tests ---"
	docker compose -f docker-compose.localstack.yml up -d
	@echo "--- Running infrastructure tests ---"
	./scripts/run-infra-tests.sh
	@echo "--- Tearing down LocalStack ---"
	docker compose -f docker-compose.localstack.yml down

Ventajas clave:

  • Costo cero: Pruebas sin facturas de AWS
  • Velocidad: Ciclos de feedback en segundos (no minutos)
  • Offline: Desarrollo sin conexión a internet
  • Consistencia: Comportamiento idéntico a AWS
  • Seguridad: No se usan credenciales reales en desarrollo

💡 Consejo del Arquitecto: La variable SERVICES en LocalStack es crucial. Solo los servicios listados se iniciarán. Si necesitas Lambda, añade lambda a la lista. Siempre verifica que los servicios necesarios para tus módulos Terraform estén incluidos.

1.4. El Entorno Local: Orquestando un Clúster de Kubernetes con kind

Para desarrollo local, usamos kind (Kubernetes in Docker) con Terraform. Con un simple make infra-up:

  flowchart LR
    A[make infra-up] --> B[Terraform crea clúster kind]
    B --> C[Generar KUBECONFIG]
    C --> D[kubectl listo para usar]

Ejemplo de configuración Terraform para kind:

# modules/local-k8s/main.tf
resource "kind_cluster" "main" {
  name = "dev-cluster"

  node {
    image = "kindest/node:v1.27.3"
    roles  = ["control-plane", "worker"]
  }
}

1.5. GitLab CI pipeline modular y orquestada

En un sistema tan completo como el nuestro, un único pipeline monolítico sería un desastre. Por ello, hemos adoptado una arquitectura de CI/CD modular y orquestada, donde cada componente del sistema tiene su propio ciclo de vida, pero todos trabajan en armonía. El cerebro de esta operación es un fichero orquestador (ci/pipelines.yml) que incluye pipelines especializados para cada dominio: la aplicación (backend y frontend), la infraestructura (Terraform), la configuración (Ansible) y el despliegue (Helm/GitOps).

La clave de la eficiencia es la directiva rules:changes, que asegura que un pipeline solo se ejecute cuando se modifican los ficheros de su dominio. Esto evita ejecuciones innecesarias, ahorrando tiempo y recursos.

Nota: La arquitectura modular y orquestada nos permite desplegar y actualizar cada componente del sistema de forma independiente, sin embargo esto se hace asi aqui porque tenemos un monorepo con todo, para casos reales seria mas inteligente separar cada componente en su propio repositorio con su pipeline independiente.

A continuación, desglosamos cada uno de estos pipelines.

1.5.1. Pipelines de Aplicación: El Corazón del Código

Estos pipelines se centran exclusivamente en la calidad y el empaquetado del código de la aplicación. Su objetivo es validar, probar, construir y escanear el backend y el frontend de forma independiente.

a) Pipeline de Backend (Python/Flask)
  • Fichero de definición: ci/backend-ci.yml.
  • Se enfoca en: Validar la calidad del código Python, ejecutar pruebas unitarias y de integración, y construir la imagen Docker final.
  • Activación: Se dispara automáticamente solo si se detectan cambios en la ruta apps/backend/**/*.
  • Etapas y Trabajos Clave:
    • validate-be: Ejecuta lint-backend con herramientas como Flake8 para asegurar que el código sigue las guías de estilo.
    • test-be: Ejecuta test-backend usando pytest para validar la lógica de negocio.
    • build-be: El trabajo build-backend-kaniko construye la imagen Docker sin necesidad de un demonio Docker, lo que es más seguro y eficiente en CI.
    • scan-be: El trabajo scan-backend-image utiliza Trivy para escanear la imagen recién construida en busca de vulnerabilidades de alta criticidad.
b) Pipeline de Frontend (React)
  • Fichero de definición: ci/frontend-ci.yml.
  • Se enfoca en: Asegurar la calidad del código JavaScript/TypeScript, ejecutar pruebas de componentes y construir los artefactos estáticos para servirlos.
  • Activación: Se dispara solo con cambios en apps/frontend/**/*.
  • Etapas y Trabajos Clave:
    • validate-fe: Ejecuta lint-frontend para verificar el formato y estilo del código.
    • test-fe: Lanza test-frontend con el conjunto de pruebas de la aplicación React.
    • build-fe: El trabajo build-frontend-kaniko empaqueta la aplicación en una imagen Docker lista para ser servida.
    • scan-fe: Al igual que el backend, scan-frontend-image escanea la imagen con Trivy en busca de vulnerabilidades.
Cómo ejecutar los pipelines de aplicación en local

Aquí viene la magia. No necesitas hacer un push para ver si tu pipeline funciona. Gracias a gitlab-ci-local y nuestro Makefile, puedes simular todo el flujo de CI en tu máquina.

  1. Prerrequisitos:

    • Tener gitlab-ci-local instalado.
    • Configurar tus credenciales de Docker Hub en un fichero .gitlab-ci-local-variables.yml (que está ignorado por Git) para que Kaniko pueda subir las imágenes. El fichero debería contener tu DOCKER_AUTH_CONFIG.
  2. Comandos Mágicos:

    • Para probar el pipeline completo del backend (linting + tests + build):

      make ci-backend
      

      Este comando orquesta la ejecución de los diferentes jobs del backend localmente.

    • Para probar el pipeline completo del frontend:

      make ci-frontend
      

      De forma análoga, este comando ejecuta todo el ciclo de CI para la aplicación React.

Estos comandos utilizan internamente gitlab-ci-local con el parámetro --file para apuntar al archivo YAML específico de cada pipeline, simulando las rules:changes para que se ejecuten aunque no haya habido un cambio real en Git.

5.1.2. Pipelines de Plataforma: Los Cimientos y la Orquesta

Estos pipelines gestionan la infraestructura, configuración y despliegue de la plataforma - todo lo que no es código de aplicación.

Pipeline de Infraestructura (IaC)

Aspecto Detalles
Archivo ci/iac-pipeline.yml
Propósito Validar, probar y aplicar infraestructura con Terraform/Terragrunt
Activación Cambios en infra/terraform/**/*

Etapas principales:

  • validate-infra - Valida sintaxis HCL y mejores prácticas con terraform-validate y tflint
  • test-infra - Prueba infraestructura contra LocalStack (simulación AWS sin costo)
  • plan-infra - Genera plan de ejecución con terragrunt-plan para revisión
  • apply-infra - Aplica cambios (trabajo manual por seguridad)

Pipeline de Configuración (Ansible)

Aspecto Detalles
Archivo ci/config-ci.yml
Propósito Configurar servicios (BD, colas) post-aprovisionamiento
Activación Cambios en config/ansible/**/*

Etapas principales:

  • validate-config - Ejecuta ansible-lint y molecule-test en contenedores Docker
  • configure - Aplica playbooks con ansible-configure (esquemas PostgreSQL, topología RabbitMQ)

Pipeline de Entrega Continua (GitOps)

Aspecto Detalles
Archivo ci/cd-pipeline.yml
Propósito Actualizar estado deseado en Git (no desplegar directamente)
Activación Cambios en helm-charts/**/* o nuevas imágenes

Etapas principales:

  • validate-cd - Valida Helm Umbrella Chart con helm-lint-platform
  • deploy - cd-gitops-update actualiza values.yaml y hace push; ArgoCD sincroniza automáticamente

Ejecución Local

# Probar infraestructura contra LocalStack
make test-infra

# Validar sintaxis de toda la plataforma
make ci-infra
Resumen de Comandos Locales
Comando make Propósito Pipelines Relacionados
make ci-backend Ejecuta el CI completo (lint, test, build) para el backend. Aplicación (Backend)
make ci-frontend Ejecuta el CI completo (lint, test, build) para el frontend. Aplicación (Frontend)
make ci-app Ejecuta los dos pipelines de aplicación anteriores. Aplicación (Ambos)
make test-infra Prueba los módulos de Terraform contra una simulación de AWS (LocalStack). Plataforma (IaC)
make ci-infra Valida estáticamente (linting) todo el código de la plataforma: Terraform, Helm y Ansible. Plataforma (IaC, Config, CD)
make ci-all Ejecuta absolutamente todo: ci-infra seguido de ci-app. Todos

Con esta arquitectura y los atajos del Makefile, tienes un sistema de CI/CD extremadamente potente, modular y, lo más importante, que te permite desarrollar de forma rápida e iterativa con la confianza de que lo que funciona en tu máquina se comportará de la misma manera en el pipeline real.

1.6. Conclusión de la Parte 1 y Avance

Hemos construido una fundación sólida:

  • Infraestructura versionada en Git
  • Reproducible en múltiples entornos
  • Pruebas rápidas con LocalStack
  • Clúster Kubernetes local para desarrollo
  • Pipelines validados en GitLab CI

Pero, ¿de qué sirve un escenario vacío? En la Parte 2, subiremos a los tramoyistas: Ansible y Molecule, para preparar las bases de datos y colas de mensajes para la aplicación.


Parte 2: Domando el Caos - Configuración con Ansible y Pruebas Rigurosas

2.1. Introducción: De la Infraestructura Vacía a los Servicios Listos

Tras crear la infraestructura en la Parte 1, tenemos cajas vacías:

  • PostgreSQL sin esquemas ni usuarios
  • RabbitMQ sin colas ni exchanges
  • Kubernetes sin aplicaciones

Aquí es donde entra Ansible, nuestro maestro de la configuración. Sus principios clave:

  • Sin agentes: Se conecta por SSH
  • Idempotencia: Ejecutar múltiples veces = mismo resultado
  • YAML legible: Configuración fácil de entender

2.2. Anatomía de Nuestra Configuración Automatizada

La estructura de Ansible es clave para la escalabilidad:

  graph TD
    config/ansible/ --> inventory/
    inventory/ --> local.ini
    inventory/ --> lab.ini
    config/ansible/ --> group_vars/
    group_vars/ --> all/
    group_vars/ --> local/
    config/ansible/ --> playbooks/
    playbooks/ --> configure_local.yml
    config/ansible/ --> roles/
    roles/ --> postgresql_config/
    roles/ --> rabbitmq_config/

Detalles clave de cada directorio:

  • inventory: Define dónde se ejecutan las tareas
    # inventory/local.ini
    [k8s_cluster]
    localhost ansible_connection=local
    
  • group_vars: Variables por entorno
    # group_vars/local/db.yml
    db_name: "dev_db"
    db_user: "dev_user"
    db_password: "dev_password"
    
  • roles: Lógica reutilizable
    # roles/postgresql_config/tasks/main.yml
    - name: Create database
      postgresql_db:
        name: "{{ db_name }}"
        state: present
    

2.3. Testing con Molecule: Validación de Roles en Contenedores

Molecule es el guardián de calidad para Ansible. Permite ejecutar roles de Ansible en contenedores Docker aislados, asegurando que cada cambio en configuración se pruebe en entornos idénticos a producción antes de commit.

Flujo iterativo de prueba:

  1. Actualizar rol Ansible
  2. Ejecutar molecule test localmente
  3. Validar en CI con pipeline config-ci.yml
  4. Solo cambios probados llegan a Git
  flowchart TD
    A[Modificación en rol Ansible] --> B[Molecule crea contenedor]
    B --> C[Aplica rol]
    C --> D[Verifica resultados]
    D --> E{¿Pruebas pasaron?}
    E -->|Sí| F[Commit en Git]
    E -->|No| G[Corregir y repetir]

Ejemplo de configuración Molecule:

# molecule/postgresql_config/molecule.yml
driver:
  name: docker

provisioner:
  name: ansible

platforms:
  - name: postgres
    image: "postgres:14"

verifier:
  name: testinfra
  options:
    sudo: yes

lint:
  name: yamllint

Ejemplo de prueba para PostgreSQL:

# molecule/postgresql_config/tests/test_default.py
import pytest
import psycopg2

def test_database_created(host):
    # Verificar que la base de datos existe
    conn = psycopg2.connect(
        host="localhost",
        database="dev_db",
        user="dev_user",
        password="dev_password"
    )
    cur = conn.cursor()
    cur.execute("SELECT 1 FROM tasks")
    assert cur.fetchone() is not None

Ventajas clave:

  • Aislamiento: Cada prueba en contenedor independiente
  • Idempotencia verificada: Ejecutar múltiples veces = mismo resultado
  • Rápido feedback: No necesitas esperar a CI remoto
  • Confianza: Sabes que los cambios funcionarán en producción

💡 Consejo del Arquitecto: Molecule permite probar en múltiples plataformas (Docker, Vagrant, etc.). Para pruebas de PostgreSQL, usa la imagen oficial de PostgreSQL; para RabbitMQ, usa la imagen oficial de RabbitMQ.

2.4. Un Vistazo a los Roles: Configurando PostgreSQL y RabbitMQ

Rol PostgreSQL Config

  flowchart TD
    A[Crear usuario] --> B[Crear base de datos]
    B --> C[Crear esquema]
    C --> D[Crear tablas]
    D --> E[Crear índices]
    E --> F[Opcional: poblar datos]

Ejemplo de tarea para crear tablas:

- name: Create tasks table
  postgresql_query:
    db: "{{ db_name }}"
    query: |
      CREATE TABLE IF NOT EXISTS tasks (
        id SERIAL PRIMARY KEY,
        title VARCHAR(255) NOT NULL,
        status VARCHAR(50) CHECK (status IN ('pending', 'in_progress', 'completed')),
        created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
      );

Rol RabbitMQ Config

  flowchart TD
    A[Crear exchanges] --> B[Crear DLX]
    B --> C[Crear colas]
    C --> D[Crear bindings]

Configuración típica de RabbitMQ:

- name: Declare task_events_exchange
  rabbitmq_exchange:
    name: "task_events_exchange"
    type: "topic"
    durable: yes
    state: present
    vhost: "/"

- name: Declare task.created.queue
  rabbitmq_queue:
    name: "task.created.queue"
    durable: yes
    vhost: "/"

- name: Bind queue to exchange
  rabbitmq_binding:
    exchange: "task_events_exchange"
    queue: "task.created.queue"
    routing_key: "task.created"
    vhost: "/"

2.5. Conclusión de la Parte 2 y Avance

Hemos logrado:

  • Configuración idempotente y reutilizable
  • Pruebas automatizadas en contenedores aislados
  • Separación clara entre infraestructura (Parte 1) y configuración (Parte 2)
  • Pipelines de CI/CD validados antes de merge

Con el escenario listo y los servicios configurados, solo falta la aplicación. En la Parte 3, daremos vida a nuestra sinfonía con un flujo GitOps completo.


Parte 3: El Gran Final - Despliegue GitOps con ArgoCD, Helm y GitLab CI

3.1. Introducción: ¡Es la Hora del Espectáculo!

Ahora que tenemos:

  • Infraestructura definida en Terraform
  • Servicios configurados con Ansible
  • Clúster Kubernetes listo

Es hora de desplegar la aplicación. Pero no cualquier despliegue… GitOps.

GitOps es una filosofía donde:

  • Git es la única fuente de la verdad
  • Los cambios se aplican automáticamente
  • El sistema se auto-repara
  • Los rollbacks son triviales

Comparación: Enfoque Tradicional vs GitOps

Característica Enfoque Tradicional GitOps
Fuente de Verdad Dispersa (documentos, scripts) Centralizada: repositorio Git
Acción de Despliegue Imperativa (kubectl apply) Declarativa (ArgoCD reconcilia)
Detección de Deriva Manual Automática y continua
Reversión Compleja git revert
Impulsor del Cambio Humano “empuja” cambios Agente “tira” cambios desde Git

3.2. Las Herramientas del Oficio GitOps

Helm: El Gestor de Paquetes de Kubernetes

Helm empaqueta aplicaciones en charts reutilizables. En este proyecto, usamos un Umbrella Chart:

  graph TD
    todo-platform/ --> backend/
    todo-platform/ --> frontend/
    todo-platform/ --> common/
    backend/ --> Dockerfile
    backend/ --> requirements.txt
    frontend/ --> package.json
    frontend/ --> src/

Ejemplo de values-prod.yaml:

backend:
  image:
    repository: my-registry/backend
    tag: a1b2c3d

frontend:
  image:
    repository: my-registry/frontend
    tag: v1.2.3

ArgoCD: El Cerebro de GitOps

ArgoCD es el operador que reconcilia el estado deseado con el estado real:

  flowchart LR
    A[Repositorio Git] --> B[ArgoCD]
    B --> C[Comparar estado real vs deseado]
    C --> D{¿Hay diferencias?}
    D -->|Sí| E[Aplicar cambios]
    D -->|No| F[No hacer nada]
    E --> B

Beneficios clave:

  • Auto-reparación: Si alguien borra un recurso, ArgoCD lo recrea
  • Historial completo: Todos los cambios auditados en Git
  • Rollbacks instantáneos: git revert + sincronización

GitLab CI: El Motor de Automatización

GitLab CI gestiona la integración continua antes del despliegue:

  flowchart LR
    A[Push a main] --> B[Detectar cambios en apps/backend/]
    B --> C[Build imagen con Kaniko]
    C --> D[Actualizar values-prod.yaml]
    D --> E[Commit y push a Git]
    E --> F[ArgoCD detecta cambio]
    F --> G[Despliegue en producción]

3.3. Testing con kind: Despliegues en Kubernetes Local

Para validar Helm charts antes de sincronizar con ArgoCD, usamos kind (Kubernetes in Docker) para crear clústeres Kubernetes locales.

Flujo iterativo de prueba:

  1. Modificar Helm chart
  2. Desplegar en kind local
  3. Verificar funcionalidad
  4. Commit a Git
  5. ArgoCD sincroniza solo cambios validados
  flowchart TD
    A[Modificación en Helm chart] --> B[Crear clúster kind]
    B --> C[helm install --dry-run]
    C --> D[Verificar recursos]
    D --> E{¿Funciona?}
    E -->|Sí| F[Commit en Git]
    E -->|No| G[Corregir y repetir]

Ejemplo de prueba con kind:

# Crear clúster kind
kind create cluster --name test-cluster

# Instalar Helm chart en dry-run
helm install --dry-run --debug ./helm/todo-platform

# Desplegar en clúster
helm install todo-app ./helm/todo-platform

# Verificar aplicaciones
kubectl get pods
kubectl get services

# Limpiar
helm uninstall todo-app
kind delete cluster --name test-cluster

Ventajas clave:

  • Seguridad: No afecta clústeres reales
  • Rapidez: Clústeres se crean y destruyen en segundos
  • Consistencia: Mismo comportamiento que en producción
  • Validación temprana: Detecta errores antes de sincronizar con ArgoCD

💡 Consejo del Arquitecto: Usa --dry-run para validar sin desplegar, y --debug para ver detalles de Helm. Esto es crucial para evitar despliegues fallidos en producción.

3.4. Testing con gitlab-ci-local: Pipelines probadas antes del push

La herramienta gitlab-ci-local permite probar pipelines de GitLab en tu máquina antes de hacer push, asegurando que los cambios en .gitlab-ci.yml funcionarán correctamente en remoto.

Flujo iterativo de prueba:

  1. Modificar .gitlab-ci.yml
  2. Probar con gitlab-ci-local --file ci/pipelines.yml
  3. Confirmar cambios solo si pasan pruebas
  4. GitLab CI ejecuta validaciones en remoto
  flowchart TD
    A[Modificación en .gitlab-ci.yml] --> B[gitlab-ci-local]
    B --> C[Ejecutar pipeline local]
    C --> D{¿Pruebas pasaron?}
    D -->|Sí| E[Commit en Git]
    D -->|No| F[Corregir y repetir]

Ejecución local con gitlab-ci-local:

# Probar todos los pipelines en local
gitlab-ci-local --file ci/pipelines.yml \
  --variable CI_PIPELINE_SOURCE=merge_request_event \
  --evaluate-rule-changes=false

# Probar solo la infraestructura
gitlab-ci-local --file ci/iac-pipeline.yml \
  --variable CI_PIPELINE_SOURCE=merge_request_event \
  --evaluate-rule-changes=false \
  validate plan

# Probar solo la configuración de Ansible
gitlab-ci-local --file ci/config-ci.yml \
  --variable CI_PIPELINE_SOURCE=merge_request_event \
  --evaluate-rule-changes=false \
  ansible-lint molecule-test

# Probar solo el backend
gitlab-ci-local --file ci/backend-ci.yml \
  --variable CI_PIPELINE_SOURCE=merge_request_event \
  --evaluate-rule-changes=false \
  pytest flake8 docker-build

# Probar solo el frontend
gitlab-ci-local --file ci/frontend-ci.yml \
  --variable CI_PIPELINE_SOURCE=merge_request_event \
  --evaluate-rule-changes=false \
  npm-test npm-build

# Probar solo el CD pipeline
gitlab-ci-local --file ci/cd-pipeline.yml \
  --variable CI_PIPELINE_SOURCE=merge_request_event \
  --evaluate-rule-changes=false \
  update-values sync-argocd

Ventajas clave:

  • Ciclos de feedback rápidos: No necesitas esperar a runners de CI remoto
  • Reducción de costos: Menos minutos de CI consumidos
  • Mayor confianza: Sabes que los cambios funcionarán en remoto
  • Seguridad: Evitas romper el pipeline principal

💡 Consejo del Arquitecto: Usa --evaluate-rule-changes=false para probar el pipeline completo sin depender de cambios en archivos específicos. Esto es esencial para probar reglas complejas de CI/CD.

3.5. La Sinfonía Completa: El Flujo End-to-End

Sigamos el viaje de un cambio desde el portátil hasta producción:

  flowchart TD
    A[Desarrollador hace push] --> B[GitLab CI detecta cambios]
    B --> C[Ejecutar pipelines específicos]
    C --> D[backend-ci.yml: tests y build]
    C --> E[frontend-ci.yml: tests y build]
    C --> F[iac-pipeline.yml: validar infraestructura]
    C --> G[config-ci.yml: validar configuración]
    C --> H[test-helm: validar Helm charts]
    D & E & F & G & H --> I[cd-pipeline.yml: actualizar valores y sincronizar]
    I --> J[ArgoCD detecta cambio en Git]
    J --> K[ArgoCD reconcilia clúster con estado deseado]
    K --> L[Clúster actualizado automáticamente]

3.6. Pipeline Maestro: pipelines.yml

Para organizar todos los pipelines en un solo lugar, creamos un archivo maestro ci/pipelines.yml:

# ci/pipelines.yml
include:
  - local: ci/iac-pipeline.yml
  - local: ci/config-ci.yml
  - local: ci/backend-ci.yml
  - local: ci/frontend-ci.yml
  - local: ci/cd-pipeline.yml

Este archivo permite ejecutar todos los pipelines en una sola ejecución, facilitando la gestión y el mantenimiento.

3.7. Conclusión Final: La Orquesta en Perfecta Armonía

Hemos construido una sinfonía DevOps completa:

Componente Rol Herramientas
Escenario Infraestructura Terraform, Terragrunt, LocalStack
Tramoyistas Configuración Ansible, Molecule
Músicos Aplicación Helm, kind, ArgoCD
Director Orquestación GitLab CI, gitlab-ci-local
Partitura Fuente de verdad Git

Beneficios transformadores:

  • 🚀 Entrega acelerada: Despliegues en minutos
  • 🔒 Seguridad mejorada: Revisión por pares en todos los cambios
  • 🛡️ Resiliencia: Auto-reparación ante derivas
  • 📜 Auditoría completa: Historial Git de todos los cambios
  • 💡 Innovación sin miedo: Confianza para implementar cambios

Al final, no se trata de las herramientas, sino de la partitura. Con GitOps, cada componente interpreta la misma melodía desde la única fuente de la verdad: Git. El resultado es una aplicación que no solo funciona, sino que se auto-repara, es auditable por diseño y, lo más importante, le devuelve al equipo de ingeniería la tranquilidad para innovar sin miedo.

La verdadera sinfonía no es solo la música que se escucha, sino la tranquilidad de saber que la música nunca se detendrá.


¿Listo para comenzar?

  1. 📁 Clona el repositorio de ejemplo: `git clone https://gitlab.com/rubentxu74/todo-aws

  2. 🚀 Ejecuta la fundación: make infra-up (Terraform + LocalStack)

  3. 🛠️ Configura los servicios: ansible-playbook config/ansible/playbooks/configure_local.yml

  4. 🎶 Despliega la aplicación: git push origin main (GitLab CI + ArgoCD)

  5. 🔍 Prueba los pipelines en local antes de push:

    gitlab-ci-local --file ci/pipelines.yml \
      --variable CI_PIPELINE_SOURCE=merge_request_event \
      --evaluate-rule-changes=false
    

¡Y observa cómo la sinfonía DevOps comienza a sonar!

Nota: Para que todo funciones correctamente tendrás que instalar muchas herramientas diversas que no cubrimos en este documento, como:

  • terraform
  • terragrunt
  • kubectl
  • helm
  • gitlab-ci-local
  • ansible
  • awscli (opcional)
  • yq / jq (utilidades)
  • direnv (opcional)

Si tienes linux o sistema unix talvez asdf-vm te pueda ayudar bastante. https://asdf-vm.com/


Anexo A. Estructura del Repositorio de Código Unificado

Se propone una estructura de repositorio monorepo que contiene el código de la aplicación y toda la definición de su infraestructura y despliegue, promoviendo la coherencia y la facilidad de gestión. En casos mas avanzados se podria separar el codigo de la aplicacion, la infraestructura y el de configuracion y despliegue en repositorios diferentes y que cada uno de ellos tenga su pipeline de gitlab centrado en su objetivo. Pero como en este caso es un proyecto pequeño y de ejemplo, se opta por la estructura monorepo.

project-root/
├── Makefile
├── infra/
│   ├── terraform/
│   │   ├── modules/
│   │   │   ├── network/
│   │   │   ├── eks/
│   │   │   ├── rds/
│   │   │   ├── rabbitmq/
│   │   │   ├── s3/
│   │   │   └── local-k8s/
│   │   ├── environments/
│   │   │   ├── dev/ (#1)
│   │   │   ├── lab/ (#2)
│   │   │   └── prod/ (#3)
│   │   └── terragrunt.hcl
├── config/
│   ├── ansible/
│   │   ├── group_vars/
│   │   │   ├── all/
│   │   │   ├── local/
│   │   │   ├── lab/
│   │   │   └── prod/
│   │   ├── playbooks/
│   │   │   ├── postgresql/
│   │   │   ├── rabbitmq/
│   │   │   └── kubernetes/
│   │   ├── inventory/
│   │   │   ├── local.ini
│   │   │   ├── lab.ini
│   │   │   └── prod.ini
│   │   └── roles/
│   │       ├── postgresql-config/
│   │       ├── rabbitmq-config/
│   │       └── k8s-config/
├── apps/
│   ├── python-app/
│   │   ├── Dockerfile
│   │   ├── src/
│   │   ├── tests/
│   │   └── requirements.txt
├── helm/
│   ├── charts/
│   │   ├── python-app/
│   │   │   ├── Chart.yaml
│   │   │   ├── values.yaml
│   │   │   ├── templates/
│   │   │   └── requirements.yaml
│   │   ├── rabbitmq/
│   │   └── postgresql/
│   └── environments/
│           ├── local/
│           ├── lab/
│           └── prod/
├── argocd/
│   ├── applications/
│   │   ├── base/
│   │   └── overlays/
│   ├── projects/
│   └── config/
├── ci-cd/
│   ├── .gitlab-ci.yml              # Pipeline con rules:changes para aplicacion y infraestructura
    └── Makefile

  • Anexo: Implementación de Terraform, Terragrunt y LocalStack: Este anexo detalla la gestión de infraestructura como código (IaC) usando Terraform y Terragrunt para entornos múltiples, y LocalStack para pruebas locales. Incluye diagramas Mermaid de la estructura de componentes y flujos de trabajo, ejemplos de configuración de Terragrunt, scripts de Makefile para automatización, y guías para integrar con CI/CD.
  • Anexo: Uso de Ansible y Molecule para Configuración: Explica cómo usar Ansible para la configuración de recursos (esquemas de BBDD, colas de mensajes) y Molecule para el testing de roles en contenedores.