Burricornio Dev

Ingeniería de software sin humo: sistemas, código y aprendizajes en abierto.

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

¿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:

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:

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:

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

Ansible + Molecule: Configuración probada en contenedores

Helm + kind: Despliegues de aplicaciones en Kubernetes local

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

💡 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:

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:

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?

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:

💡 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)
b) Pipeline de Frontend (React)
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:

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:

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:

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:

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:

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

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:

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:

💡 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:

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:

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

GitOps es una filosofía donde:

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:

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:

💡 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:

💡 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:

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