d4c3affa2f
CI Pipeline / HTML Lint (push) Successful in 8s
Deploy QA / Build and Push (push) Successful in 16s
CI Pipeline / Build Docker Image (push) Successful in 1m7s
Deploy QA / Deploy to QA (push) Successful in 9s
CI Pipeline / Security Scan (push) Successful in 12s
CI Pipeline / HTML Lint (pull_request) Successful in 8s
CI Pipeline / Build Docker Image (pull_request) Successful in 58s
CI Pipeline / Security Scan (pull_request) Successful in 12s
229 lines
9.4 KiB
Markdown
229 lines
9.4 KiB
Markdown
# CI/CD Multi-Environment Pipeline POC
|
|
|
|
Prueba de concepto de un pipeline CI/CD multi-ambiente con Gitea Actions, Docker y Nginx.
|
|
|
|
## 📌 Resumen de la Arquitectura
|
|
|
|
Implementación de una estrategia de Integración y Despliegue Continuo (CI/CD) para múltiples entornos (QA, Staging, Producción) alojada en un único servidor VPS (Debian/Ubuntu). La arquitectura utiliza **Gitea** (como control de versiones, Registry de contenedores y Runner de acciones), **Docker** para la contenerización de la aplicación estática, y un servidor **Nginx** en el host (Host Nginx) que actúa como Reverse Proxy y terminador SSL.
|
|
|
|
## 🏗️ Estrategia de Infraestructura y Red
|
|
|
|
Para aislar los entornos en el mismo servidor sin conflictos de red, se implementó el siguiente flujo:
|
|
|
|
* **Puertos Internos Dockerizados:** Cada entorno corre en un contenedor Docker independiente, exponiendo un puerto único a nivel de `localhost` en el VPS (ej. QA en `8081`, Staging en `8082`, Prod en `8083`).
|
|
* **Reverse Proxy:** El servidor Nginx principal del host escucha el puerto `80` y `443` (públicos). Mediante bloques `server_name`, intercepta las peticiones a subdominios específicos (`practicas.qa.kubistudio.cloud`, `practicas.staging.kubistudio.cloud`, `practicas.prod.kubistudio.cloud`) y hace un `proxy_pass` hacia el puerto interno correspondiente.
|
|
* **Terminación SSL:** La seguridad HTTPS se maneja exclusivamente en el Nginx del host utilizando **Certbot (Let's Encrypt)**. Los contenedores de Docker solo manejan tráfico HTTP interno, evitando colisiones en el puerto `443`.
|
|
|
|
## ⚙️ Inyección Dinámica de Variables (Frontend Estático)
|
|
|
|
Para cumplir con la premisa de *Build Once, Deploy Anywhere* en una aplicación estática (HTML/JS/CSS servida por Nginx), se solucionó el problema de las variables de entorno de la siguiente manera:
|
|
|
|
1. **Variables en el Pipeline:** El pipeline de Gitea Actions pasa variables de entorno (Commit SHA, Branch, App Env, Build Date) mediante la bandera `-e` en el comando `docker run`.
|
|
2. **Entrypoint Interceptor:** Se utiliza un script personalizado `docker-entrypoint.sh` en el contenedor.
|
|
3. **Generación Runtime:** Antes de que el Nginx del contenedor arranque, el script lee las variables de entorno de Linux y genera dinámicamente un archivo estático `/usr/share/nginx/html/env-config.js` inyectando el objeto `window.__ENV__`.
|
|
4. **Consumo en Cliente:** El archivo `index.html` carga este script de configuración antes de ejecutar la lógica de la aplicación, permitiendo que la interfaz cambie de aspecto y muestre la metadata correcta según el entorno (QA, Staging o Prod).
|
|
|
|
## 🚀 Flujo de las Pipelines de CD y Ramas
|
|
|
|
El despliegue está dividido en tres flujos principales basados en ramas:
|
|
|
|
```
|
|
feature/*
|
|
|
|
|
PR
|
|
|
|
|
┌───┴───┐
|
|
│ dev │ ──(push)──► CI Pipeline ──► Deploy QA
|
|
└───┬───┘ (lint, build, (puerto 8081)
|
|
│ security scan)
|
|
PR docker registry
|
|
│ │
|
|
┌───┴───┐ │
|
|
│staging│ ──(push)────────┼──► Deploy Staging
|
|
└───┬───┘ │ (puerto 8082)
|
|
│ │
|
|
PR │
|
|
│ │
|
|
┌───┴───┐ │
|
|
│ main │ ──(push)────────┴──► Deploy Production
|
|
└───────┘ (aprobación) (puerto 8083)
|
|
```
|
|
|
|
### Detalle por Entorno
|
|
|
|
* **QA (Entorno de Pruebas):**
|
|
* **Triggers:** Push a la rama `dev`.
|
|
* **Build & Push:** Construye la imagen Docker inyectando argumentos de compilación (`--build-arg` con el Git SHA y Build Date) y la sube al Gitea Container Registry con las etiquetas `qa-latest` y `sha-<hash>`.
|
|
* **Deploy (SSH):** Se conecta por SSH al VPS, detiene y elimina el contenedor anterior (`cicd-qa`), levanta el nuevo en el puerto `8081` con `APP_ENV=qa` y valida el despliegue con un health check.
|
|
|
|
* **Staging (Entorno de Pre-producción):**
|
|
* **Triggers:** Push a la rama `staging`.
|
|
* **Build & Push:** Genera la imagen etiquetada para staging (`staging-latest`).
|
|
* **Deploy (SSH):** Detiene el contenedor anterior (`cicd-staging`), levanta el nuevo en el puerto `8082` con `APP_ENV=staging` y realiza smoke tests comprobando la variable de entorno expuesta en el de salud.
|
|
|
|
* **Production (Entorno de Producción):**
|
|
* **Triggers:** Push a la rama `main`.
|
|
* **Build & Push:** Genera la imagen inmutable de producción (`production-latest`, `stable` y `sha-<hash>`).
|
|
* **Deploy (SSH):** Guarda la imagen actual para rollback, levanta el nuevo contenedor en el puerto `8083` con `APP_ENV=production`. Si el health check falla, realiza un **rollback automático** restaurando la imagen anterior.
|
|
|
|
## Ejecución Local
|
|
|
|
### Con docker-compose
|
|
|
|
```yaml
|
|
version: '3.8'
|
|
|
|
services:
|
|
cicd-qa:
|
|
build: .
|
|
ports:
|
|
- "8081:80"
|
|
environment:
|
|
APP_ENV: qa
|
|
APP_VERSION: dev-local
|
|
GIT_COMMIT: local
|
|
GIT_BRANCH: dev
|
|
BUILD_DATE: ${BUILD_DATE:-unknown}
|
|
DEPLOY_TIME: ${DEPLOY_TIME:-unknown}
|
|
BUILD_NUMBER: "0"
|
|
|
|
cicd-staging:
|
|
build: .
|
|
ports:
|
|
- "8082:80"
|
|
environment:
|
|
APP_ENV: staging
|
|
APP_VERSION: staging-local
|
|
GIT_COMMIT: local
|
|
GIT_BRANCH: staging
|
|
BUILD_DATE: ${BUILD_DATE:-unknown}
|
|
DEPLOY_TIME: ${DEPLOY_TIME:-unknown}
|
|
BUILD_NUMBER: "0"
|
|
|
|
cicd-prod:
|
|
build: .
|
|
ports:
|
|
- "8083:80"
|
|
environment:
|
|
APP_ENV: production
|
|
APP_VERSION: 1.0.0
|
|
DEPLOY_TIME: ${DEPLOY_TIME:-unknown}
|
|
```
|
|
|
|
### Manual
|
|
|
|
```bash
|
|
# Build
|
|
docker build \
|
|
--build-arg APP_VERSION=1.0.0 \
|
|
--build-arg BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
|
--build-arg GIT_COMMIT=$(git rev-parse --short HEAD) \
|
|
--build-arg GIT_BRANCH=$(git rev-parse --abbrev-ref HEAD) \
|
|
-t cicd-poc:latest .
|
|
|
|
# Run
|
|
docker run -d \
|
|
--name cicd-qa \
|
|
-p 8081:80 \
|
|
-e APP_ENV=qa \
|
|
-e APP_VERSION=dev-local \
|
|
-e GIT_COMMIT=$(git rev-parse --short HEAD) \
|
|
-e GIT_BRANCH=dev \
|
|
-e BUILD_DATE=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
|
-e DEPLOY_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
|
-e BUILD_NUMBER="1" \
|
|
cicd-poc:latest
|
|
|
|
# Test
|
|
curl http://localhost:8081/health
|
|
```
|
|
|
|
## Variables de Entorno del Contenedor
|
|
|
|
| Variable | Obligatorio | Default | Descripción |
|
|
|-----------------|-------------|---------------|---------------------------------------|
|
|
| `APP_ENV` | No | `development` | Ambiente: qa, staging, production |
|
|
| `APP_VERSION` | No | `0.0.0` | Versión de la aplicación |
|
|
| `BUILD_DATE` | No | *(vacío)* | Fecha ISO del build |
|
|
| `GIT_COMMIT` | No | *(vacío)* | SHA corto del commit |
|
|
| `GIT_BRANCH` | No | *(vacío)* | Rama de git |
|
|
| `DEPLOY_TIME` | No | *(vacío)* | Timestamp del deploy |
|
|
| `BUILD_NUMBER` | No | *(vacío)* | Número de build de la pipeline |
|
|
|
|
## Rollback Manual
|
|
|
|
### Production
|
|
|
|
```bash
|
|
ssh user@deploy-host
|
|
|
|
# Rollback a stable tag
|
|
docker stop cicd-prod && docker rm cicd-prod
|
|
docker run -d \
|
|
--name cicd-prod \
|
|
--restart unless-stopped \
|
|
-p 8083:80 \
|
|
-e APP_ENV=production \
|
|
-e APP_VERSION=rollback \
|
|
-e DEPLOY_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
|
registry-url/image-name:stable
|
|
|
|
# O con un SHA específico
|
|
docker run -d \
|
|
--name cicd-prod \
|
|
--restart unless-stopped \
|
|
-p 8083:80 \
|
|
-e APP_ENV=production \
|
|
-e APP_VERSION=rollback \
|
|
-e DEPLOY_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ") \
|
|
registry-url/image-name:sha-SHA_DEL_COMMIT
|
|
```
|
|
|
|
### QA / Staging
|
|
|
|
```bash
|
|
# QA
|
|
docker stop cicd-qa && docker rm cicd-qa
|
|
docker run -d --name cicd-qa -p 8081:80 ... registry-url/image-name:sha-SHA_ANTERIOR
|
|
|
|
# Staging
|
|
docker stop cicd-staging && docker rm cicd-staging
|
|
docker run -d --name cicd-staging -p 8082:80 ... registry-url/image-name:sha-SHA_ANTERIOR
|
|
```
|
|
|
|
## Endpoints
|
|
|
|
| Endpoint | Descripción |
|
|
|-----------|--------------------------------------|
|
|
| `/` | Aplicación web |
|
|
| `/health` | Health check (JSON) |
|
|
|
|
### Ejemplo `/health`
|
|
|
|
```json
|
|
{
|
|
"status": "ok",
|
|
"env": "production",
|
|
"version": "release-a1b2c3d",
|
|
"timestamp": "2024-01-15T14:23:00+00:00"
|
|
}
|
|
```
|
|
|
|
## Estructura del Proyecto
|
|
|
|
```
|
|
.
|
|
├── src/
|
|
│ └── index.html # App web (UI completa self-contained)
|
|
├── .gitea/workflows/
|
|
│ ├── ci.yml # CI: lint + build + security scan
|
|
│ ├── deploy-qa.yml # Deploy a QA (push a dev)
|
|
│ ├── deploy-staging.yml # Deploy a Staging (push a staging)
|
|
│ └── deploy-prod.yml # Deploy a Producción (push a main + aprobación)
|
|
├── docker-entrypoint.sh # Entrypoint que genera env-config.js en runtime
|
|
├── Dockerfile # Multi-stage build
|
|
├── healthcheck.sh # Script de health check
|
|
├── nginx.conf # Configuración de nginx
|
|
└── README.md # Este archivo
|
|
```
|