Blog - Jorge Rodriguez Flores
← Volver al blog

Automatización DevOps con GitLab CI/CD: Pipelines, Runners y Buenas Prácticas

gitlab ci/cd pipelines devops automatizacion runners
Compartir: X / Twitter LinkedIn
Automatización DevOps con GitLab CI/CD: Pipelines, Runners y Buenas Prácticas

GitLab CI/CD es una de las herramientas más completas para automatizar el ciclo de vida del software. Con un único archivo .gitlab-ci.yml en la raíz del repositorio puedes definir toda la cadena de integración continua y despliegue continuo, desde las pruebas unitarias hasta el rollout en producción. En este artículo veremos cómo construir pipelines robustos, gestionar variables sensibles y configurar runners para distintos entornos.

¿Qué es GitLab CI/CD?

GitLab CI/CD es el motor de automatización integrado en GitLab que ejecuta pipelines de forma automática ante eventos como push, merge request o tags. A diferencia de herramientas externas como Jenkins, no requiere instalar software adicional si ya usas GitLab.com o una instancia self-hosted: el CI/CD viene incluido y comparte el mismo control de acceso, el mismo registro de contenedores y el mismo sistema de variables.

Un pipeline está compuesto por stages (etapas) y dentro de cada stage hay uno o más jobs (trabajos). Los stages se ejecutan en secuencia; los jobs dentro del mismo stage se ejecutan en paralelo, siempre que haya runners disponibles.

Estructura del archivo .gitlab-ci.yml

Todo comienza en el archivo .gitlab-ci.yml que vive en la raíz del repositorio. La estructura mínima luce así:

stages:
  - build
  - test
  - deploy

variables:
  APP_ENV: production

build-job:
  stage: build
  image: node:20-alpine
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    expire_in: 1 hour

test-job:
  stage: test
  image: node:20-alpine
  script:
    - npm ci
    - npm test
  coverage: '/Lines\s*:\s*(\d+\.?\d*)%/'

deploy-production:
  stage: deploy
  script:
    - ./scripts/deploy.sh
  environment:
    name: production
    url: https://mi-app.com
  only:
    - main

La directiva stages define el orden de ejecución. Cada job declara a qué stage pertenece. Si un job no especifica stage, cae en el stage test por defecto.

Variables y Secretos

GitLab ofrece tres niveles para definir variables:

  • En el propio .gitlab-ci.yml — para valores no sensibles como nombres de entorno o rutas.
  • En Settings → CI/CD → Variables — para secretos como tokens, contraseñas o claves de API. Se pueden marcar como Masked (no se imprimen en logs) y Protected (solo disponibles en ramas protegidas).
  • Variables de grupo — definidas a nivel de grupo de GitLab, disponibles para todos los proyectos del grupo.
deploy-job:
  stage: deploy
  script:
    - echo "Desplegando en $APP_ENV"
    - curl -X POST "$WEBHOOK_URL" -H "Authorization: Bearer $API_TOKEN"

Las variables definidas en Settings nunca aparecen en el .yml, lo que mantiene limpio el repositorio. Para variables con saltos de línea (como claves PEM), activa la opción Expand variable reference o usa base64 para codificarlas.

GitLab Runners: Tipos y Configuración

Los runners son los agentes que ejecutan los jobs. GitLab ofrece runners compartidos en su SaaS (GitLab.com) y permite registrar runners propios para mayor control. Hay tres tipos principales:

  • Shared runners — disponibles para todos los proyectos, mantenidos por GitLab. Ideales para empezar.
  • Group runners — disponibles para todos los proyectos de un grupo.
  • Specific runners — dedicados a un proyecto en particular. Perfectos para entornos con requisitos especiales (hardware, red privada, etc.).

Para registrar un runner propio en Ubuntu/Debian:

# Instalar el runner
curl -L --output /usr/local/bin/gitlab-runner \
  https://gitlab-runner-releases.s3.amazonaws.com/latest/binaries/gitlab-runner-linux-amd64
chmod +x /usr/local/bin/gitlab-runner

# Registrar
gitlab-runner register \
  --url https://gitlab.com/ \
  --registration-token $TOKEN \
  --executor docker \
  --docker-image alpine:latest \
  --description "Runner produccion" \
  --tag-list "produccion,linux"

