¡Hola, desarrolladores! En este artículo, hablaré sobre CI/CD, un concepto.
- ¿Qué es un pipeline de CI/CD en el contexto de la programación?
- 🔄 ¿De qué suele constar un pipeline de CI/CD?
- 🛠 Herramientas populares de CI/CD:
- 🧠 ¿Por qué necesitamos CI/CD?
- 📦 Ejemplos sencillos de CI/CD con GitHub Actions
- 🚚 Despliegue en plataformas populares
- 🌟 CI/CD Avanzado: Construir Docker → Push a GHCR → Staging/Producción en GCP Cloud Run
¿Qué es un pipeline de CI/CD en el contexto de la programación?
El pipeline de CI/CD (Integración Continua / Entrega Continua o Despliegue Continuo) es un proceso automatizado que permite a los desarrolladores entregar cambios de código de forma rápida y fiable a un entorno de producción.
Desglosemos los conceptos clave:
🔧 CI — Integración Continua
Esta es una práctica en la que los desarrolladores integran con frecuencia cambios en una base de código compartida. Cada cambio de este tipo es automáticamente:
- Construido (build)
- Probado (pruebas unitarias, pruebas de integración)
- Verificado para el cumplimiento de estándares (linting, análisis estático)
👉 Objetivo de CI: Identificar errores en la etapa más temprana, antes de que rompan algo importante o lleguen a una versión.
🚀 CD — Entrega Continua o Despliegue Continuo
Aquí hay dos opciones:
✅ Entrega Continua
Después de pasar con éxito la etapa de CI, los cambios automáticamente:
- Se someten a pruebas adicionales (por ejemplo, pruebas E2E – de extremo a extremo)
- Se despliegan en un servidor de staging (prueba)
👉 Pero el despliegue a producción todavía requiere confirmación manual. Esto le da al equipo control sobre cuándo exactamente los usuarios verán los cambios.
🤖 Despliegue Continuo
Este es el siguiente paso después de la Entrega Continua. Aquí, el despliegue a producción ocurre completamente automáticamente si todas las etapas anteriores del pipeline (construcción, todas las pruebas) han pasado con éxito. Este es el nivel más avanzado de automatización.
🔄 ¿De qué suele constar un pipeline de CI/CD?
Un pipeline típico incluye las siguientes etapas:
- Checkout — Clonación de la última versión del código del repositorio.
- Build — Construcción del proyecto (compilación, ensamblaje de artefactos, imágenes Docker).
- Test — Ejecución de varios tipos de pruebas (unitarias, de integración, E2E).
- Lint/Code Quality — Verificación del código para el cumplimiento del estilo y posibles errores utilizando analizadores estáticos.
- Deploy — Despliegue de la aplicación (a un servidor de staging o producción).
- Notify — Envío de notificaciones sobre el estado del pipeline al equipo (por ejemplo, Slack, correo electrónico).
🛠 Herramientas populares de CI/CD:
- GitHub Actions (¡nuestro enfoque hoy!)
- GitLab CI/CD
- Jenkins
- CircleCI
- Bitbucket Pipelines
- Azure DevOps
- TeamCity
🧠 ¿Por qué necesitamos CI/CD?
- Reduce el error humano: La automatización elimina los errores asociados con las operaciones manuales.
- Detección rápida de errores: Los errores se encuentran antes, lo que los hace más fáciles y económicos de corregir.
- Automatización de tareas rutinarias: Los desarrolladores dedican menos tiempo a la construcción y el despliegue, y más a la codificación.
- Mejora de la calidad del código: Las comprobaciones y pruebas continuas elevan el nivel general de calidad.
- Entrega rápida de características a los usuarios: Las nuevas funcionalidades llegan al usuario final más rápido y con mayor frecuencia.
📦 Ejemplos sencillos de CI/CD con GitHub Actions
Veamos pipelines básicos para tecnologías populares. Todos los ejemplos utilizan GitHub Actions y se guardan en el directorio .github/workflows/ de su proyecto.
🐍 CI/CD para Python (con pytest y flake8)
# .github/workflows/python-ci.yml
name: Python CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11' # Especifique su versión
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install -r requirements.txt # Asegúrese de tener requirements.txt
pip install flake8 pytest
- name: Lint with flake8
run: |
# Comprobar código en las carpetas src y tests (adaptar a su proyecto)
flake8 src tests
- name: Run tests
run: |
pytest
🌐 CI/CD para Node.js (con npm test y eslint)
# .github/workflows/node-ci.yml
name: Node.js CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [18.x] # Especifique su versión de Node.js
steps:
- uses: actions/checkout@v3
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- name: Install dependencies
run: npm install # o npm ci para una instalación más predecible
- name: Lint with ESLint
run: npx eslint . # Asegúrese de que ESLint esté configurado en el proyecto
- name: Run tests
run: npm test
🐳 CI/CD para Docker (construcción y push a Docker Hub)
Para este ejemplo, necesitará los secretos DOCKER_USERNAME y DOCKER_PASSWORD (o token) en la configuración de su repositorio de GitHub (Settings -> Secrets and variables -> Actions).
# .github/workflows/docker-ci.yml
name: Docker CI/CD
on:
push:
branches: [ main ] # Ejecutar solo para la rama main
jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Log in to Docker Hub
run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin
- name: Build Docker image
# Reemplace myapp con el nombre de su aplicación
run: docker build -t ${{ secrets.DOCKER_USERNAME }}/myapp:latest .
- name: Push Docker image
run: docker push ${{ secrets.DOCKER_USERNAME }}/myapp:latest
🚚 Despliegue en plataformas populares
Ahora que tenemos artefactos construidos y probados (por ejemplo, una imagen Docker), veamos cómo se pueden desplegar.
🟣 Despliegue en Heroku
🔐 Secretos de GitHub: HEROKU_API_KEY, HEROKU_APP_NAME.
# .github/workflows/deploy-heroku.yml
name: Deploy to Heroku
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install Heroku CLI
run: curl https://cli-assets.heroku.com/install.sh | sh
- name: Login to Heroku
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: heroku auth:token
- name: Deploy to Heroku
env:
HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
run: |
heroku git:remote -a ${{ secrets.HEROKU_APP_NAME }}
git push heroku main -f # Tenga cuidado con -f (force push)
Si está desplegando una imagen Docker en Heroku:
# ... (pasos de construcción y login en Docker Hub/GHCR de ejemplos anteriores) ...
# deploy:
# name: Deploy to Heroku
# needs: build # Depende del trabajo de construcción de la imagen
# runs-on: ubuntu-latest
# steps:
# # ...
# - name: Login to Heroku container registry
# run: echo "${{ secrets.HEROKU_API_KEY }}" | docker login --username=_ --password-stdin registry.heroku.com
# - name: Tag image for Heroku
# # Suponiendo que la imagen se construye como ghcr.io/username/repo/myapp:latest
# run: docker tag ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest registry.heroku.com/${{ secrets.HEROKU_APP_NAME }}/web
# - name: Push image to Heroku
# run: docker push registry.heroku.com/${{ secrets.HEROKU_APP_NAME }}/web
# - name: Release Heroku App
# env:
# HEROKU_API_KEY: ${{ secrets.HEROKU_API_KEY }}
# run: heroku container:release web --app ${{ secrets.HEROKU_APP_NAME }}
🟨 Despliegue en AWS (por ejemplo, archivos estáticos en S3)
🔐 Secretos de GitHub: AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION, S3_BUCKET_NAME.
# .github/workflows/deploy-aws-s3.yml
name: Deploy Static Site to AWS S3
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v4
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_REGION }}
- name: Sync files to S3
# Reemplace ./public con la ruta a sus archivos estáticos
run: aws s3 sync ./public s3://${{ secrets.S3_BUCKET_NAME }} --delete
Para el despliegue en AWS Elastic Beanstalk, normalmente se utiliza la CLI de EB, el pipeline será similar, pero con comandos eb deploy.
🔵 Despliegue en Google Cloud Platform (GCP App Engine)
🔐 Secretos de GitHub: GCP_CREDENTIALS (clave JSON de la cuenta de servicio), GCP_PROJECT_ID.
# .github/workflows/deploy-gcp-app-engine.yml
name: Deploy to GCP App Engine
on:
push:
branches: [main]
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Cloud SDK
uses: google-github-actions/setup-gcloud@v2
with:
project_id: ${{ secrets.GCP_PROJECT_ID }}
service_account_key: ${{ secrets.GCP_CREDENTIALS }}
export_default_credentials: true
- name: Deploy to App Engine
# Asegúrese de tener app.yaml en la raíz del proyecto
run: gcloud app deploy --quiet
🟪 Despliegue en Render.com
Render a menudo despliega automáticamente al hacer push a GitHub si el repositorio está conectado. Pero para un disparador manual (o como parte de un pipeline más complejo), puede usar un Hook de Despliegue.
🔐 Secretos de GitHub: RENDER_DEPLOY_HOOK (URL obtenida de la configuración del servicio de Render).
# .github/workflows/deploy-render.yml
name: Trigger Render Deploy
on:
workflow_dispatch: # Disparador manual desde la UI de GitHub
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Trigger Render Deploy Hook
run: curl -X POST ${{ secrets.RENDER_DEPLOY_HOOK }}
🌟 CI/CD Avanzado: Construir Docker → Push a GHCR → Staging/Producción en GCP Cloud Run
¡Y ahora la guinda del pastel! Construyamos un pipeline avanzado:
- Construir imagen Docker.
- Publicar imagen en GitHub Container Registry (ghcr.io).
- Despliegue automático en el entorno de staging en GCP Cloud Run.
- Despliegue en el entorno de producción en GCP Cloud Run después de la confirmación manual.
Para esto, necesitaremos varios archivos de flujo de trabajo.
Secretos de GitHub requeridos:
GCP_PROJECT_ID: ID de su proyecto de GCP.GCP_CREDENTIALS: Clave JSON de la cuenta de servicio de GCP con permisos para desplegar en Cloud Run y acceder a GHCR (si es necesario). NormalmenteGITHUB_TOKENes suficiente para el acceso a GHCR desde Actions.GCP_REGION: Región para Cloud Run (por ejemplo,europe-west1).
1. Construir y publicar imagen Docker en GHCR
# .github/workflows/build.yml
name: Build & Push to GHCR
on:
push:
branches: [main] # Ejecutar en push a main
jobs:
build-and-push:
runs-on: ubuntu-latest
permissions:
contents: read # Para checkout
packages: write # Para push a GHCR
steps:
- name: Checkout repository
uses: actions/checkout@v3
- name: Log in to GitHub Container Registry
uses: docker/login-action@v3
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest
# Puede agregar etiquetado por SHA de commit para unicidad:
# tags: |
# ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest
# ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:${{ github.sha }}
github.repository_owner: Propietario del repositorio (su nombre de usuario u organización).github.event.repository.name: Nombre del repositorio.myapp: Nombre de su aplicación/imagen.
2. Despliegue automático en Staging (GCP Cloud Run)
Este flujo de trabajo se ejecutará automáticamente después de que `build.yml` se complete con éxito.
# .github/workflows/deploy-staging.yml
name: Deploy to GCP Cloud Run (Staging)
on:
workflow_run:
workflows: ["Build & Push to GHCR"] # Nombre del flujo de trabajo de construcción
types:
- completed
jobs:
deploy-staging:
runs-on: ubuntu-latest
# Ejecutar solo si el flujo de trabajo de construcción se completó con éxito
if: ${{ github.event.workflow_run.conclusion == 'success' }}
# Usar entornos de GitHub para staging (opcional, pero buena práctica)
environment:
name: staging
url: ${{ steps.deploy.outputs.url }} # La URL estará disponible después del despliegue
steps:
- uses: actions/checkout@v3 # Necesario si usa configuraciones del repositorio
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
- name: 'Deploy to Cloud Run (Staging)'
id: deploy
uses: 'google-github-actions/deploy-cloudrun@v2'
with:
service: 'myapp-staging' # Nombre de su servicio de staging de Cloud Run
region: '${{ secrets.GCP_REGION }}'
# Usar la imagen que se subió en build.yml
image: 'ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest'
project_id: '${{ secrets.GCP_PROJECT_ID }}'
flags: '--allow-unauthenticated --platform=managed' # Permitir acceso no autenticado para el ejemplo
3. Despliegue en Producción con confirmación manual (GCP Cloud Run)
Este flujo de trabajo se activa manualmente a través de la UI de GitHub Actions.
# .github/workflows/deploy-prod.yml
name: Deploy to GCP Cloud Run (Production)
on:
workflow_dispatch: # Permite el disparador manual
jobs:
deploy-production:
runs-on: ubuntu-latest
environment:
name: production
url: ${{ steps.deploy.outputs.url }}
steps:
- uses: actions/checkout@v3
- id: 'auth'
uses: 'google-github-actions/auth@v2'
with:
credentials_json: '${{ secrets.GCP_CREDENTIALS }}'
- name: 'Deploy to Cloud Run (Production)'
id: deploy
uses: 'google-github-actions/deploy-cloudrun@v2'
with:
service: 'myapp-production' # Nombre de su servicio de producción
region: '${{ secrets.GCP_REGION }}'
image: 'ghcr.io/${{ github.repository_owner }}/${{ github.event.repository.name }}/myapp:latest' # Usar la misma imagen 'latest'
project_id: '${{ secrets.GCP_PROJECT_ID }}'
flags: '--allow-unauthenticated --platform=managed'
# Para producción, puede agregar --no-traffic y luego cambiar el tráfico gradualmente
# traffic:
# latest: true
# percent: 100
Puntos importantes de este pipeline avanzado:
- GitHub Container Registry (ghcr.io): Lo usamos para almacenar imágenes Docker. Esto es conveniente ya que está estrechamente integrado con GitHub Actions.
workflow_run: Permite que un flujo de trabajo (despliegue de staging) se active al finalizar otro (construcción).workflow_dispatch: Proporciona la capacidad de activar manualmente un flujo de trabajo (despliegue de producción), lo que garantiza el control.- GitHub Environments: Le permiten configurar reglas de protección para producción (por ejemplo, requerir la aprobación de revisores específicos) y almacenar secretos específicos del entorno.
- GCP Cloud Run: Una excelente opción sin servidor para ejecutar aplicaciones en contenedores.
🔐 Seguridad – ¡es importante!
- Use Secretos de GitHub: Nunca almacene tokens, contraseñas, claves API directamente en archivos YAML. Use
Settings -> Secrets and variables -> Actionsen su repositorio. - Privilegios mínimos: Para las cuentas de servicio (por ejemplo, GCP), otorgue solo los permisos estrictamente necesarios para realizar tareas de CI/CD.
- Aislar entornos: Staging y Producción deben estar lo más aislados posible. Diferentes proyectos/cuentas en proveedores de la nube son una buena práctica.
- Protección de ramas: Configure la protección para la rama
main(omaster) para que los pushes a ella solo sean posibles a través de Pull Requests con comprobaciones de CI obligatorias.