38 CI/CD für eine containerisierte Angular-Anwendung

38.1 Einführung in CI/CD für Angular-Projekte

Continuous Integration und Continuous Delivery/Deployment (CI/CD) sind essentielle Praktiken in der modernen Softwareentwicklung. Für Angular-Anwendungen, insbesondere solche, die wie im vorherigen Kapitel beschrieben containerisiert wurden, bietet CI/CD erhebliche Vorteile:

In diesem Kapitel entwickeln wir eine umfassende CI/CD-Pipeline für unsere containerisierte Angular-Anwendung, die verschiedene Tools und Best Practices integriert.

38.2 CI/CD-Strategie und Workflow-Design

38.2.1 Pipeline-Architektur planen

Eine effektive CI/CD-Pipeline für Angular-Anwendungen sollte folgende Phasen umfassen:

  1. Code-Änderungen: Entwickler committen Code in das Repository
  2. Code-Qualitätsprüfung: Statische Codeanalyse, Linting und Formatüberprüfung
  3. Unit-Tests: Automatisierte Tests der Komponenten und Services
  4. Build: Kompilierung der Anwendung
  5. E2E-Tests: End-to-End-Tests der kompilierten Anwendung
  6. Image-Erstellung: Bauen des Docker-Images
  7. Sicherheitsscan: Überprüfung auf Sicherheitslücken
  8. Artifakt-Veröffentlichung: Veröffentlichung des Images in einer Registry
  9. Deployment: Bereitstellung in verschiedenen Umgebungen
  10. Smoke-Tests: Einfache Tests nach dem Deployment

Diese Phasen können je nach Umgebung (Development, Staging, Production) variieren oder zusammengefasst werden.

38.2.2 Branching-Strategie

Eine gut definierte Branching-Strategie ist grundlegend für jeden CI/CD-Workflow. Für Angular-Projekte empfehlen wir folgende Struktur:

Diese Strategie, auch bekannt als GitFlow oder eine Variation davon, unterstützt parallele Entwicklung und stabile Releases.

38.2.3 Umgebungen definieren

Typischerweise werden für Angular-Anwendungen mindestens drei Umgebungen verwendet:

  1. Development: Zum Testen während der Entwicklung
  2. Staging/QA: Zum Testen vor der Produktion
  3. Production: Live-Umgebung für Endbenutzer

Für jede Umgebung sollten spezifische Konfigurationen und Zugriffsberechtigungen definiert werden.

38.3 CI/CD mit GitLab CI

GitLab CI ist eine leistungsstarke Lösung für CI/CD-Pipelines, die tief in GitLab integriert ist.

38.3.1 GitLab CI-Konfiguration

Erstellen Sie eine .gitlab-ci.yml-Datei im Wurzelverzeichnis Ihres Projekts:

# Definieren der Stages für die Pipeline
stages:
  - validate
  - test
  - build
  - security
  - publish
  - deploy
  - verify

# Globale Variablen für alle Jobs
variables:
  DOCKER_REGISTRY: registry.gitlab.com
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE/angular-app
  KUBERNETES_NAMESPACE: angular-apps

# Cache-Konfiguration für npm
cache:
  key: ${CI_COMMIT_REF_SLUG}
  paths:
    - node_modules/

# Validierungsphase
lint:
  stage: validate
  image: node:20-alpine
  script:
    - npm ci
    - npm run lint
    - npm run format:check

# Testphase
unit-test:
  stage: test
  image: node:20-alpine
  script:
    - npm ci
    - npm run test:ci
  coverage: '/Statements\s+:\s+(\d+.?\d*)%/'
  artifacts:
    paths:
      - coverage/
    expire_in: 1 week

# Build-Phase
build-app:
  stage: build
  image: node:20-alpine
  script:
    - npm ci
    - npm run build -- --configuration production
  artifacts:
    paths:
      - dist/
    expire_in: 1 day

