Modern software delivery requires robust, automated pipelines that ensure code quality, security, and reliable deployments. Let's explore advanced CI/CD patterns using Azure DevOps and GitHub Actions.
GitHub Actions: Advanced Workflows
Multi-Environment Deployment Pipeline
yaml1name: CI/CD Pipeline 2 3on: 4 push: 5 branches: [main, develop] 6 pull_request: 7 branches: [main] 8 9env: 10 DOTNET_VERSION: '8.0.x' 11 NODE_VERSION: '18.x' 12 13jobs: 14 build-and-test: 15 runs-on: ubuntu-latest 16 outputs: 17 version: ${{ steps.version.outputs.version }} 18 19 steps: 20 - uses: actions/checkout@v4 21 with: 22 fetch-depth: 0 23 24 - name: Setup .NET 25 uses: actions/setup-dotnet@v3 26 with: 27 dotnet-version: ${{ env.DOTNET_VERSION }} 28 29 - name: Setup Node.js 30 uses: actions/setup-node@v3 31 with: 32 node-version: ${{ env.NODE_VERSION }} 33 cache: 'npm' 34 35 - name: Restore dependencies 36 run: | 37 dotnet restore 38 npm ci 39 40 - name: Build application 41 run: | 42 dotnet build --no-restore --configuration Release 43 npm run build 44 45 - name: Run unit tests 46 run: dotnet test --no-build --verbosity normal --collect:"XPlat Code Coverage" 47 48 - name: Upload coverage to Codecov 49 uses: codecov/codecov-action@v3 50 with: 51 files: '**/coverage.cobertura.xml' 52 53 - name: Run security scan 54 uses: securecodewarrior/github-action-add-sarif@v1 55 with: 56 sarif-file: 'security-scan-results.sarif' 57 58 - name: Generate version 59 id: version 60 run: | 61 VERSION=$(date +'%Y.%m.%d').${{ github.run_number }} 62 echo "version=$VERSION" >> $GITHUB_OUTPUT 63 64 - name: Package application 65 run: | 66 dotnet publish --configuration Release --output ./publish 67 68 - name: Upload artifacts 69 uses: actions/upload-artifact@v3 70 with: 71 name: webapp-${{ steps.version.outputs.version }} 72 path: ./publish 73 74 deploy-staging: 75 needs: build-and-test 76 runs-on: ubuntu-latest 77 if: github.ref == 'refs/heads/develop' 78 environment: staging 79 80 steps: 81 - name: Download artifacts 82 uses: actions/download-artifact@v3 83 with: 84 name: webapp-${{ needs.build-and-test.outputs.version }} 85 path: ./publish 86 87 - name: Deploy to Azure Web App (Staging) 88 uses: azure/webapps-deploy@v2 89 with: 90 app-name: 'myapp-staging' 91 publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_STAGING }} 92 package: './publish' 93 94 - name: Run integration tests 95 run: | 96 npm run test:integration -- --baseUrl https://myapp-staging.azurewebsites.net 97 98 - name: Run accessibility tests 99 run: | 100 npm run test:a11y -- --url https://myapp-staging.azurewebsites.net 101 102 deploy-production: 103 needs: [build-and-test, deploy-staging] 104 runs-on: ubuntu-latest 105 if: github.ref == 'refs/heads/main' 106 environment: production 107 108 steps: 109 - name: Download artifacts 110 uses: actions/download-artifact@v3 111 with: 112 name: webapp-${{ needs.build-and-test.outputs.version }} 113 path: ./publish 114 115 - name: Deploy to Azure Web App (Production) 116 uses: azure/webapps-deploy@v2 117 with: 118 app-name: 'myapp-production' 119 publish-profile: ${{ secrets.AZURE_WEBAPP_PUBLISH_PROFILE_PRODUCTION }} 120 package: './publish' 121 122 - name: Create release 123 uses: actions/create-release@v1 124 env: 125 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 126 with: 127 tag_name: v${{ needs.build-and-test.outputs.version }} 128 release_name: Release v${{ needs.build-and-test.outputs.version }} 129 draft: false 130 prerelease: false
Advanced Security and Quality Gates
yaml1name: Security and Quality Analysis 2 3on: 4 push: 5 branches: [main, develop] 6 pull_request: 7 branches: [main] 8 9jobs: 10 security-analysis: 11 runs-on: ubuntu-latest 12 13 steps: 14 - uses: actions/checkout@v4 15 16 - name: Run Snyk Security Scan 17 uses: snyk/actions/dotnet@master 18 env: 19 SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }} 20 with: 21 args: --severity-threshold=high 22 23 - name: Run CodeQL Analysis 24 uses: github/codeql-action/init@v2 25 with: 26 languages: csharp, javascript 27 28 - name: Build for CodeQL 29 run: dotnet build 30 31 - name: Perform CodeQL Analysis 32 uses: github/codeql-action/analyze@v2 33 34 - name: Run Dependency Check 35 uses: dependency-check/Dependency-Check_Action@main 36 with: 37 project: 'MyProject' 38 path: '.' 39 format: 'SARIF' 40 41 - name: Upload results to GitHub 42 uses: github/codeql-action/upload-sarif@v2 43 with: 44 sarif_file: reports/dependency-check-report.sarif 45 46 code-quality: 47 runs-on: ubuntu-latest 48 49 steps: 50 - uses: actions/checkout@v4 51 with: 52 fetch-depth: 0 53 54 - name: SonarCloud Scan 55 uses: SonarSource/sonarcloud-github-action@master 56 env: 57 GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 58 SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 59 60 - name: Quality Gate Check 61 uses: sonarqube-quality-gate-action@master 62 timeout-minutes: 5 63 env: 64 SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }}
Azure DevOps Pipelines
YAML Pipeline with Advanced Features
yaml1trigger: 2 branches: 3 include: 4 - main 5 - develop 6 paths: 7 exclude: 8 - docs/* 9 - README.md 10 11pr: 12 branches: 13 include: 14 - main 15 paths: 16 exclude: 17 - docs/* 18 19variables: 20 buildConfiguration: 'Release' 21 dotNetFramework: 'net8.0' 22 dotNetVersion: '8.0.x' 23 vmImageName: 'ubuntu-latest' 24 25stages: 26 - stage: Build 27 displayName: 'Build and Test' 28 jobs: 29 - job: BuildJob 30 displayName: 'Build Job' 31 pool: 32 vmImage: $(vmImageName) 33 34 steps: 35 - task: UseDotNet@2 36 displayName: 'Use .NET $(dotNetVersion)' 37 inputs: 38 version: $(dotNetVersion) 39 40 - task: DotNetCoreCLI@2 41 displayName: 'Restore packages' 42 inputs: 43 command: 'restore' 44 projects: '**/*.csproj' 45 46 - task: DotNetCoreCLI@2 47 displayName: 'Build application' 48 inputs: 49 command: 'build' 50 projects: '**/*.csproj' 51 arguments: '--configuration $(buildConfiguration) --no-restore' 52 53 - task: DotNetCoreCLI@2 54 displayName: 'Run unit tests' 55 inputs: 56 command: 'test' 57 projects: '**/*Tests/*.csproj' 58 arguments: '--configuration $(buildConfiguration) --collect:"XPlat Code Coverage" --logger trx --results-directory $(Agent.TempDirectory)' 59 60 - task: PublishTestResults@2 61 displayName: 'Publish test results' 62 inputs: 63 testResultsFormat: 'VSTest' 64 testResultsFiles: '**/*.trx' 65 searchFolder: '$(Agent.TempDirectory)' 66 67 - task: PublishCodeCoverageResults@1 68 displayName: 'Publish code coverage' 69 inputs: 70 codeCoverageTool: 'Cobertura' 71 summaryFileLocation: '$(Agent.TempDirectory)/*/coverage.cobertura.xml' 72 73 - task: DotNetCoreCLI@2 74 displayName: 'Publish application' 75 inputs: 76 command: 'publish' 77 publishWebProjects: true 78 arguments: '--configuration $(buildConfiguration) --output $(Build.ArtifactStagingDirectory)' 79 zipAfterPublish: true 80 81 - task: PublishBuildArtifacts@1 82 displayName: 'Publish artifacts' 83 inputs: 84 pathToPublish: '$(Build.ArtifactStagingDirectory)' 85 artifactName: 'webapp' 86 87 - stage: SecurityScan 88 displayName: 'Security Analysis' 89 dependsOn: Build 90 jobs: 91 - job: SecurityJob 92 displayName: 'Security Scanning' 93 pool: 94 vmImage: $(vmImageName) 95 96 steps: 97 - task: CredScan@3 98 displayName: 'Run Credential Scanner' 99 100 - task: SdtReport@2 101 displayName: 'Create Security Analysis Report' 102 inputs: 103 GdnExportAllTools: false 104 GdnExportGdnToolCredScan: true 105 106 - task: PublishSecurityAnalysisLogs@3 107 displayName: 'Publish Security Analysis Logs' 108 109 - stage: DeployStaging 110 displayName: 'Deploy to Staging' 111 dependsOn: [Build, SecurityScan] 112 condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/develop')) 113 jobs: 114 - deployment: StagingDeployment 115 displayName: 'Staging Deployment' 116 pool: 117 vmImage: $(vmImageName) 118 environment: 'staging' 119 strategy: 120 runOnce: 121 deploy: 122 steps: 123 - task: AzureWebApp@1 124 displayName: 'Deploy to Azure Web App' 125 inputs: 126 azureSubscription: 'Azure Service Connection' 127 appType: 'webApp' 128 appName: 'myapp-staging' 129 package: $(Pipeline.Workspace)/**/webapp.zip 130 deploymentMethod: 'auto' 131 132 - task: AzureAppServiceManage@0 133 displayName: 'Restart Azure App Service' 134 inputs: 135 azureSubscription: 'Azure Service Connection' 136 action: 'Restart Azure App Service' 137 webAppName: 'myapp-staging' 138 139 - stage: IntegrationTests 140 displayName: 'Integration Tests' 141 dependsOn: DeployStaging 142 jobs: 143 - job: IntegrationTestJob 144 displayName: 'Integration Tests' 145 pool: 146 vmImage: $(vmImageName) 147 148 steps: 149 - task: DotNetCoreCLI@2 150 displayName: 'Run integration tests' 151 inputs: 152 command: 'test' 153 projects: '**/*IntegrationTests/*.csproj' 154 arguments: '--configuration $(buildConfiguration) --logger trx' 155 env: 156 BaseUrl: 'https://myapp-staging.azurewebsites.net' 157 158 - stage: DeployProduction 159 displayName: 'Deploy to Production' 160 dependsOn: [DeployStaging, IntegrationTests] 161 condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main')) 162 jobs: 163 - deployment: ProductionDeployment 164 displayName: 'Production Deployment' 165 pool: 166 vmImage: $(vmImageName) 167 environment: 'production' 168 strategy: 169 canary: 170 increments: [25, 50, 100] 171 deploy: 172 steps: 173 - task: AzureWebApp@1 174 displayName: 'Deploy to Azure Web App' 175 inputs: 176 azureSubscription: 'Azure Service Connection' 177 appType: 'webApp' 178 appName: 'myapp-production' 179 package: $(Pipeline.Workspace)/**/webapp.zip 180 deploymentMethod: 'auto'
Infrastructure as Code with Terraform
Automate infrastructure provisioning:
hcl1# main.tf 2terraform { 3 required_providers { 4 azurerm = { 5 source = "hashicorp/azurerm" 6 version = "~>3.0" 7 } 8 } 9 10 backend "azurerm" { 11 resource_group_name = "tfstate-rg" 12 storage_account_name = "tfstatestorage" 13 container_name = "tfstate" 14 key = "prod.terraform.tfstate" 15 } 16} 17 18provider "azurerm" { 19 features {} 20} 21 22# Resource Group 23resource "azurerm_resource_group" "main" { 24 name = "myapp-${var.environment}-rg" 25 location = var.location 26 27 tags = { 28 Environment = var.environment 29 Project = "MyApp" 30 } 31} 32 33# App Service Plan 34resource "azurerm_service_plan" "main" { 35 name = "myapp-${var.environment}-plan" 36 resource_group_name = azurerm_resource_group.main.name 37 location = azurerm_resource_group.main.location 38 39 os_type = "Linux" 40 sku_name = var.app_service_sku 41 42 tags = { 43 Environment = var.environment 44 } 45} 46 47# Web App 48resource "azurerm_linux_web_app" "main" { 49 name = "myapp-${var.environment}" 50 resource_group_name = azurerm_resource_group.main.name 51 location = azurerm_service_plan.main.location 52 service_plan_id = azurerm_service_plan.main.id 53 54 site_config { 55 always_on = var.environment == "production" 56 57 application_stack { 58 dotnet_version = "8.0" 59 } 60 61 cors { 62 allowed_origins = var.allowed_origins 63 } 64 } 65 66 app_settings = { 67 "ASPNETCORE_ENVIRONMENT" = var.environment 68 "ConnectionStrings__DefaultConnection" = azurerm_postgresql_database.main.connection_string 69 } 70 71 tags = { 72 Environment = var.environment 73 } 74} 75 76# PostgreSQL Database 77resource "azurerm_postgresql_flexible_server" "main" { 78 name = "myapp-${var.environment}-db" 79 resource_group_name = azurerm_resource_group.main.name 80 location = azurerm_resource_group.main.location 81 82 administrator_login = var.db_admin_username 83 administrator_password = var.db_admin_password 84 85 sku_name = var.db_sku_name 86 version = "14" 87 88 backup_retention_days = var.environment == "production" ? 30 : 7 89 90 tags = { 91 Environment = var.environment 92 } 93}
Monitoring and Observability Pipeline
yaml1# monitoring-pipeline.yml 2name: Monitoring Setup 3 4trigger: 5 paths: 6 include: 7 - monitoring/* 8 - infrastructure/* 9 10stages: 11 - stage: DeployMonitoring 12 displayName: 'Deploy Monitoring Infrastructure' 13 jobs: 14 - job: MonitoringJob 15 displayName: 'Setup Application Insights and Alerts' 16 17 steps: 18 - task: AzureCLI@2 19 displayName: 'Create Application Insights' 20 inputs: 21 azureSubscription: 'Azure Service Connection' 22 scriptType: 'bash' 23 scriptLocation: 'inlineScript' 24 inlineScript: | 25 az monitor app-insights component create \ 26 --app myapp-insights \ 27 --location eastus \ 28 --resource-group myapp-rg \ 29 --application-type web 30 31 - task: AzureCLI@2 32 displayName: 'Setup Alerts' 33 inputs: 34 azureSubscription: 'Azure Service Connection' 35 scriptType: 'bash' 36 scriptLocation: 'inlineScript' 37 inlineScript: | 38 # High response time alert 39 az monitor metrics alert create \ 40 --name "High Response Time" \ 41 --resource-group myapp-rg \ 42 --scopes /subscriptions/{subscription-id}/resourceGroups/myapp-rg/providers/Microsoft.Web/sites/myapp \ 43 --condition "avg requests/duration > 5" \ 44 --description "Alert when response time > 5 seconds" 45 46 # High error rate alert 47 az monitor metrics alert create \ 48 --name "High Error Rate" \ 49 --resource-group myapp-rg \ 50 --scopes /subscriptions/{subscription-id}/resourceGroups/myapp-rg/providers/Microsoft.Web/sites/myapp \ 51 --condition "count requests/failed > 10" \ 52 --description "Alert when error rate > 10 per minute"
Best Practices Summary
1. Pipeline Security
- Use managed identities and service connections
- Store secrets in Key Vault
- Implement least privilege access
- Regular security scanning
2. Quality Gates
- Automated testing at multiple levels
- Code coverage thresholds
- Security vulnerability scanning
- Performance testing
3. Deployment Strategies
- Blue-green deployments for zero downtime
- Canary releases for risk mitigation
- Feature flags for controlled rollouts
- Automated rollback capabilities
4. Monitoring and Observability
- Comprehensive logging and metrics
- Real-time alerting
- Application performance monitoring
- Infrastructure monitoring
Conclusion
Modern DevOps pipelines require a comprehensive approach that includes automated testing, security scanning, infrastructure as code, and robust monitoring. By implementing these patterns with Azure DevOps and GitHub Actions, you can achieve reliable, secure, and efficient software delivery.
Remember to continuously iterate and improve your pipelines based on feedback and changing requirements. The goal is to enable fast, safe deployments while maintaining high quality and security standards.