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¶
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¶
-
Utwórz nowy plik w
masaku-api/Sql/Liquibase/: -
Zaktualizuj
liquibase.properties-template: -
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)