# Sicherheitsüberprüfung
dependency-scan:
  stage: security
  image: node:20-alpine
  script:
    - npm ci
    - npm audit --production
  allow_failure: true

# Container-Image erstellen und in Registry pushen
build-image:
  stage: publish
  image: docker:20.10.16
  services:
    - docker:20.10.16-dind
  variables:
    DOCKER_TLS_CERTDIR: "/certs"
  script:
    - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY
    - docker build -t $DOCKER_IMAGE:$CI_COMMIT_SHA -t $DOCKER_IMAGE:latest .
    - docker push $DOCKER_IMAGE:$CI_COMMIT_SHA
    - docker push $DOCKER_IMAGE:latest

# Container-Sicherheitsscan
container-scan:
  stage: security
  image: 
    name: aquasec/trivy:latest
    entrypoint: [""]
  variables:
    TRIVY_NO_PROGRESS: "true"
    TRIVY_CACHE_DIR: .trivycache/
  script:
    - trivy image --exit-code 0 --severity HIGH,CRITICAL $DOCKER_IMAGE:$CI_COMMIT_SHA
  cache:
    paths:
      - .trivycache/
  allow_failure: true

# Development-Deployment
deploy-dev:
  stage: deploy
  image: alpine/helm:3.12.0
  script:
    - helm upgrade --install angular-dev ./angular-app 
      --set image.repository=$DOCKER_IMAGE 
      --set image.tag=$CI_COMMIT_SHA 
      --namespace $KUBERNETES_NAMESPACE-dev
  environment:
    name: development
    url: https://dev.angular-app.example.com
  only:
    - develop

# Staging-Deployment
deploy-staging:
  stage: deploy
  image: alpine/helm:3.12.0
  script:
    - helm upgrade --install angular-staging ./angular-app 
      --set image.repository=$DOCKER_IMAGE 
      --set image.tag=$CI_COMMIT_SHA 
      --namespace $KUBERNETES_NAMESPACE-staging
  environment:
    name: staging
    url: https://staging.angular-app.example.com
  only:
    - main
  when: manual

# Production-Deployment
deploy-prod:
  stage: deploy
  image: alpine/helm:3.12.0
  script:
    - helm upgrade --install angular-prod ./angular-app 
      --set image.repository=$DOCKER_IMAGE 
      --set image.tag=$CI_COMMIT_SHA 
      --namespace $KUBERNETES_NAMESPACE-prod
  environment:
    name: production
    url: https://angular-app.example.com
  only:
    - main
  when: manual

# Smoke-Tests nach Deployment
smoke-test:
  stage: verify
  image: cypress/included:12.14.0
  script:
    - npm ci
    - npm run cypress:run -- --config baseUrl=$CI_ENVIRONMENT_URL
  only:
    - develop
    - main

38.3.2 GitLab CI/CD Variablen

Für eine sichere Konfiguration müssen verschiedene CI/CD-Variablen in GitLab definiert werden:

Diese können in den CI/CD-Einstellungen des Projekts oder der Gruppe in GitLab konfiguriert werden.

38.4 CI/CD mit GitHub Actions

GitHub Actions ist eine flexible CI/CD-Lösung direkt in GitHub integriert.

38.4.1 Workflow-Konfiguration

Erstellen Sie den Ordner .github/workflows und darin eine Datei wie angular-cicd.yml:

name: Angular CI/CD Pipeline

on:
  push:
    branches: [ main, develop ]
  pull_request:
    branches: [ main, develop ]

env:
  DOCKER_REGISTRY: ghcr.io
  IMAGE_NAME: angular-app
  HELM_CHART_PATH: ./angular-app

