Salta el contingut

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:

  1. build-and-test: Instal·la dependències, executa linter, tests unitaris i genera cobertura de codi. Aquest és el primer filtre de qualitat.

  2. security-scan: Escaneja vulnerabilitats conegudes amb npm audit i Snyk. Això assegura que no despleguem codi amb problemes de seguretat coneguts.

  3. 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.

  4. deploy-staging: Desplega l'aplicació a l'entorn de staging utilitzant SSH. Això crea un entorn idèntic a producció per fer proves finals.

  5. integration-tests: Executa proves d'integració contra l'entorn de staging per verificar que tot funciona correctament en un entorn real.

  6. 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-job com 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: manual requereix aprovació
  • Només en tags: Producció només es desplega quan es crea un tag (release)