Exemples de Pipelines
5. Exemples Pràctics
5.1 Aplicació Node.js amb GitHub Actions
Estructura del projecte:
my-node-app/
├── .github/
│ └── workflows/
│ └── ci-cd.yml
├── src/
│ ├── app.js
│ └── routes/
├── tests/
│ └── app.test.js
├── package.json
├── Dockerfile
└── README.md
Fitxer: .github/workflows/ci-cd.yml
name: Node.js CI/CD
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
env:
NODE_VERSION: '18.x'
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
# Job 1: Build i Test
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Run linter
run: npm run lint
- name: Run unit tests
run: npm test
- name: Run coverage
run: npm run test:coverage
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v3
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: ./coverage/coverage-final.json
flags: unittests
- name: Build application
run: npm run build
- name: Archive production artifacts
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/
# Job 2: Security Scan
security-scan:
runs-on: ubuntu-latest
needs: build-and-test
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Run npm audit
run: npm audit --production
continue-on-error: true
- name: Run Snyk security scan
uses: snyk/actions/node@master
env:
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
# Job 3: Build Docker Image
build-docker:
runs-on: ubuntu-latest
needs: [build-and-test, security-scan]
if: github.ref == 'refs/heads/main'
permissions:
contents: read
packages: write
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Log in to Container Registry
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=branch
type=semver,pattern={{version}}
type=sha
- name: Build and push Docker image
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
# Job 4: Deploy to Staging
deploy-staging:
runs-on: ubuntu-latest
needs: build-docker
environment:
name: staging
url: https://staging.myapp.com
steps:
- name: Deploy to staging server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.STAGING_HOST }}
username: ${{ secrets.STAGING_USER }}
key: ${{ secrets.STAGING_SSH_KEY }}
script: |
cd /opt/myapp
docker-compose pull
docker-compose up -d
docker-compose exec -T web npm run migrate
# Job 5: Integration Tests
integration-tests:
runs-on: ubuntu-latest
needs: deploy-staging
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install dependencies
run: npm ci
- name: Run integration tests
env:
API_URL: https://staging.myapp.com
run: npm run test:integration
# Job 6: Deploy to Production
deploy-production:
runs-on: ubuntu-latest
needs: integration-tests
environment:
name: production
url: https://myapp.com
steps:
- name: Deploy to production server
uses: appleboy/ssh-action@master
with:
host: ${{ secrets.PROD_HOST }}
username: ${{ secrets.PROD_USER }}
key: ${{ secrets.PROD_SSH_KEY }}
script: |
cd /opt/myapp
docker-compose pull
docker-compose up -d
docker-compose exec -T web npm run migrate
- name: Verify deployment
run: |
curl --fail https://myapp.com/health || exit 1
- name: Notify team
uses: 8398a7/action-slack@v3
with:
status: ${{ job.status }}
text: 'Deployment to production completed!'
webhook_url: ${{ secrets.SLACK_WEBHOOK }}
if: always()
Explicació del pipeline:
Aquest pipeline té sis jobs que s'executen en seqüència:
-
build-and-test: Instal·la dependències, executa linter, tests unitaris i genera cobertura de codi. Aquest és el primer filtre de qualitat.
-
security-scan: Escaneja vulnerabilitats conegudes amb npm audit i Snyk. Això assegura que no despleguem codi amb problemes de seguretat coneguts.
-
build-docker: Construeix la imatge Docker i la puja al registre. Només s'executa si estem a la branca main i els passos anteriors han tingut èxit.
-
deploy-staging: Desplega l'aplicació a l'entorn de staging utilitzant SSH. Això crea un entorn idèntic a producció per fer proves finals.
-
integration-tests: Executa proves d'integració contra l'entorn de staging per verificar que tot funciona correctament en un entorn real.
-
deploy-production: Si tot ha anat bé, desplega a producció. Aquest job requereix aprovació manual (configurat amb
environment: production).
5.2 Aplicació Java amb GitLab CI
Fitxer: .gitlab-ci.yml
stages:
- build
- test
- quality
- package
- deploy-staging
- deploy-production
variables:
MAVEN_OPTS: "-Dmaven.repo.local=$CI_PROJECT_DIR/.m2/repository"
MAVEN_CLI_OPTS: "--batch-mode --errors --fail-at-end --show-version"
DOCKER_DRIVER: overlay2
DOCKER_TLS_CERTDIR: "/certs"
# Template per jobs amb Maven
.maven-job:
image: maven:3.9-openjdk-17
cache:
paths:
- .m2/repository
key: ${CI_COMMIT_REF_SLUG}
# Stage: Build
build:
extends: .maven-job
stage: build
script:
- mvn $MAVEN_CLI_OPTS clean compile
artifacts:
paths:
- target/classes
expire_in: 1 day
# Stage: Test
unit-tests:
extends: .maven-job
stage: test
script:
- mvn $MAVEN_CLI_OPTS test
artifacts:
when: always
reports:
junit:
- target/surefire-reports/TEST-*.xml
paths:
- target/surefire-reports/
- target/site/jacoco/
expire_in: 1 week
coverage: '/Total.*?([0-9]{1,3})%/'
integration-tests:
extends: .maven-job
stage: test
services:
- name: postgres:15
alias: postgres
variables:
POSTGRES_DB: testdb
POSTGRES_USER: testuser
POSTGRES_PASSWORD: testpass
SPRING_DATASOURCE_URL: jdbc:postgresql://postgres:5432/testdb
script:
- mvn $MAVEN_CLI_OPTS verify -P integration-tests
artifacts:
when: always
reports:
junit:
- target/failsafe-reports/TEST-*.xml
# Stage: Quality
code-quality:
extends: .maven-job
stage: quality
script:
- mvn $MAVEN_CLI_OPTS sonar:sonar
-Dsonar.host.url=$SONAR_HOST_URL
-Dsonar.login=$SONAR_TOKEN
-Dsonar.projectKey=$CI_PROJECT_PATH_SLUG
only:
- main
- merge_requests
security-scan:
extends: .maven-job
stage: quality
script:
- mvn $MAVEN_CLI_OPTS dependency-check:check
artifacts:
paths:
- target/dependency-check-report.html
expire_in: 1 week
allow_failure: true
# Stage: Package
package:
extends: .maven-job
stage: package
script:
- mvn $MAVEN_CLI_OPTS package -DskipTests
artifacts:
paths:
- target/*.jar
expire_in: 1 month
only:
- main
- tags
docker-build:
stage: package
image: docker:24.0
services:
- docker:24.0-dind
before_script:
- docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
script:
- docker build -t $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA .
- docker build -t $CI_REGISTRY_IMAGE:latest .
- docker push $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA
- docker push $CI_REGISTRY_IMAGE:latest
only:
- main
- tags
# Stage: Deploy Staging
deploy-staging:
stage: deploy-staging
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan $STAGING_HOST >> ~/.ssh/known_hosts
script:
- |
ssh $STAGING_USER@$STAGING_HOST << 'EOF'
cd /opt/myapp
docker pull $CI_REGISTRY_IMAGE:latest
docker-compose up -d
docker-compose exec -T app java -jar app.jar --spring.profiles.active=staging db migrate
EOF
environment:
name: staging
url: https://staging.myapp.com
only:
- main
smoke-tests-staging:
stage: deploy-staging
needs: [deploy-staging]
script:
- apk add --no-cache curl
- curl --fail https://staging.myapp.com/actuator/health || exit 1
- echo "Staging deployment verified"
# Stage: Deploy Production
deploy-production:
stage: deploy-production
image: alpine:latest
before_script:
- apk add --no-cache openssh-client
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add -
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- ssh-keyscan $PROD_HOST >> ~/.ssh/known_hosts
script:
- |
ssh $PROD_USER@$PROD_HOST << 'EOF'
cd /opt/myapp
docker pull $CI_REGISTRY_IMAGE:$CI_COMMIT_TAG
docker-compose up -d
docker-compose exec -T app java -jar app.jar --spring.profiles.active=prod db migrate
EOF
environment:
name: production
url: https://myapp.com
only:
- tags
when: manual
smoke-tests-production:
stage: deploy-production
needs: [deploy-production]
script:
- apk add --no-cache curl
- curl --fail https://myapp.com/actuator/health || exit 1
- echo "Production deployment verified"
only:
- tags
Característiques destacades:
- Templates: S'utilitza
.maven-jobcom a template per reutilitzar configuració - Cache: Maven cache per accelerar builds
- Services: PostgreSQL com a servei per integration tests
- Artefactes: JUnit reports i cobertura de codi
- Desplegament manual a producció:
when: manualrequereix aprovació - Només en tags: Producció només es desplega quan es crea un tag (release)