jobs:
  validate:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Lint code
        run: npm run lint
        
      - name: Check formatting
        run: npm run format:check

  test:
    needs: validate
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Run unit tests
        run: npm run test:ci
        
      - name: Upload coverage report
        uses: actions/upload-artifact@v3
        with:
          name: coverage-report
          path: coverage/
          
  build:
    needs: test
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Build Angular app
        run: npm run build -- --configuration production
        
      - name: Upload build artifacts
        uses: actions/upload-artifact@v3
        with:
          name: angular-dist
          path: dist/
  
  security:
    needs: build
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Set up Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
          cache: 'npm'
          
      - name: Install dependencies
        run: npm ci
        
      - name: Run npm audit
        run: npm audit --production
        continue-on-error: true
        
      - name: Run OWASP dependency check
        uses: dependency-check/Dependency-Check_Action@main
        with:
          project: 'Angular App'
          path: '.'
          format: 'HTML'
          out: 'reports'
          
      - name: Upload security reports
        uses: actions/upload-artifact@v3
        with:
          name: security-reports
          path: reports/
  
  publish:
    needs: [build, security]
    runs-on: ubuntu-latest
    if: github.event_name == 'push'
    permissions:
      contents: read
      packages: write
    steps:
      - uses: actions/checkout@v4
      
      - name: Download build artifacts
        uses: actions/download-artifact@v3
        with:
          name: angular-dist
          path: dist/
          
      - name: Set up Docker Buildx
        uses: docker/setup-buildx-action@v3
        
      - name: Login to GitHub Container Registry
        uses: docker/login-action@v3
        with:
          registry: ${{ env.DOCKER_REGISTRY }}
          username: ${{ github.actor }}
          password: ${{ secrets.GITHUB_TOKEN }}
          
      - name: Extract Docker metadata
        id: meta
        uses: docker/metadata-action@v5
        with:
          images: ${{ env.DOCKER_REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}
          
      - 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 }}
          
      - name: Scan image for vulnerabilities
        uses: aquasecurity/trivy-action@master
        with:
          image-ref: ${{ env.DOCKER_REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }}:${{ github.sha }}
          format: 'sarif'
          output: 'trivy-results.sarif'
          severity: 'CRITICAL,HIGH'
          
      - name: Upload Trivy scan results
        uses: github/codeql-action/upload-sarif@v2
        with:
          sarif_file: 'trivy-results.sarif'
  
  deploy-dev:
    needs: publish
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/develop'
    environment:
      name: development
      url: https://dev.angular-app.example.com
    steps:
      - uses: actions/checkout@v4
        
      - name: Set up Helm
        uses: azure/setup-helm@v3
        
      - name: Set up Kubectl
        uses: azure/setup-kubectl@v3
        
      - name: Set up Kubernetes config
        uses: azure/k8s-set-context@v3
        with:
          kubeconfig: ${{ secrets.KUBECONFIG }}
          
      - name: Deploy to development
        run: |
          helm upgrade --install angular-dev ${{ env.HELM_CHART_PATH }} \
          --set image.repository=${{ env.DOCKER_REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} \
          --set image.tag=${GITHUB_SHA::8} \
          --set config.apiUrl=https://api-dev.example.com \
          --namespace angular-apps-dev
          
      - name: Run smoke tests
        run: |
          npm ci
          npm run cypress:run -- --config baseUrl=https://dev.angular-app.example.com
  
  deploy-prod:
    needs: publish
    runs-on: ubuntu-latest
    if: github.ref == 'refs/heads/main'
    environment:
      name: production
      url: https://angular-app.example.com
    steps:
      - uses: actions/checkout@v4
        
      - name: Set up Helm
        uses: azure/setup-helm@v3
        
      - name: Set up Kubectl
        uses: azure/setup-kubectl@v3
        
      - name: Set up Kubernetes config
        uses: azure/k8s-set-context@v3
        with:
          kubeconfig: ${{ secrets.KUBECONFIG }}
          
      - name: Deploy to production
        run: |
          helm upgrade --install angular-prod ${{ env.HELM_CHART_PATH }} \
          --set image.repository=${{ env.DOCKER_REGISTRY }}/${{ github.repository_owner }}/${{ env.IMAGE_NAME }} \
          --set image.tag=${GITHUB_SHA::8} \
          --set config.apiUrl=https://api.example.com \
          --namespace angular-apps-prod
          
      - name: Run smoke tests
        run: |
          npm ci
          npm run cypress:run -- --config baseUrl=https://angular-app.example.com

38.4.2 GitHub Secrets und Umgebungen

GitHub Actions benötigt verschiedene Secrets für sichere Deployments:

Diese können in den Repository- oder Umgebungseinstellungen definiert werden.

38.5 CI/CD mit Azure DevOps

Azure DevOps bietet eine umfassende Lösung für CI/CD-Pipelines, insbesondere für Teams, die bereits auf Microsoft-Technologien setzen.

38.5.1 Pipeline-Konfiguration

Erstellen Sie eine Datei azure-pipelines.yml im Wurzelverzeichnis des Projekts:

trigger:
  branches:
    include:
      - main
      - develop
      - release/*

pool:
  vmImage: 'ubuntu-latest'

variables:
  - group: angular-app-variables
  - name: dockerRegistry
    value: 'yourregistry.azurecr.io'
  - name: imageName
    value: 'angular-app'
  - name: helmChartPath
    value: './angular-app'

stages:
- stage: Build
  jobs:
  - job: BuildAndTest
    steps:
    - task: NodeTool@0
      inputs:
        versionSpec: '20.x'
      displayName: 'Install Node.js'

    - script: |
        npm ci
      displayName: 'Install dependencies'

    - script: |
        npm run lint
      displayName: 'Lint code'

    - script: |
        npm run test:ci
      displayName: 'Run unit tests'
      
    - task: PublishTestResults@2
      inputs:
        testResultsFormat: 'JUnit'
        testResultsFiles: '**/junit.xml'
        mergeTestResults: true
      displayName: 'Publish test results'
      
    - task: PublishCodeCoverageResults@1
      inputs:
        codeCoverageTool: 'Cobertura'
        summaryFileLocation: '$(System.DefaultWorkingDirectory)/coverage/cobertura-coverage.xml'
      displayName: 'Publish code coverage'

    - script: |
        npm run build -- --configuration production
      displayName: 'Build Angular app'
      
    - task: Docker@2
      inputs:
        containerRegistry: 'AzureContainerRegistry'
        repository: '$(imageName)'
        command: 'buildAndPush'
        Dockerfile: 'Dockerfile'
        tags: |
          $(Build.BuildId)
          latest
      displayName: 'Build and push Docker image'
      
    - task: AzureKeyVault@2
      inputs:
        azureSubscription: 'Your-Azure-Subscription'
        KeyVaultName: 'your-keyvault'
        SecretsFilter: '*'
      displayName: 'Get KeyVault secrets'

    - task: HelmDeploy@0
      inputs:
        connectionType: 'Azure Resource Manager'
        azureSubscription: 'Your-Azure-Subscription'
        azureResourceGroup: 'AKS-ResourceGroup'
        kubernetesCluster: 'Your-AKS-Cluster'
        command: 'package'
        chartPath: '$(helmChartPath)'
        destination: '$(Build.ArtifactStagingDirectory)'
      displayName: 'Package Helm chart'
      
    - task: PublishBuildArtifacts@1
      inputs:
        PathtoPublish: '$(Build.ArtifactStagingDirectory)'
        ArtifactName: 'helm-chart'
        publishLocation: 'Container'
      displayName: 'Publish Helm chart artifact'

- stage: DeployDev
  dependsOn: Build
  condition: succeeded()
  jobs:
  - deployment: DeployToDev
    environment: development
    strategy:
      runOnce:
        deploy:
          steps:
          - task: HelmDeploy@0
            inputs:
              connectionType: 'Azure Resource Manager'
              azureSubscription: 'Your-Azure-Subscription'
              azureResourceGroup: 'AKS-ResourceGroup'
              kubernetesCluster: 'Your-AKS-Cluster'
              namespace: 'angular-apps-dev'
              command: 'upgrade'
              chartType: 'FilePath'
              chartPath: '$(Pipeline.Workspace)/helm-chart/angular-app-*.tgz'
              releaseName: 'angular-dev'
              overrideValues: 'image.repository=$(dockerRegistry)/$(imageName),image.tag=$(Build.BuildId),config.apiUrl=https://api-dev.example.com'
            displayName: 'Deploy to Dev with Helm'

- stage: DeployProd
  dependsOn: DeployDev
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
  jobs:
  - deployment: DeployToProd
    environment: production
    strategy:
      runOnce:
        deploy:
          steps:
          - task: HelmDeploy@0
            inputs:
              connectionType: 'Azure Resource Manager'
              azureSubscription: 'Your-Azure-Subscription'
              azureResourceGroup: 'AKS-ResourceGroup'
              kubernetesCluster: 'Your-AKS-Cluster'
              namespace: 'angular-apps-prod'
              command: 'upgrade'
              chartType: 'FilePath'
              chartPath: '$(Pipeline.Workspace)/helm-chart/angular-app-*.tgz'
              releaseName: 'angular-prod'
              overrideValues: 'image.repository=$(dockerRegistry)/$(imageName),image.tag=$(Build.BuildId),config.apiUrl=https://api.example.com'
            displayName: 'Deploy to Prod with Helm'

38.5.2 Variablengruppen und Umgebungen

In Azure DevOps können Sie Variablengruppen und Umgebungen definieren, um Konfigurationen und Sicherheitsrichtlinien zu verwalten:

38.6 CI/CD auf Jenkins

Für Teams, die bereits Jenkins nutzen, ist die Integration einer Angular-CI/CD-Pipeline möglich.

38.6.1 Jenkinsfile konfigurieren

Erstellen Sie eine Jenkinsfile im Wurzelverzeichnis:

pipeline {
    agent {
        kubernetes {
            yaml """
apiVersion: v1
kind: Pod
spec:
  containers:
  - name: node
    image: node:20-alpine
    command:
    - cat
    tty: true
  - name: docker
    image: docker:20.10.16-dind
    command:
    - cat
    tty: true
    volumeMounts:
    - mountPath: /var/run/docker.sock
      name: docker-sock
  - name: helm
    image: alpine/helm:3.12.0
    command:
    - cat
    tty: true
  volumes:
  - name: docker-sock
    hostPath:
      path: /var/run/docker.sock
"""
        }
    }
    
    environment {
        DOCKER_REGISTRY = 'your-registry.example.com'
        IMAGE_NAME = 'angular-app'
        HELM_CHART_PATH = './angular-app'
    }
    
    stages {
        stage('Checkout') {
            steps {
                checkout scm
            }
        }
        
        stage('Install Dependencies') {
            steps {
                container('node') {
                    sh 'npm ci'
                }
            }
        }
        
        stage('Lint') {
            steps {
                container('node') {
                    sh 'npm run lint'
                }
            }
        }
        
        stage('Test') {
            steps {
                container('node') {
                    sh 'npm run test:ci'
                }
            }
            post {
                always {
                    junit 'junit.xml'
                    publishHTML(target: [
                        allowMissing: false,
                        alwaysLinkToLastBuild: false,
                        keepAll: true,
                        reportDir: 'coverage',
                        reportFiles: 'index.html',
                        reportName: 'Coverage Report'
                    ])
                }
            }
        }
        
        stage('Build') {
            steps {
                container('node') {
                    sh 'npm run build -- --configuration production'
                }
            }
        }
        
        stage('Build and Push Docker Image') {
            steps {
                container('docker') {
                    withCredentials([usernamePassword(credentialsId: 'docker-registry-credentials', usernameVariable: 'DOCKER_USERNAME', passwordVariable: 'DOCKER_PASSWORD')]) {
                        sh '''
                            docker login -u $DOCKER_USERNAME -p $DOCKER_PASSWORD $DOCKER_REGISTRY
                            docker build -t $DOCKER_REGISTRY/$IMAGE_NAME:$BUILD_NUMBER -t $DOCKER_REGISTRY/$IMAGE_NAME:latest .
                            docker push $DOCKER_REGISTRY/$IMAGE_NAME:$BUILD_NUMBER
                            docker push $DOCKER_REGISTRY/$IMAGE_NAME:latest
                        '''
                    }
                }
            }
        }
        
        stage('Deploy to Development') {
            when {
                branch 'develop'
            }
            steps {
                container('helm') {
                    withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
                        sh '''
                            helm upgrade --install angular-dev $HELM_CHART_PATH \
                            --set image.repository=$DOCKER_REGISTRY/$IMAGE_NAME \
                            --set image.tag=$BUILD_NUMBER \
                            --set config.apiUrl=https://api-dev.example.com \
                            --namespace angular-apps-dev \
                            --kubeconfig $KUBECONFIG
                        '''
                    }
                }
            }
        }
        
        stage('Deploy to Production') {
            when {
                branch 'main'
            }
            steps {
                timeout(time: 1, unit: 'DAYS') {
                    input message: 'Deploy to Production?', ok: 'Yes'
                }
                container('helm') {
                    withCredentials([file(credentialsId: 'kubeconfig', variable: 'KUBECONFIG')]) {
                        sh '''
                            helm upgrade --install angular-prod $HELM_CHART_PATH \
                            --set image.repository=$DOCKER_REGISTRY/$IMAGE_NAME \
                            --set image.tag=$BUILD_NUMBER \
                            --set config.apiUrl=https://api.example.com \
                            --namespace angular-apps-prod \
                            --kubeconfig $KUBECONFIG
                        '''
                    }
                }
            }
        }
    }
    
    post {
        always {
            cleanWs()
        }
        success {
            echo 'Pipeline completed successfully!'
        }
        failure {
            echo 'Pipeline failed!'
        }
    }
}