El ejecutor más recomendado es docker, ya que cada job corre en un contenedor limpio, evitando contaminación entre ejecuciones. Para jobs que necesitan Docker-in-Docker (construir imágenes), usa el servicio docker:dind:

build-image:
  stage: build
  image: docker:24
  services:
    - docker:24-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  script:
    - docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA .
    - docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHA

Caché, Artefactos y Dependencias

Dos mecanismos reducen drásticamente los tiempos de pipeline:

  • cache — persiste directorios entre ejecuciones del mismo runner. Ideal para node_modules, .venv o la caché de Maven.
  • artifacts — pasa archivos de un stage al siguiente dentro del mismo pipeline. También los hace descargables desde la UI.
build-job:
  stage: build
  cache:
    key: "$CI_COMMIT_REF_SLUG"
    paths:
      - node_modules/
  script:
    - npm ci
    - npm run build
  artifacts:
    paths:
      - dist/
    reports:
      junit: reports/junit.xml
    expire_in: 7 days

La clave $CI_COMMIT_REF_SLUG crea una caché por rama, evitando colisiones entre main y feature branches.

Entornos y Despliegues Controlados

GitLab permite modelar entornos (staging, production) con la directiva environment. Esto habilita el panel Deployments en la UI, historial de despliegues y la opción de hacer rollback con un clic.

deploy-staging:
  stage: deploy
  script:
    - ssh deployer@$STAGING_HOST "cd /app && git pull && ./restart.sh"
  environment:
    name: staging
    url: https://staging.mi-app.com
  only:
    - develop

deploy-production:
  stage: deploy
  script:
    - ssh deployer@$PROD_HOST "cd /app && git pull && ./restart.sh"
  environment:
    name: production
    url: https://mi-app.com
  when: manual
  only:
    - main

Nota la directiva when: manual en el despliegue a producción: el job queda pendiente y requiere aprobación desde la UI antes de ejecutarse. Esto añade una barrera humana justo donde más se necesita.

Pipelines Avanzados: Includes, Reglas y DAG

Cuando el proyecto crece, conviene dividir el .gitlab-ci.yml en múltiples archivos con include:

include:
  - local: '.gitlab/ci/build.yml'
  - local: '.gitlab/ci/test.yml'
  - project: 'mi-grupo/templates-ci'
    ref: main
    file: '/templates/deploy.yml'

Las rules reemplazan a only/except y ofrecen lógica condicional más expresiva:

test-job:
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event"'
      when: always
    - if: '$CI_COMMIT_BRANCH == "main"'
      when: always
    - when: never

Para pipelines con dependencias no lineales entre jobs, GitLab soporta DAG (Directed Acyclic Graph) con la directiva needs:

deploy-api:
  stage: deploy
  needs: ["build-api", "test-api"]
  script: ./deploy_api.sh

deploy-frontend:
  stage: deploy
  needs: ["build-frontend"]
  script: ./deploy_frontend.sh

Buenas Prácticas para Pipelines Eficientes

  • Usa imágenes específicas — evita :latest; fija la versión para builds reproducibles (node:20.12-alpine).
  • Minimiza el tiempo de pipeline — paraleliza con jobs en el mismo stage, usa caché y DAG para evitar esperas innecesarias.
  • Separa las responsabilidades — un job, una responsabilidad. Evita scripts enormes; usa scripts externos versionados.
  • Protege las ramas y variables — activa Protected branches para main y marca las variables sensibles como Protected + Masked.
  • Notifica el resultado — integra Slack o email para alertar fallos en main. GitLab tiene integraciones nativas en Settings → Integrations.
  • Revisa los tiempos regularmente — la sección CI/CD Analytics muestra histograma de duración por pipeline. Un pipeline que supera los 15 minutos suele tener optimizaciones pendientes.

Conclusión

GitLab CI/CD es un ecosistema completo que crece con las necesidades del equipo. Empezar es simple: un archivo YAML, un runner, y ya tienes automatización. Pero las capacidades avanzadas (DAG, environments, includes, reglas condicionales) permiten construir pipelines de nivel enterprise sin salir de la plataforma. La clave está en empezar simple, medir los tiempos y refactorizar el pipeline igual que refactorizas el código: con disciplina y versión controlada.

Artículos relacionados