Przejdź do treści

Deployment

Wprowadzenie

System Masaku używa Azure Pipelines do CI/CD z osobnymi pipeline'ami dla każdego środowiska i regionu.

Środowiska

Środowisko Region URL Pipeline
Testing DE https://test.masaku.de azure-pipelines.yml
Testing AT https://test.masaku.at azure-pipelines-au-testing.yml
Staging DE https://staging.masaku.de azure-pipelines-staging.yml
Staging AT https://staging.masaku.at azure-pipelines-au-staging.yml
Pre-production DE https://preprod.masaku.de azure-pipelines-preproduction.yml
Pre-production AT https://preprod.masaku.at (TBD)
Production DE https://app.masaku.de azure-pipelines-production.yml
Production AT https://app.masaku.at azure-pipelines-au-production.yml

Pipeline Structure

Frontend Pipeline (masaku-portal)

# azure-pipelines.yml (Testing DE)
trigger:
  branches:
    include:
      - develop
  paths:
    include:
      - masaku-portal/**

pool:
  vmImage: 'ubuntu-latest'

variables:
  nodeVersion: '18.x'
  environment: 'test'
  region: 'de'

stages:
  - stage: Build
    jobs:
      - job: BuildFrontend
        steps:
          # Install Node.js
          - task: NodeTool@0
            inputs:
              versionSpec: $(nodeVersion)
            displayName: 'Install Node.js'

          # Install dependencies
          - script: |
              cd masaku-portal
              npm ci
            displayName: 'npm install'

          # Lint
          - script: |
              cd masaku-portal
              npm run lint
            displayName: 'Lint code'

          # Run tests
          - script: |
              cd masaku-portal
              npm run test:ci
            displayName: 'Run tests'

          # Build
          - script: |
              cd masaku-portal
              npm run build:$(environment)-$(region)
            displayName: 'Build application'

          # Archive artifacts
          - task: ArchiveFiles@2
            inputs:
              rootFolderOrFile: 'masaku-portal/dist/masaku/$(region)'
              includeRootFolder: false
              archiveType: 'zip'
              archiveFile: '$(Build.ArtifactStagingDirectory)/frontend-$(Build.BuildId).zip'
            displayName: 'Archive build'

          # Publish artifacts
          - task: PublishBuildArtifacts@1
            inputs:
              PathtoPublish: '$(Build.ArtifactStagingDirectory)'
              ArtifactName: 'frontend'
            displayName: 'Publish artifacts'

  - stage: Deploy
    dependsOn: Build
    jobs:
      - deployment: DeployFrontend
        environment: 'Testing-DE'
        strategy:
          runOnce:
            deploy:
              steps:
                # Download artifacts
                - task: DownloadBuildArtifacts@0
                  inputs:
                    buildType: 'current'
                    downloadType: 'single'
                    artifactName: 'frontend'
                    downloadPath: '$(System.ArtifactsDirectory)'

                # Deploy to Azure Static Web Apps
                - task: AzureStaticWebApp@0
                  inputs:
                    app_location: '/'
                    azure_static_web_apps_api_token: $(AZURE_STATIC_WEB_APPS_API_TOKEN)
                    action: 'upload'
                    ###### Repository/Build Configurations ######
                    app_build_command: 'echo "Already built"'
                    skip_app_build: true

Backend Pipeline (masaku-api)

# azure-pipelines.yml (Testing DE)
trigger:
  branches:
    include:
      - develop
  paths:
    include:
      - masaku-api/**

pool:
  vmImage: 'ubuntu-latest'

variables:
  buildConfiguration: 'Release'
  dotnetVersion: '6.0.x'
  environment: 'test'

stages:
  - stage: Build
    jobs:
      - job: BuildBackend
        steps:
          # Install .NET SDK
          - task: UseDotNet@2
            inputs:
              version: $(dotnetVersion)
            displayName: 'Install .NET SDK'

          # Restore dependencies
          - script: |
              cd masaku-api
              dotnet restore
            displayName: 'Restore NuGet packages'

          # Build
          - script: |
              cd masaku-api
              dotnet build --configuration $(buildConfiguration) --no-restore
            displayName: 'Build solution'

          # Run tests
          - script: |
              cd masaku-api
              dotnet test --configuration $(buildConfiguration) --no-build --logger trx
            displayName: 'Run tests'

          # Publish test results
          - task: PublishTestResults@2
            condition: succeededOrFailed()
            inputs:
              testResultsFormat: 'VSTest'
              testResultsFiles: '**/*.trx'
            displayName: 'Publish test results'

          # Publish application
          - script: |
              cd masaku-api
              dotnet publish Masaku.API/Masaku.API.csproj \
                --configuration $(buildConfiguration) \
                --output $(Build.ArtifactStagingDirectory)/api \
                --no-build
            displayName: 'Publish application'

          # Publish artifacts
          - task: PublishBuildArtifacts@1
            inputs:
              PathtoPublish: '$(Build.ArtifactStagingDirectory)'
              ArtifactName: 'backend'
            displayName: 'Publish artifacts'

  - stage: DatabaseMigration
    dependsOn: Build
    jobs:
      - job: RunLiquibase
        steps:
          # Download Liquibase
          - script: |
              wget https://github.com/liquibase/liquibase/releases/download/v4.20.0/liquibase-4.20.0.tar.gz
              tar -xzf liquibase-4.20.0.tar.gz
            displayName: 'Download Liquibase'

          # Run migrations
          - script: |
              cd masaku-api/Sql/Liquibase
              ../../../liquibase/liquibase \
                --changeLogFile=3_dbchangelog.xml \
                --url="jdbc:sqlserver://$(DB_SERVER);databaseName=$(DB_NAME)" \
                --username=$(DB_USER) \
                --password=$(DB_PASSWORD) \
                update
            displayName: 'Run database migrations'

  - stage: Deploy
    dependsOn: DatabaseMigration
    jobs:
      - deployment: DeployBackend
        environment: 'Testing-DE'
        strategy:
          runOnce:
            deploy:
              steps:
                # Download artifacts
                - task: DownloadBuildArtifacts@0
                  inputs:
                    buildType: 'current'
                    downloadType: 'single'
                    artifactName: 'backend'
                    downloadPath: '$(System.ArtifactsDirectory)'

                # Deploy to Azure App Service
                - task: AzureWebApp@1
                  inputs:
                    azureSubscription: 'Azure-Subscription'
                    appType: 'webApp'
                    appName: 'masaku-api-test-de'
                    package: '$(System.ArtifactsDirectory)/backend/api'
                    deploymentMethod: 'auto'