38.6.2 Jenkins Credentials und Plugins

Für Jenkins benötigen Sie verschiedene Credentials und Plugins:

38.7 Automatisierte Tests in CI/CD-Pipelines

38.7.1 Unit-Tests

Angular verwendet Karma und Jasmine für Unit-Tests. Für CI/CD-Pipelines benötigen wir eine Headless-Browser-Konfiguration:

// karma.conf.ci.js
module.exports = function(config) {
  config.set({
    basePath: '',
    frameworks: ['jasmine', '@angular-devkit/build-angular'],
    plugins: [
      require('karma-jasmine'),
      require('karma-chrome-launcher'),
      require('karma-jasmine-html-reporter'),
      require('karma-coverage'),
      require('karma-junit-reporter'),
      require('@angular-devkit/build-angular/plugins/karma')
    ],
    client: {
      clearContext: false
    },
    coverageReporter: {
      dir: require('path').join(__dirname, './coverage'),
      subdir: '.',
      reporters: [
        { type: 'html' },
        { type: 'text-summary' },
        { type: 'cobertura' }
      ]
    },
    reporters: ['progress', 'kjhtml', 'coverage', 'junit'],
    junitReporter: {
      outputDir: './junit',
      outputFile: 'junit.xml',
      useBrowserName: false
    },
    port: 9876,
    colors: true,
    logLevel: config.LOG_INFO,
    autoWatch: false,
    browsers: ['ChromeHeadlessCI'],
    customLaunchers: {
      ChromeHeadlessCI: {
        base: 'ChromeHeadless',
        flags: ['--no-sandbox']
      }
    },
    singleRun: true
  });
};

