Saltar al contenido principal

Clase 08 — YAML en CI/CD (GitHub Actions & GitLab CI)

GitHub Actions

Estructura básica

# .github/workflows/ci.yml
name: CI Pipeline

on:
push:
branches: [main, develop]
pull_request:
branches: [main]

jobs:
nombre_job:
runs-on: ubuntu-latest
steps:
- name: Paso 1
run: echo "Hola"

Eventos (triggers)

on:
push:
branches:
- main
- 'release/**'
paths:
- 'src/**'
- '!src/**/*.md' # Ignorar cambios en .md
tags:
- 'v*'

pull_request:
branches: [main]
types: [opened, synchronize, reopened]

schedule:
- cron: '0 6 * * 1-5' # Lun-Vie a las 6AM UTC

workflow_dispatch:
inputs:
environment:
description: 'Ambiente de deploy'
required: true
default: 'staging'
type: choice
options:
- staging
- production

Ejemplo completo: CI/CD para API Python

name: CI/CD Pipeline

on:
push:
branches: [main]
pull_request:
branches: [main]

env:
PYTHON_VERSION: "3.11"
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
# ─── LINT ───
lint:
name: Lint & Format
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
- name: Verificar formato
run: |
pip install flake8 black isort
black --check .
isort --check-only .
flake8 .

# ─── TEST ───
test:
name: Tests
runs-on: ubuntu-latest
needs: lint
services:
postgres:
image: postgres:16-alpine
env:
POSTGRES_DB: test_db
POSTGRES_USER: test
POSTGRES_PASSWORD: test
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
cache: pip
- name: Ejecutar tests
env:
DATABASE_URL: postgresql://test:test@localhost:5432/test_db
run: |
pip install -r requirements.txt
pip install pytest pytest-cov
pytest tests/ --cov=app --cov-report=xml -v

# ─── BUILD ───
build:
name: Build & Push Docker
runs-on: ubuntu-latest
needs: test
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v4
- name: Login al registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build y Push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}:${{ github.sha }}

# ─── DEPLOY ───
deploy-staging:
name: Deploy Staging
runs-on: ubuntu-latest
needs: build
environment:
name: staging
url: https://staging.mi-app.com
steps:
- uses: actions/checkout@v4
- name: Deploy a staging
run: echo "Deploying to staging..."

deploy-production:
name: Deploy Production
runs-on: ubuntu-latest
needs: deploy-staging
environment:
name: production
url: https://mi-app.com
steps:
- uses: actions/checkout@v4
- name: Deploy a producción
run: echo "Deploying to production..."

Matrix Strategy

jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
python-version: ["3.10", "3.11", "3.12"]
exclude:
- os: macos-latest
python-version: "3.10"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- run: |
pip install -r requirements.txt
pytest tests/

GitLab CI/CD

Estructura básica

# .gitlab-ci.yml
stages:
- test
- build
- deploy

variables:
APP_NAME: mi-app

test:
stage: test
image: python:3.11
script:
- pip install -r requirements.txt
- pytest tests/

Ejemplo completo: GitLab CI/CD

stages:
- lint
- test
- build
- deploy

variables:
PYTHON_VERSION: "3.11"
REGISTRY: $CI_REGISTRY
IMAGE: $CI_REGISTRY_IMAGE

# ─── Templates reutilizables ───
.python_template: &python_template
image: python:${PYTHON_VERSION}
before_script:
- pip install --upgrade pip
- pip install -r requirements.txt

.docker_template: &docker_template
image: docker:24
services:
- docker:24-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY

.deploy_template: &deploy_template
image: bitnami/kubectl:latest
before_script:
- kubectl config set-cluster k8s --server=$K8S_SERVER
- kubectl config set-credentials deployer --token=$K8S_TOKEN
- kubectl config use-context default

# ─── LINT ───
lint:
<<: *python_template
stage: lint
script:
- pip install flake8 black
- black --check .
- flake8 .

# ─── TEST ───
test:
<<: *python_template
stage: test
services:
- postgres:16-alpine
variables:
POSTGRES_DB: test_db
POSTGRES_USER: test
POSTGRES_PASSWORD: test
DATABASE_URL: "postgresql://test:test@postgres:5432/test_db"
script:
- pip install pytest pytest-cov
- pytest tests/ --cov=app --cov-report=xml -v

# ─── BUILD ───
build:
<<: *docker_template
stage: build
script:
- docker build -t $IMAGE:$CI_COMMIT_SHA -t $IMAGE:latest .
- docker push $IMAGE:$CI_COMMIT_SHA
- docker push $IMAGE:latest
rules:
- if: '$CI_COMMIT_BRANCH == "main"'

# ─── DEPLOY ───
deploy_staging:
<<: *deploy_template
stage: deploy
script:
- kubectl set image deployment/$CI_PROJECT_NAME app=$IMAGE:$CI_COMMIT_SHA
- kubectl rollout status deployment/$CI_PROJECT_NAME --timeout=180s
environment:
name: staging
rules:
- if: '$CI_COMMIT_BRANCH == "main"'

deploy_production:
<<: *deploy_template
stage: deploy
script:
- kubectl set image deployment/$CI_PROJECT_NAME app=$IMAGE:$CI_COMMIT_SHA
- kubectl rollout status deployment/$CI_PROJECT_NAME --timeout=300s
environment:
name: production
rules:
- if: '$CI_COMMIT_BRANCH == "main"'
when: manual

Comparación rápida

ConceptoGitHub ActionsGitLab CI
Archivo.github/workflows/*.yml.gitlab-ci.yml
Ejecuciónjobsstages + jobs
Runnerruns-onimage / tags
Variablesenv / secretsvariables
Condicionalifrules / only
Cachéactions/cachecache
Artefactosactions/upload-artifactartifacts
Manualworkflow_dispatchwhen: manual
ReutilizarReusable workflowsinclude / anclas

Ejercicios

  1. Creá un GitHub Actions workflow para una app Node.js con: lint, test y build de Docker
  2. Creá un .gitlab-ci.yml con stages de test, build y deploy usando anclas para evitar duplicación
  3. Agregá una matrix strategy para testear en múltiples versiones de Node.js