Módulo 5 — DevOps y Automatización del SDLC
UTEC Posgrado | Arquitectura de Soluciones Multinube
Docente: Aldo Trucios
En este laboratorio implementarás un pipeline de despliegue Canary sobre Google Cloud Run usando GitHub Actions. El pipeline automatiza el ciclo completo:
- Build → construye la imagen Docker y la sube a Artifact Registry
- Canary (10%) → despliega la nueva versión con solo el 10% del tráfico
- Aprobación manual → un revisor decide si promover al 100% o hacer rollback
- Promote / Rollback → según la decisión, el tráfico se ajusta automáticamente
push a main
│
▼
┌─────────────────────┐
│ build_and_canary │ ← Build imagen + deploy canary 10%
└────────┬────────────┘
│ (si NO es primer deploy)
▼
┌─────────────────────┐
│ promote_or_rollback │ ← ⏸ Aprobación manual en GitHub
└────────┬────────────┘
│
┌────┴────┐
│ │
✅ OK ❌ Rechazado / Fallo
│ │
▼ ▼
100% rollback_canary
tráfico (vuelve a versión estable)
| Herramienta | Versión mínima | Verificar con |
|---|---|---|
| Git | 2.x | git --version |
| Docker Desktop | 24.x | docker --version |
Google Cloud CLI (gcloud) |
400+ | gcloud --version |
| Node.js | 18 LTS | node --version |
💡 Alternativa sin instalación local: usa GitHub Codespaces. Abre el repositorio en GitHub →
Code→Codespaces→Create codespace on main. Todas las herramientas ya están preinstaladas.
- ✅ Cuenta de GitHub (gratuita)
- ✅ Cuenta de Google Cloud Platform con proyecto activo
- ✅ Proyecto GCP con billing habilitado
- ✅ Acceso de Editor o Owner al proyecto GCP
Ejecuta los siguientes comandos en tu terminal (o en Cloud Shell):
gcloud config set project utec-posgrado-01
gcloud services enable \
run.googleapis.com \
artifactregistry.googleapis.com \
cloudbuild.googleapis.com \
iam.googleapis.comVerifica que estén activas:
gcloud services list --enabled \
--filter="name:(run.googleapis.com OR artifactregistry.googleapis.com)"Artifact Registry es donde se almacenarán las imágenes Docker que construya el pipeline.
gcloud artifacts repositories create app-gemini \
--repository-format=docker \
--location=us-central1 \
--description="Repositorio de imágenes Docker — UTEC Posgrado"Verifica que se creó correctamente:
gcloud artifacts repositories list --location=us-central1GitHub Actions necesita credenciales para autenticarse en GCP. Crearemos una Service Account con los permisos mínimos necesarios.
# Crear la service account
gcloud iam service-accounts create github-actions-sa-<initial_name> \
--display-name="GitHub Actions — UTEC Posgrado" \
--description="SA para pipeline CI/CD desde GitHub Actions"Asignar los roles necesarios:
PROJECT_ID="utec-posgrado-01"
SA_EMAIL="github-actions-sa@${PROJECT_ID}.iam.gserviceaccount.com"
# Permisos para Cloud Run (crear y actualizar servicios)
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/run.admin"
# Permisos para Artifact Registry (subir imágenes)
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/artifactregistry.writer"
# Permisos para actuar como service account en deployments
gcloud projects add-iam-policy-binding $PROJECT_ID \
--member="serviceAccount:${SA_EMAIL}" \
--role="roles/iam.serviceAccountUser"Verifica los roles asignados:
gcloud projects get-iam-policy $PROJECT_ID \
--flatten="bindings[].members" \
--filter="bindings.members:github-actions-sa"gcloud iam service-accounts keys create gcp-sa-key.json \
--iam-account="github-actions-sa@utec-posgrado-01.iam.gserviceaccount.com"
⚠️ IMPORTANTE: El archivogcp-sa-key.jsoncontiene credenciales sensibles.
- Nunca lo subas a GitHub (está en el
.gitignore)- Guárdalo temporalmente para copiarlo como secreto en el siguiente paso
- Elimínalo de tu máquina después de configurar el secreto
Visualiza el contenido para copiarlo:
cat gcp-sa-key.jsonEl job promote_or_rollback usa un Environment de GitHub para requerir aprobación manual. Debes crearlo antes de ejecutar el pipeline.
- Ve a tu repositorio en GitHub
- Haz clic en Settings → Environments
- Haz clic en "New environment"
- Nombre:
dev - Haz clic en "Configure environment"
- En "Required reviewers": agrega tu propio usuario (o el del equipo)
- Haz clic en "Save protection rules"
🔐 Esto hace que el job
promote_or_rollbackpause y espere tu aprobación en GitHub antes de ejecutarse.
Ve a tu repositorio → Settings → Secrets and variables → Actions
Haz clic en "New repository secret" para cada uno:
| Secret Name | Valor | Descripción |
|---|---|---|
GCP_SA_KEY |
(contenido completo del archivo gcp-sa-key.json) |
Credenciales de la Service Account |
ALUMNO_ID |
tu-nombre-o-alias |
Identificador único del alumno (sin espacios, en minúsculas, ej: jperez) |
ENVIRONMENT_NAME |
dev |
Nombre del entorno de despliegue |
💡 Para
GCP_SA_KEY: copia todo el contenido JSON del archivo, incluyendo las llaves{ }.
Estas ya están hardcodeadas en el workflow, pero puedes moverlas aquí si prefieres:
| Variable Name | Valor |
|---|---|
GCP_PROJECT_ID |
utec-posgrado-01 |
GCP_REGION |
us-central1 |
En el primer push, el pipeline detecta que el servicio de Cloud Run no existe todavía y despliega con el 100% del tráfico directamente (no hay Canary en el primer deploy, no habría versión estable a la que volver).
git add .
git commit -m "feat: primer despliegue a Cloud Run"
git push origin mainObserva en GitHub → Actions cómo:
- Se ejecuta el job
build_and_canary - El step "Check if Service Exists" detecta que es primer deploy (
is_first_deploy=true) - El job
promote_or_rollbackse salta (condiciónif: ... == 'false')
A partir del segundo push, el pipeline activa el flujo Canary completo:
# Haz algún cambio en la aplicación, por ejemplo en src/app/app.component.html
git add .
git commit -m "feat: nueva versión con cambio visible"
git push origin mainEl pipeline:
- ✅
build_and_canary— construye la imagen y asigna 90% tráfico a la versión estable / 10% a la nueva (Canary) - ⏸
promote_or_rollback— se pausa esperando tu aprobación en GitHub - Ve a Actions → haz clic en el run activo → verás el botón "Review deployments"
- Selecciona el environment
devy elige:- ✅ Approve → el Canary sube al 100% del tráfico
- ❌ Reject → se dispara
rollback_canaryy la versión estable vuelve al 100%
# Ver el servicio desplegado
SERVICE_NAME="gemini-angular-TU_ALUMNO_ID-dev"
gcloud run services describe $SERVICE_NAME \
--region us-central1 \
--format="table(status.url,status.traffic)"
# Ver la distribución de tráfico entre revisiones
gcloud run services describe $SERVICE_NAME \
--region us-central1 \
--format="value(status.traffic)"- Ve a console.cloud.google.com
- Navega a Cloud Run
- Haz clic en tu servicio
gemini-angular-TU_ALUMNO_ID-dev - En la pestaña "Traffic" verás las revisiones y sus porcentajes
angular-cloud-run-v2/
├── .github/
│ └── workflows/
│ ├── utec.yml ← Pipeline principal (este laboratorio)
│ └── workflow-reuse.yml ← Workflow reutilizable (referencia)
├── src/
│ └── app/
│ ├── app.component.html
│ ├── app.component.scss
│ ├── app.component.ts
│ ├── app.config.ts
│ └── app.routes.ts
├── Dockerfile ← Imagen Docker de la app Angular
├── nginx.conf ← Configuración del servidor web
├── entrypoint.sh
├── angular.json
├── package.json
└── README.md ← Este archivo
outputs:
is_first_deploy: ${{ steps.check_service.outputs.is_first_deploy }}El job build_and_canary expone un output que los jobs siguientes consumen con needs.build_and_canary.outputs.is_first_deploy. Así el pipeline toma decisiones basadas en el estado real de la infraestructura.
# Solo corre si NO es el primer deploy
if: needs.build_and_canary.outputs.is_first_deploy == 'false'
# Solo corre si hubo fallo o cancelación en el job anterior
if: always() && ... && (needs.promote_or_rollback.result == 'failure' || needs.promote_or_rollback.result == 'cancelled')environment: devCuando un job referencia un Environment configurado con Required reviewers, GitHub pausa la ejecución y muestra un botón de aprobación. Si el revisor rechaza, el job queda en estado cancelled, lo que activa el job de rollback.
SERVICE_NAME="gemini-angular-${{ secrets.ALUMNO_ID }}-${{ secrets.ENVIRONMENT_NAME }}"
IMAGE_TAG="..../app-${{ secrets.ALUMNO_ID }}:${{ github.sha }}"Cada alumno usa su propio ALUMNO_ID como secreto, lo que genera nombres de servicio e imágenes únicos. Así todos trabajan en el mismo proyecto GCP sin colisiones.
# Reautenticar Docker con GCP
gcloud auth configure-docker us-central1-docker.pkg.devVerifica que la Service Account tiene los roles correctos:
gcloud projects get-iam-policy utec-posgrado-01 \
--flatten="bindings[].members" \
--filter="bindings.members:github-actions-sa"El secreto no está configurado en el repositorio. Ve a Settings → Secrets and variables → Actions y verifica que GCP_SA_KEY existe con el contenido JSON completo.
Verifica que el output is_first_deploy sea false. Esto solo ocurre a partir del segundo push. En el primer deploy, ese job se omite por diseño.
# Verifica que el flag --allow-unauthenticated está activo
gcloud run services describe gemini-angular-TU_ALUMNO_ID-dev \
--region us-central1 \
--format="value(spec.template.spec.containers[0].image)"Para evitar cargos en GCP, elimina los recursos creados:
# Eliminar el servicio de Cloud Run
gcloud run services delete "gemini-angular-TU_ALUMNO_ID-dev" \
--region us-central1 --quiet
# Eliminar las imágenes del Artifact Registry
gcloud artifacts docker images delete \
"us-central1-docker.pkg.dev/utec-posgrado-01/app-gemini/app-TU_ALUMNO_ID" \
--delete-tags --quiet
# (Opcional) Eliminar la Service Account
gcloud iam service-accounts delete \
"github-actions-sa@utec-posgrado-01.iam.gserviceaccount.com" --quiet- GitHub Actions — Environments y aprobaciones
- GitHub Actions — Outputs entre jobs
- Google Cloud Run — Traffic splitting
- Artifact Registry — Autenticación Docker
- google-github-actions/auth
Material elaborado para UTEC Posgrado — Módulo 5, Sesión 1 | Docente: Aldo Trucios