Fügen Sie ein entsprechendes NPM-Skript hinzu:

{
  "scripts": {
    "test:ci": "ng test --karma-config=karma.conf.ci.js --code-coverage --watch=false"
  }
}

38.7.2 End-to-End-Tests

Cypress ist eine der besten Optionen für E2E-Tests bei Angular-Anwendungen:

// cypress.config.ts
import { defineConfig } from 'cypress';

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:4200',
    supportFile: 'cypress/support/e2e.ts',
    specPattern: 'cypress/e2e/**/*.cy.ts',
    video: true,
    screenshotOnRunFailure: true,
    reporter: 'junit',
    reporterOptions: {
      mochaFile: 'cypress/results/results-[hash].xml',
      toConsole: true
    }
  }
});

Mit dem entsprechenden NPM-Skript:

{
  "scripts": {
    "cypress:run": "cypress run"
  }
}

38.7.3 Test-Abdeckung und Qualitätsmetriken

Stellen Sie sicher, dass Ihre Pipelines Testabdeckungsberichte generieren und Qualitätsmetriken aufzeichnen. Diese Berichte sollten die folgenden Aspekte abdecken:

Tools wie SonarQube, Codacy oder CodeClimate können in Ihre Pipeline integriert werden, um diese Metriken zu verfolgen und zu visualisieren.

