Azure Devops Topics To Learn

Azure Devops Topics To Learn

Reading time1 min
#DevOps#Cloud#Automation#AzureDevOps#YAMLPipelines#CICD

Mastering Azure DevOps Pipelines: Practical YAML for Real CI/CD

Pipeline automation often stumbles at scale: scripts tangle, releases block, and unintended production changes slip through. Azure DevOps YAML pipelines address these pain points—but only with disciplined design and a focus on reusability.


Classic Pipelines vs. YAML: Control Trade-offs

The legacy Classic UI provided an approachable experience but limited versioning, code review, and reusability. For sustained projects, YAML’s advantages are immediate:

  • Pipeline-as-Code: Configurability sits alongside application source, allowing atomic changes.
  • Template Inheritance: Modularize build/test/deploy logic using extends or includes.
  • Diff/Review: PRs expose pipeline changes for team scrutiny.

GUI editing trades short-term comfort for long-term pain. Exceptions exist (e.g., point-and-click for POC).


Minimal YAML: The .NET Build Baseline

Fast feedback is non-negotiable. Example: Say you maintain a .NET 7.x codebase. Commit to main triggers a build, test, and publishes an artifact:

trigger:
  branches:
    include:
      - main

pool:
  vmImage: 'ubuntu-22.04'

steps:
  - task: UseDotNet@2
    inputs:
      packageType: 'sdk'
      version: '7.0.x'
    
  - script: dotnet restore
    displayName: 'Restore NuGet packages'

  - script: dotnet build --configuration Release --no-restore
    displayName: 'Build solution'

  - script: dotnet test --no-build --verbosity normal
    displayName: 'Run tests'

  - publish: $(Build.ArtifactStagingDirectory)
    artifact: drop

Note: ubuntu-22.04 avoids potential compatibility regressions introduced by ubuntu-latest rollovers.


Variables & Templates: Reducing the Copy/Paste Tax

Hardcoding values pins you to one environment, one use case. Use variables to avoid this:

variables:
  buildConfig: 'Release'
  solutionGlob: '**/*.sln'

Reference:

- script: dotnet build $(solutionGlob) --configuration $(buildConfig)

For commonly repeated steps across pipelines, use YAML templates.
build.yml:

parameters:
  solution: ''
  buildConfig: 'Release'

steps:
  - script: dotnet restore $(solution)
  - script: dotnet build $(solution) --configuration $(buildConfig)

In main pipeline:

stages:
- stage: Build
  jobs:
    - job: BuildJob
      steps:
        - template: build.yml
          parameters:
            solution: '**/*.sln'
            buildConfig: 'Debug'

Gotcha: Template parameter names are case sensitive.


Multi-Stage Orchestration: Real Environments, Real Constraints

A simplistic build pipeline rarely matches real-world workflows—acceptance tests, gated deployments, manual approvals, and rollbacks all complicate the flow.

End-to-End Example:

trigger:
  branches:
    include:
      - main

stages:
- stage: Build
  jobs:
    - job: Build
      steps:
        - script: echo "Build step"

- stage: Test
  dependsOn: Build
  jobs:
    - job: UnitTest
      steps:
        - script: echo "Run unit tests"

- stage: Deploy_Dev
  dependsOn: Test
  condition: succeeded()
  jobs:
    - deployment: DeployToDev
      environment: 'dev'
      strategy:
        runOnce:
          deploy:
            steps:
              - script: echo "Deploy to Dev"

- stage: Deploy_Prod
  dependsOn: Deploy_Dev
  condition: and(succeeded(), eq(variables['Build.SourceBranch'], 'refs/heads/main'))
  jobs:
    - deployment: DeployToProd
      environment: 'prod'
      strategy:
        runOnce:
          deploy:
            steps:
              - script: echo "Deploy to Prod"
  • dependsOn chains execution across stages.
  • Conditional deploy: Gates promotion to production on branch policy and prior success.
  • Deployment jobs: Attach to Azure DevOps Environments, enabling approval gates—not just resource scoping.

Known issue: Environment resources sometimes fail to deallocate after approvals timeout; be prepared to intervene manually.


Practical Tactics for Robust Pipelines

  • Start simple. Even advanced architectures grow from one working stage. Complex template nesting can hide bugs.
  • Use environment approvals. Enforce “two-person rule” for production via release checks.
  • Pipeline caching:
    • Cut build times by 40–60% with Cache@2 for yarn, npm, or NuGet.
    • Example cache step:
    - task: Cache@2
      inputs:
        key: 'nuget | "$(Agent.OS)" | packages.lock.json'
        path: ~/.nuget/packages
    
  • Secure secrets via linked Key Vault or library groups, never inline in YAML.
  • Error log analysis:
    • Learn common signature.
    • E.g., look for ##[error] lines and trace to specific task IDs.
IssueSymptomTypical Cause
Pipeline fails pre-deployNo artifact publishedPath typo, or inconsistent trigger
Infinite approval waitStuck in Pending ApprovalNo approver assigned to env
Caching doesn’t hitNo CacheRestored messageKey misconfigured

Side note: Feature flags—toggle risky deployments by key, or minimize blast radius using ring-based deployments.


Final Observation

Templates and multi-stage YAML reduce human toil at scale, but add up-front complexity. Over-design is easy; the real cost comes when onboarding new engineers or firefighting at 2 a.m. Keep initial pipelines legible, and only extract cross-team logic once reuse is obvious.


References for further study

Non-obvious tip: Parameterize agent pools for workload isolation (e.g., ephemeral vs. persistent runners) to defend against slow CI and resource overcommit.


Deploy smart, not just fast.