Lokalne budowanie

Frontend

cd masaku-portal

# Development
npm start

# Build dla testing (DE)
npm run build:test-de

# Build dla testing (AT)
npm run build:test-at

# Build dla staging (DE)
npm run build:stage-de

# Build dla production (DE)
npm run build:prod-de

# Analyze bundle
npm run bundle:analyze

Backend

cd masaku-api

# Build
dotnet build

# Run locally
dotnet run --project Masaku.API

# Run tests
dotnet test

# Publish
dotnet publish Masaku.API/Masaku.API.csproj \
  -c Release \
  -o ./publish

Docker

Local development

cd masaku-api/docker
docker-compose up -d

Uruchamia: - SQL Server (port 1433) - Azurite (ports 7777, 8888, 9999) - MailHog (ports 1025, 8025)

Build Docker image

# masaku-api/Dockerfile
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443

FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["Masaku.API/Masaku.API.csproj", "Masaku.API/"]
COPY ["Masaku.Services/Masaku.Services.csproj", "Masaku.Services/"]
COPY ["Masaku.Repository/Masaku.Repository.csproj", "Masaku.Repository/"]
COPY ["Masaku.Domain/Masaku.Domain.csproj", "Masaku.Domain/"]
RUN dotnet restore "Masaku.API/Masaku.API.csproj"
COPY . .
WORKDIR "/src/Masaku.API"
RUN dotnet build "Masaku.API.csproj" -c Release -o /app/build

FROM build AS publish
RUN dotnet publish "Masaku.API.csproj" -c Release -o /app/publish

FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "Masaku.API.dll"]
# Build
docker build -t masaku-api:latest .

# Run
docker run -p 5000:80 -e ASPNETCORE_ENVIRONMENT=Development masaku-api:latest

Environment Variables

Frontend (Angular)

Zmienne środowiskowe w plikach environment*.ts:

// src/environments/de/environment.prod.ts
export const environment = {
  production: true,
  apiUrl: 'https://api.masaku.de',
  region: 'DE',
  azureAdB2C: { /* ... */ },
  applicationInsights: {
    instrumentationKey: 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx'
  }
};

Backend (.NET)