38.8 Konfigurationsmanagement für verschiedene Umgebungen

Angular bietet integrierte Möglichkeiten zur Verwaltung verschiedener Umgebungskonfigurationen.

38.8.1 Angular-Umgebungsdateien

Verwenden Sie die Umgebungsdateien in Angular, um umgebungsspezifische Konfigurationen zu verwalten:

// environment.ts (Development)
export const environment = {
  production: false,
  apiUrl: 'http://localhost:3000/api',
  enableDebug: true
};

// environment.prod.ts (Production)
export const environment = {
  production: true,
  apiUrl: 'https://api.example.com',
  enableDebug: false
};

// environment.staging.ts (Staging)
export const environment = {
  production: false,
  apiUrl: 'https://api-staging.example.com',
  enableDebug: false
};

38.8.2 Anpassung der Angular-Build-Konfiguration

Ergänzen Sie die angular.json-Datei, um verschiedene Build-Konfigurationen zu unterstützen:

{
  "projects": {
    "your-app": {
      "architect": {
        "build": {
          "configurations": {
            "production": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.prod.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": false,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true,
              "budgets": [
                {
                  "type": "initial",
                  "maximumWarning": "2mb",
                  "maximumError": "5mb"
                }
              ]
            },
            "staging": {
              "fileReplacements": [
                {
                  "replace": "src/environments/environment.ts",
                  "with": "src/environments/environment.staging.ts"
                }
              ],
              "optimization": true,
              "outputHashing": "all",
              "sourceMap": true,
              "namedChunks": false,
              "extractLicenses": true,
              "vendorChunk": false,
              "buildOptimizer": true
            }
          }
        }
      }
    }
  }
}

