Continuous Integration (Integración Continua)
La CI es la práctica de integrar cambios de código en un repositorio compartido múltiples veces al día, con validación automática mediante builds y tests.
Continuous Delivery vs Continuous Deployment
Entrega Continua: El código está siempre en estado desplegable. Los despliegues a producción requieren aprobación manual.
Despliegue Continuo: Todo cambio que pasa las pruebas automáticas se despliega automáticamente a producción sin intervención humana.
Un pipeline CI típico incluye:
stages:
- lint
- test
- build
lint-code:
stage: lint
image: node:18
script:
- npm ci
- npm run lint
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
- if: $CI_COMMIT_BRANCH == "main"
unit-tests:
stage: test
image: node:18
script:
- npm ci
- npm run test:unit
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
artifacts:
reports:
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
build-app:
stage: build
image: node:18
script:
- npm ci
- npm run build
artifacts:
paths:
- dist/
expire_in: 1 hour
rules:
- if: $CI_COMMIT_BRANCH == "main"
Nota: Los artefactos se pasan entre stages [1]
validate-merge-request:
stage: test
script:
- echo "Validando Merge Request..."
- npm run test
rules:
- if: $CI_PIPELINE_SOURCE == "merge_request_event"
only:
- merge_requests
Se ejecuta solo cuando se crea o actualiza un MR
build-production:
stage: build
image: node:18
script:
- npm ci
- NODE_ENV=production npm run build
artifacts:
paths:
- dist/
expire_in: 7 days
only:
- main
Los artefactos son el resultado compilado listo para desplegar
deploy-s3-staging:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
dependencies:
- build-production
script:
- aws s3 sync ./dist s3://$S3_BUCKET_STAGING/ --delete
- echo "Deploy to staging successful"
environment:
name: staging
url: https://staging.example.com
only:
- main
deploy-production:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
dependencies:
- build-production
script:
- aws s3 sync ./dist s3://$S3_BUCKET_PROD/ --delete
- echo "Deploy to production successful"
environment:
name: production
url: https://www.example.com
when: manual
only:
- main
when: manual requiere aprobación humana
Nunca guardes secretos en código
deploy-s3:
script:
- aws s3 cp ./dist s3://$S3_BUCKET/ --recursive
variables:
AWS_DEFAULT_REGION: us-east-1
Secretos configurados en GitLab UI:
AWS_ACCESS_KEY_ID (masked, protected)AWS_SECRET_ACCESS_KEY (masked, protected)S3_BUCKET (puede ser pública)Reemplazar la versión actual directamente
Ventajas: Simple, bajo costo
Desventajas: Downtime, rollback más lento
Dos entornos idénticos: Blue (actual) y Green (nuevo)
Despliegue gradual a un subconjunto de usuarios
Registrar eventos clave del despliegue
deploy:
script:
- echo "Starting deployment at $(date)"
- aws s3 sync ./dist s3://$S3_BUCKET/
- echo "Deployment completed successfully"
after_script:
- echo "Cleaning up resources..."
Logs centralizados en GitLab CI/CD Jobs
Verificar que la aplicación funciona después del despliegue
deploy-production:
stage: deploy
script:
- aws s3 sync ./dist s3://$S3_BUCKET_PROD/
- echo "Waiting for deployment to propagate..."
- sleep 30
- |
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://www.example.com/health)
if [ $STATUS -ne 200 ]; then
echo "Health check failed with status $STATUS"
exit 1
fi
- echo "Health check passed!"
Tu aplicación debe exponer un endpoint:
GET /health
Respuesta:
{
"status": "healthy",
"timestamp": "2025-11-13T21:00:00Z",
"version": "1.2.3"
}
Crear un pipeline en GitLab que:
main
my-single-spa-app/
├── src/
├── dist/ # Build output
├── package.json
├── webpack.config.js
└── .gitlab-ci.yml
{
"scripts": {
"lint": "eslint src --ext .js,.jsx",
"test": "jest",
"build": "webpack --mode production",
"build:local": "webpack serve --mode development"
},
"devDependencies": {
"webpack": "^5.88.0",
"webpack-cli": "^5.1.0",
"single-spa": "^5.9.5",
"eslint": "^8.45.0",
"jest": "^29.6.0"
}
}
stages:
- lint
- test
- build
- deploy
variables:
AWS_DEFAULT_REGION: us-east-1
NODE_VERSION: "18"
# Template para jobs de Node.js
.node-template: &node-template
image: node:${NODE_VERSION}
cache:
key: ${CI_COMMIT_REF_SLUG}
paths:
- node_modules/
before_script:
- npm ci
lint:
<<: *node-template
stage: lint
script:
- npm run lint
rules:
- if: $CI_COMMIT_BRANCH == "main"
test:
<<: *node-template
stage: test
script:
- npm run test -- --coverage
coverage: '/All files[^|]*\|[^|]*\s+([\d\.]+)/'
artifacts:
reports:
junit: junit.xml
coverage_report:
coverage_format: cobertura
path: coverage/cobertura-coverage.xml
rules:
- if: $CI_COMMIT_BRANCH == "main"
build:
<<: *node-template
stage: build
script:
- NODE_ENV=production npm run build
- ls -lah dist/
artifacts:
paths:
- dist/
expire_in: 1 day
rules:
- if: $CI_COMMIT_BRANCH == "main"
deploy-s3:
stage: deploy
image: registry.gitlab.com/gitlab-org/cloud-deploy/aws-base:latest
dependencies:
- build
script:
- echo "Deploying to S3 bucket $S3_BUCKET..."
- aws s3 sync ./dist s3://$S3_BUCKET/ --delete --acl public-read
- echo "Deployment completed successfully!"
- echo "URL: https://$S3_BUCKET.s3.amazonaws.com/index.html"
environment:
name: production
url: https://$S3_BUCKET.s3.amazonaws.com
rules:
- if: $CI_COMMIT_BRANCH == "main"
Settings → CI/CD → Variables
| Key | Value | Protected | Masked |
|---|---|---|---|
AWS_ACCESS_KEY_ID |
AKIA... | ✅ | ✅ |
AWS_SECRET_ACCESS_KEY |
wJalr... | ✅ | ✅ |
S3_BUCKET |
my-single-spa-app | ✅ | ❌ |
{
"Version": "2012-10-17",
"Statement": [{
"Sid": "PublicReadGetObject",
"Effect": "Allow",
"Principal": "*",
"Action": "s3:GetObject",
"Resource": "arn:aws:s3:::my-single-spa-app/*"
}]
}
const { merge } = require('webpack-merge');
const singleSpaDefaults = require('webpack-config-single-spa-react');
module.exports = (webpackConfigEnv, argv) => {
const defaultConfig = singleSpaDefaults({
orgName: 'myorg',
projectName: 'my-app',
webpackConfigEnv,
argv,
});
return merge(defaultConfig, {
output: {
publicPath: process.env.NODE_ENV === 'production'
? 'https://my-single-spa-app.s3.amazonaws.com/'
: 'http://localhost:8080/',
},
});
};
feature/new-component
main
main
deploy-s3:
# ... código anterior ...
after_script:
- |
echo "Running health check..."
sleep 15
STATUS=$(curl -s -o /dev/null -w "%{http_code}" https://$S3_BUCKET.s3.amazonaws.com/index.html)
if [ $STATUS -ne 200 ]; then
echo "❌ Health check failed with status $STATUS"
exit 1
fi
echo "✅ Health check passed!"
notify-slack:
stage: .post
image: curlimages/curl:latest
script:
- |
curl -X POST $SLACK_WEBHOOK_URL \
-H 'Content-Type: application/json' \
-d "{\"text\":\"✅ Deploy exitoso: $CI_COMMIT_MESSAGE\"}"
rules:
- if: $CI_COMMIT_BRANCH == "main"
when: on_success
Variable: SLACK_WEBHOOK_URL (masked)
✅ CI/CD: Conceptos de integración y entrega continua
✅ Pipeline CI: Lint, test, build
✅ Pipeline CD: Artefactos, deploy a S3, producción
✅ Seguridad: Secrets, dependency scanning
✅ Estrategias: In-place, Blue-Green, Canary
✅ Observabilidad: Logs, health checks, rollback
✅ Práctica: Pipeline completo para single-spa