Zmienne środowiskowe w Azure App Service Configuration:

ConnectionStrings__DefaultConnection=Server=...
AzureAdB2C__ClientId=xxxxxxxx
AzureStorage__ConnectionString=DefaultEndpoints...
Email__SendGrid__ApiKey=SG.xxx

Lub w appsettings.{Environment}.json:

{
  "ConnectionStrings": {
    "DefaultConnection": "Server=..."
  },
  "AzureAdB2C": {
    "ClientId": "xxxxxxxx"
  }
}

Database Migrations (Liquibase)

Tworzenie nowej migracji

  1. Utwórz nowy plik w masaku-api/Sql/Liquibase/:

    <!-- 4_dbchangelog.xml -->
    <?xml version="1.0" encoding="UTF-8"?>
    <databaseChangeLog>
        <changeSet id="4-1" author="dev">
            <comment>Add new column to Budgets</comment>
            <addColumn tableName="Budgets">
                <column name="Description" type="NVARCHAR(500)"/>
            </addColumn>
        </changeSet>
    </databaseChangeLog>
    

  2. Zaktualizuj liquibase.properties-template:

    changeLogFile=Sql/Liquibase/4_dbchangelog.xml
    

  3. Merge do target brancha - automatyczny deployment przez pipeline

Ręczne uruchomienie (lokalnie)

cd masaku-api/Sql/Liquibase

liquibase \
  --changeLogFile=4_dbchangelog.xml \
  --url="jdbc:sqlserver://localhost:1433;databaseName=Masaku" \
  --username=sa \
  --password=Masaku123 \
  update

Rollback Strategy

Frontend

Rollback przez Azure Static Web Apps: 1. Azure Portal → Static Web Apps → Deployment history 2. Wybierz poprzednią wersję → "Promote to production"

Backend

Rollback przez Azure App Service: 1. Azure Portal → App Service → Deployment Center → Deployment history 2. Wybierz poprzednią wersję → "Redeploy"

Database

Rollback przez Liquibase:

# Rollback last changeset
liquibase rollback-count 1

# Rollback to specific tag
liquibase rollback <tag-name>

# Rollback to date
liquibase rollback-to-date 2025-01-24

UWAGA: Nie wszystkie zmiany można zrollbackować automatycznie (np. DROP TABLE). W takich przypadkach potrzebny jest manual restore z backupu.

Monitoring

Application Insights

Dostępne metryki: - Request rate - Response time - Failed requests - Exceptions - Custom events

Azure Portal → Application Insights → Performance/Failures/Metrics

Health Checks

// API health check endpoint
GET /api/health

Response:
{
  "status": "Healthy",
  "checks": [
    { "name": "Database", "status": "Healthy" },
    { "name": "AzureStorage", "status": "Healthy" }
  ]
}

Logs

# Azure CLI - stream logs
az webapp log tail \
  --name masaku-api-prod-de \
  --resource-group masaku-prod

# Azure Portal
App Service  Monitoring  Log stream

Blue-Green Deployment (Opcjonalnie)

Azure App Service slots dla zero-downtime deployment:

# Deploy to staging slot
- task: AzureWebApp@1
  inputs:
    appName: 'masaku-api-prod-de'
    slotName: 'staging'
    package: '$(System.ArtifactsDirectory)/backend/api'

# Swap slots (staging → production)
- task: AzureAppServiceManage@0
  inputs:
    azureSubscription: 'Azure-Subscription'
    Action: 'Swap Slots'
    WebAppName: 'masaku-api-prod-de'
    SourceSlot: 'staging'
    SwapWithProduction: true

Best Practices

1. Zawsze testuj przed deployem

# Frontend
npm run lint
npm run test
npm run build:prod-de

# Backend
dotnet test
dotnet build -c Release

2. Używaj feature flags

// Frontend
if (environment.features.newBudgetModule) {
  // Nowa funkcjonalność
}

// Backend
if (_featureFlagService.IsEnabled("NewBudgetApi"))
{
    // Nowa funkcjonalność
}

3. Monitoruj po deploymencie

  • Sprawdź Application Insights
  • Sprawdź error rate
  • Sprawdź response time
  • Sprawdź logi

4. Komunikuj deployment

  • Powiadom użytkowników o maintenance window (jeśli potrzebne)
  • Dokumentuj zmiany w release notes
  • Update version w package.json (frontend)

Dalsze zasoby