38.8.3 Runtime-Konfiguration mit Docker

Für containerisierte Anwendungen kann es sinnvoll sein, bestimmte Konfigurationen zur Laufzeit zu injizieren:

FROM nginx:alpine

# Kopieren der statischen Dateien
COPY dist/your-app /usr/share/nginx/html

# Kopieren des Nginx-Konfigurationstemplate
COPY nginx.conf /etc/nginx/conf.d/default.conf

# Kopieren eines Startup-Skripts
COPY startup.sh /usr/share/nginx/

# Umgebungsvariablen festlegen
ENV API_URL=https://api.example.com
ENV AUTH_URL=https://auth.example.com

# Container-Startbefehl
CMD ["/bin/sh", "/usr/share/nginx/startup.sh"]

Mit einem entsprechenden Startup-Skript startup.sh:

#!/bin/sh

# Ersetzen von Platzhaltern in der index.html mit Umgebungsvariablen
sed -i "s|__API_URL__|$API_URL|g" /usr/share/nginx/html/main*.js
sed -i "s|__AUTH_URL__|$AUTH_URL|g" /usr/share/nginx/html/main*.js

# Starten von Nginx im Vordergrund
nginx -g "daemon off;"

38.9 Monitoring und Fehlerbehandlung

Eine effektive CI/CD-Pipeline sollte Monitoring und Fehlerbehandlung integrieren, um Probleme schnell zu erkennen und zu beheben.

38.9.1 Anwendungsmonitoring

Implementieren Sie Monitoring-Tools wie:

38.9.2 Protokollierung und Analyse

Konfigurieren Sie ein zentrales Logging-System für Ihre Angular-Anwendung:

// logger.service.ts
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { environment } from '../environments/environment';

export enum LogLevel {
  Info = 'info',
  Warning = 'warning',
  Error = 'error'
}

@Injectable({
  providedIn: 'root'
})
export class LoggerService {
  constructor(private http: HttpClient) {}

  log(level: LogLevel, message: string, context?: any): void {
    // Für Entwicklungsumgebungen
    if (!environment.production) {
      console.log(`[${level.toUpperCase()}]`, message, context || '');
    }

    // Für Produktionsumgebungen zu einem Log-Server senden
    if (environment.logServerUrl) {
      this.http.post(environment.logServerUrl, {
        level,
        message,
        context,
        timestamp: new Date().toISOString(),
        userAgent: navigator.userAgent,
        url: window.location.href
      }).subscribe();
    }
  }

  info(message: string, context?: any): void {
    this.log(LogLevel.Info, message, context);
  }

  warning(message: string, context?: any): void {
    this.log(LogLevel.Warning, message, context);
  }

  error(message: string, context?: any): void {
    this.log(LogLevel.Error, message, context);
  }
}

38.9.3 Alarme und Benachrichtigungen

Richten Sie Benachrichtigungen für CI/CD-Fehler und Anwendungsprobleme ein:

38.10 Kontinuierliche Verbesserung der Pipeline

Eine CI/CD-Pipeline sollte kontinuierlich verbessert werden, um die Effizienz und Effektivität zu steigern.

38.10.1 Pipeline-Metriken

Erfassen Sie Metriken über Ihre CI/CD-Pipeline:

38.10.2 Optimierung

Basierend auf den gesammelten Metriken können Sie verschiedene Optimierungen vornehmen:

# Beispiel für Test-Parallelisierung in GitHub Actions
test:
  runs-on: ubuntu-latest
  strategy:
    matrix:
      shard: [1, 2, 3, 4]
  steps:
    - uses: actions/checkout@v4
    - name: Set up Node.js
      uses: actions/setup-node@v4
      with:
        node-version: '20'
    - name: Install dependencies
      run: npm ci
    - name: Run tests (shard ${{ matrix.shard }})
      run: npm run test:ci -- --shard=${{ matrix.shard }}/4

38.11 Zusammenfassung und Bewertung

Eine effektive CI/CD-Pipeline für Angular-Anwendungen bietet erhebliche Vorteile für die Softwarequalität und die Entwicklungsgeschwindigkeit. Die in diesem Kapitel vorgestellten Praktiken und Konfigurationen bilden eine solide Grundlage für die Implementierung von CI/CD in Ihrem Projekt.

38.11.1 Bewertung Ihrer CI/CD-Pipeline

Bewerten Sie Ihre Pipeline anhand der folgenden Kriterien:

38.11.2 Ausblick und nächste Schritte

Basierend auf der aktuellen Pipeline können die folgenden Erweiterungen in Betracht gezogen werden:

Mit diesen fortgeschrittenen Praktiken können Sie Ihre CI/CD-Pipeline weiter verbessern und die Qualität und Zuverlässigkeit Ihrer Angular-Anwendungen steigern.