Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige
Lesedauer 7 Min.

Managed DevOps Pools

Agent Pools als Managed Service mit einfacher Integration in private Netzwerke und Authentisierung mittels Managed Identity tragen deutlich zur Sicherheit der Agent-Infrastruktur bei.
© EMGenie

 

Heutzutage ist ein moderner DevOps-Prozess ohne CI/CD-Automatisierung undenkbar. Entsprechend steht die Verwaltung der Pipeline-Definitionen und der dazugehörigen Pipeline Agents im Mittelpunkt der Azure-DevOps-Administration und -Wartung. Im Printartikel „Sicher ist sicher“ der dotnetpro 6-7/2025 ab Seite 54 sind wir auf die Angriffsvektoren und entsprechende Gegenmaßnahmen rund um Azure Pipelines eingegangen. In diesem Online-Artikel möchte ich die Verwaltung der Pipelines Agents mittels Managed DevOps Pools im Detail aufzeigen. Managed DevOps Pools ist ein Feature, welches nur dem Azure DevOps Service (also der Cloud-Variante) zur Verfügung steht – On-Premises-Benutzer von Azure DevOps müssen ihre Agent-Umgebung nach wie vor selbst verwalten und betreiben.

Was haben Managed DevOps Pools mit Sicherheit zu tun?

Grundsätzlich viel – auch wenn einem das eine oder andere sicherheitsrelevante Feature erst beim zweiten Blick ins Auge fällt. Managed DevOps Pools sind der neue Weg, um Agent-Infrastruktur in Azure zu betreiben. Bislang stand uns mit Hosted Agents eine komplett von Microsoft verwaltete Agent-Umgebung zu Verfügung. Zudem konnten wir mittels Virtual Machine Scale Set Pools automatisiert eigene VMs in Azure dynamisch aufgrund der Pipeline Queue skalieren. Hosted Agents können von vielen Teams nicht verwendet werden, da diese Agents zwangsweise in der öffentlichen IP-Range von Azure betrieben werden und somit eine Integration in die interne Umgebung verunmöglichen. Virtual Machine Scale Set Agents bieten wesentlich mehr Möglichkeiten zur Integration und zur Verwendung unternehmensspezifischer Tools. Die Verwaltung obliegt aber ganz dem Anwender, und das Pflegen der VM-Images ist durchaus aufwendig. Dies schreckte einige Teams ganz ab, oder aber man begnügte sich mit weniger und eher seltener aktualisierten Agent Pools respektive VMs. Dies mindert den Sicherheitsstandard der CI/CD-Umgebung.

Eine sichere Build-Umgebung heißt, dass wir verschiedene Agents respektive Pools für spezifische Zwecke einrichten und auch die Agents für die verschiedenen Stages zwingend segmentieren. Ein kompromittierter Build Agent, zum Beispiel aufgrund eines schadhaften NuGet-Pakets, darf nie mit Credentials für das Produktive Deployment in Kontakt kommen. Ebenso soll sich der Agent so nahe wie möglich am Ziel-Environment befinden. Eine Integration ins vNet des Zielhosts ist also eine notwendige Anforderung. Mit Managed DevOps Pools können wir dies einfach und schnell umsetzen, was somit einen großen Beitrag zur Sicherheit unserer CI/CD-Umgebung leistet.

Wie funktionieren Managed DevOps Pools?

Einfach gesagt sind Managed DevOps Pools eine Kombination von Hosted Pools und Virtual Machine Scale Set Pools mit zusätzlichen Funktionen. Microsoft unterhält für die Hosted Pools Betriebssystem-Images mit den wichtigsten Abhängigkeiten für CI/CD. Selbst Images zu unterhalten ist aufwendig, vor allem wenn das offizielle Microsoft-Image gänzlich ausreichen würde. Arbeitet man mit Container-Jobs, sind sowieso die spezifischen Abhängigkeiten in den Containern, was den Bedarf an eigenen VM-Images nochmals weiter verringert. Microsoft hatte diese Images aus lizenztechnischen Gründen nie anderweitig zur Verfügung gestellt. Dies ändert sich nun mit Managed DevOps Pools. 

Hier haben wir nun die Möglichkeit, Pools mit einem oder mehreren Images zu konfigurieren, die entweder aus den von Microsoft verwalteten und zur Verfügung gestellten Images stammen dürfen, oder wir können für Spezialanforderungen auch eigene Images mit einbringen. Ermöglicht wird dies durch eine lizenztechnische Raffinesse, da die Managed DevOps Pools in einer „Schatten-Subscription“ laufen. Microsoft betreibt die Agent VMs in einer für uns dedizierten zusätzlichen Subscription. Dies löst das Lizenzproblem, und wir dürfen diese Images außerhalb vom Hosted Agent Pool verwenden. Verrechnet werden diese VM-Instanzen ganz normal über unsere Azure Subscription. Es fühlt sich also an, als würden wir das Virtual Machine Scale Set selbst betreiben, aber tatsächlich führt dies Microsoft als Managed Service für uns durch. Wir haben neu die Möglichkeit, den Managed DevOps Pool mit einem vNet aus unserer Azure Subscription zu betreiben. 

Somit laufen die Agent-VMs nicht mehr in der Public-IP-Range von Azure, sondern in unserem vNET, welches dann Zugriff auf private Endpoints oder gar über eine Site-to-Site-VPN-Verbindung über Zugriff in unser LAN verfügt. Hinzu kommt die Integration in Azure KeyVault, welches uns erlaubt, Trusted-Root-Zertifikate in die Build-Umgebung direkt zu integrieren. Außerdem können auch firmenspezifische Proxy-Settings hinterlegt werden. Wenn man nur mit Azure-Ressourcen arbeitet, kann der Agent-Instanz zusätzlich eine Managed Identity zugewiesen werden, worüber der Zugriff auf Azure-Ressourcen mittels Role-based Access Control (RBAC) verwaltet werden kann und die Lösung ganz ohne Passwörter auskommt.

Nebst den Sicherheitsfunktionen lassen sich die Agents mittels eines Regelwerks einfach skalieren. Der Managed DevOps Pool skaliert automatisch aufgrund der Queue-Länge des Agent Pools. Natürlich können wir eine Obergrenze der Instanzen einrichten, um das Budget einzuhalten. Zudem können wir definieren, ob wir in diesem Pool lieber mit Ephemeral Agents, also Agent-VMs, welche nach jedem Run gelöscht werden, arbeiten wollen oder die VM Instanz für eine bestimmte Zeit noch halten möchten, damit ein Nachfolge-Run vom lokalen Cache profitieren kann. Zusätzlich können wir während bestimmten Zeiten Agent-VMs vorhalten, damit zum Beispiel am Morgen die Pipelines sofort starten und nicht erst das Erstellen der VM abgewartet werden muss. Bild 1 zeigt das Monitoring eines Pools. Hiermit kann nachvollzogen werden, wann wie viele Agents im Einsatz standen und wie lang die Wartezeiten bis zur Build-Ausführung waren. Somit lässt sich die Konfiguration auf die Team-Bedürfnisse maßschneidern.

Managed DevOps Pool Monitoring (Bild 1)

© Marc Müller

Einrichten eines Managed DevOps Pools

Für das Einrichten eines Managed DevOps Pools benötigt man eine Dev-Center-Instanz mit einem dazugehörigen Dev-Center-Projekt. Dies stellt Microsofts neuen Ansatz dar, entwicklungszentrische Ressourcen wie Pools, aber auch Dev Boxes projektspezifisch zu verwalten. Bezahlt werden aber nur die eigentlichen Ressourcen wie VM-Instanzen. Bild 2 zeigt die Zuordnung des Pools zum Dev Center sowie die Auswahl der VM-Größe und der zu verwendenden Images. Ein Pool kann mehrere Images beinhalten, welche dann in der Pipeline-Definition ausgewählt werden können.

 

Basiskonfiguration eines Managed DevOps Pools (Bild 2)

© Marc Müller

Bild 3 zeigt wie der Pool skaliert wird. Mittels dem Pre-Provisioning Scheme können zeitbasierte Regeln hinterlegt werden, wann VM-Instanzen vorgehalten werden sollen.

Skalierung (Bild 3)

© Marc Müller

Bild 4 zeigt die Integration in ein bestehendes vNET unserer Deployment-Umgebung.

vNET-Integration (Bild 4)

© Marc Müller

Einstellungen zu Managed Identity sowie KeyVault können nicht im Creation Wizard hinterlegt werden. Sobald die Ressource erstellt wurde, kann über das Admin Portal die entsprechende Konfiguration hinterlegt werden. Dies ist in Bild 5 und 6 ersichtlich.

Integration von KeyVault-Zertifikaten (Bild 5)

© Marc Müller

Zuweisen einer Managed Identity zu einem Pool (Bild 6)

© Marc Müller 

Pipelines mit Managed Pools betreiben.

Mit der Managed-DevOps-Pool-Definition in Azure wird den ausgewählten Projekten der Azure DevOps Organization der Pool hinzugefügt. Grundsätzlich verhält sich dieser wie jeder andere Pool, außer dass er von „außen“, also über das Azure Dev Center, verwaltet wird.

Microsoft wollte mit der Einführung des Managed DevOps Pools keinen Breaking Change in der YAML-Syntax der Pipelines einführen. Entsprechend wird die Pipelines-Demands-Funktionalität für Einstellungen respektive zur parametrisierten Benutzung des Pools verwendet. Microsoft hat spezifische Demands für die Managed DevOps Pools definiert. So kann ein VM-Image ausgewählt werden oder das Working Directory auf ein Verzeichnis einer zusätzlichen, der VM angefügten Disk gelegt werden. Auch die Wiederverwendung der Agent-Instanz für den Folge-Job kann über die Demands konfiguriert werden.

Listing 1 zeigt die Definition der Managed DevOps Pools Demands als Beispiel. Stehen in einem Pool mehrere Images zur Verfügung, muss zwingend ein Image mittels ImageOverride ausgewählt werden. Wird der VM eine zusätzliche Disk hinzugefügt, kann der Arbeitsordner entsprechend auf dieses Verzeichnis gelegt werden. Dies ergibt vor allem dann Sinn, wenn viel Speicherplatz oder eine sehr hohe I/O-Performance benötigt wird.

 

Listing 1: Demands
pool: 
  # Name des Pools
  name: DemoPoolDnp                                             
  demands:

  # Wählt das Agent-Image mit Alias "ubuntu-24.04-gen2" aus.
  # Dieser Demand ist erforderlich, wenn mehrere Images im Pool verfügbar sind.
  - ImageOverride -equals ubuntu-24.04-gen2                     

  # Setzt die Job-Priorität auf "Low" (niedrig)
  - Priority -equals Low             

  # Überschreibt den Standard-Arbeitsordner des Agents. 
  # Dies ist nützlich, wenn der Agent auf einem benutzerdefinierten Laufwerk läuft.                           
  - WorkFolder -equals /mnt/storage/sdc/custom-work-folder 

   # Nutzt dieselbe stateful Agent-Instanz (Tag "MeinWert1") für Folge-Jobs 
   # mit der gleichen CustomCapability.     
  - CustomCapabilities -equals MeinWert1
 

Verwaltung mittels Infrastructure as Code

Managed DevOps Pools werden als Azure-Ressourcen verwaltet. Entsprechend sollte es klar sein, dass wir im professionellen Umfeld uns nicht durch das Admin Portal klicken, sondern über Infrastructure as Code und parametrisierbaren Templates die Erstellung und Verwaltung unserer Pools automatisieren. Grundsätzlich stehen uns hierzu alle Automatisierungsmöglichkeiten von Azure zur Verfügung, und in den meisten Fällen wird hierzu Bicep oder Terraform verwendet werden.

Listing 2 und Listing 3 zeigen ein einfaches Beispiel, wie ein Managed DevOps Pool mit vNet-Integration über IaC aufgesetzt werden kann. Speziell für die private Netzwerkintegration ist zu erwähnen, dass dem Service Principal DevOpsInfrastructure auf dem Ziel vNET entsprechende Rollen zugeteilt werden müssen. Dies führen wir in einem ersten Schritt vor dem Aufsetzen des Pools durch.

 

Listing 2: Definition von einem Managed DevOps Pool mit Bicep
targetScope = 'resourceGroup'

@description('Resource ID of existing DevCenter Project')
param devCenterProjectResourceId string

@minLength(3) 
@maxLength(63)
@description('Name for the new pool')
param poolName string

@description('Deployment location (defaults to RG location)')
param location string = resourceGroup().location

@description('Azure DevOps organization URL')
param azureDevOpsOrgUrl string

@description('Azure DevOps project names')
param azureDevOpsProjects array

@minValue(1) 
@maxValue(100)
@description('Maximum concurrent agents')
param maximumConcurrency int = 10

@allowed(['Stateless','Stateful'])
@description('Agent profile kind')
param agentProfileKind string = 'Stateless'

@description('Alias of the Azure Pipelines image (e.g. ubuntu-24.04)')
param wellKnownImageName string = 'ubuntu-24.04'

@description('VM SKU for the agent VMSS')
param vmSkuName string

@description('User-assigned managed identity resource ID')
param userAssignedIdentityId string

@description('Name of the existing virtual network')
param vnetName string

@description('Name of the existing subnet (delegated to Microsoft.DevOpsInfrastructure/pools)')
param subnetName string

@allowed(['Service','Interactive'])
@description('Logon type for VM agents')
param logonType string = 'Service'

@description('Tags for all resources')
param tags object = {}

@description('Service principal Object ID for Azure DevOps Infrastructure')
param devOpsInfraSpObjectId string = 'fda18bb2-9474-4fd2-a1ef-128533ca9e33'

// ---------------------------------------------------------------
// 1) Grant permissions on existing subnet to integrate the pool
// ---------------------------------------------------------------

resource existingVnet 'Microsoft.Network/virtualNetworks@2023-05-01' existing = {
  name: vnetName
}

resource existingSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-05-01' existing = {
  parent: existingVnet
  name: subnetName
}

var networkContributorRoleDef = subscriptionResourceId(
  'Microsoft.Authorization/roleDefinitions',
  'b24988ac-6180-42a0-ab88-20f7382dd24c'
)
var readerRoleDef = subscriptionResourceId(
  'Microsoft.Authorization/roleDefinitions',
  ' acdd72a7-3385-48ef-bd42-f606fba81ae7'
)

resource roleNetContrib 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(existingSubnet.id, networkContributorRoleDef)  
  scope: existingSubnet                                      
  properties: {
    roleDefinitionId: networkContributorRoleDef
    principalId: devOpsInfraSpObjectId
  }
}

resource roleReader 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
  name: guid(existingSubnet.id, readerRoleDef)              
  scope: existingSubnet                                     
  properties: {
    roleDefinitionId: readerRoleDef
    principalId: devOpsInfraSpObjectId
  }
}

// ---------------------------------------------------------------
// 2) Managed DevOps Pool
// ---------------------------------------------------------------

resource pool 'Microsoft.DevOpsInfrastructure/pools@2025-01-21' = {
  name: poolName
  location: location
  tags: tags

  identity: {
    type: 'UserAssigned'
    userAssignedIdentities: {
      '${userAssignedIdentityId}': {}
    }
  }

  properties: {
    devCenterProjectResourceId: devCenterProjectResourceId
    maximumConcurrency: maximumConcurrency

    agentProfile: {
      kind: agentProfileKind 
    }

    fabricProfile: {
      kind: 'Vmss'
      images: [
        { 
          wellKnownImageName: wellKnownImageName 
        }
      ]
      networkProfile: {
        subnetId: existingSubnet.id
      }
      osProfile: {
        logonType: logonType
      }
      sku: {
        name: vmSkuName
      }
    }

    organizationProfile: {
      kind: 'AzureDevOps'
      organizations: [
        {
          url: azureDevOpsOrgUrl
          projects: azureDevOpsProjects
        }
      ]
    }
  }
}
 
Listing 3: Deployment des Managed DevOps Pool mit Azure CLI
az deployment group create \
  --resource-group DemoMDP \
  --template-file main.bicep \
  --parameters \
    devCenterProjectResourceId='/subscriptions/xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/resourceGroups/DemoMDP/providers/Microsoft.DevCenter/projects/Demo' \
    poolName='demo-build' \
    azureDevOpsOrgUrl='https://dev.azure.com/xxxx' \
    azureDevOpsProjects='["YamlPipelinesDemo"]' \
    maximumConcurrency=2 \
    agentProfileKind="Stateless" \
    wellKnownImageName="ubuntu-24.04/latest" \
    vmSkuName="Standard_D2_v5" \
    userAssignedIdentityId='/subscriptions/ xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx /resourcegroups/demomdp/providers/microsoft.managedidentity/userassignedidentities/xxx' \
    vnetName="xxxx" \
    subnetName="default" \
    logonType="Service" \
    tags='{"Environment":"Production","CostCenter":"CC123"}'
 

Fazit

Managed DevOps Pools bieten eine moderne, sichere und einfache Variante, um Agent Pools zu verwalten. Mit der Einfachheit und den Automatisierungsmöglichkeiten können Zweck- und Team-spezifische Pools einfach erstellt werden, und mit der Integration in Azure vNets, Azure Key Vault und Managed Identity stehen wichtige Sicherheitsfunktionen zur Verfügung. Da Microsoft dies als Managed Service anbietet und zudem von Microsoft verwaltete und aktualisierte VM-Images verwendet werden können, hält sich der Wartungsaufwand in Grenzen. Aus meiner Sicht sind Managed DevOps Pools das, was im Regelfall für den Betrieb von Agents verwendet werden sollte. Zweck- und Deployment-Target-spezifische Pools mit aktuellen VM-Images tragen deutlich zur Sicherheit bei.

 

Neueste Beiträge

Arbeiten mit Tabellen und KI in Dataverse
Microsoft unterstützt die zentrale Datenmanagement-Lösung Dataverse in Power Apps mit KI-Features.
7 Minuten
6. Aug 2025
Müssen Ziele SMART sein?
Wenn es um Ziele im Projektmanagement oder in der Führung einer Organisation geht, stoßen wir schnell und fast ausnahmslos auf das Akronym SMART. Was steckt dahinter, und kann es nicht auch sinnvolle Ziele geben, die nicht SMART sind?
8 Minuten
DDC 2025: Schnapp Dir schnell dein Early-Bird-Ticket – Rabatte enden am 7. August - Konferenz für .NET-Entwickler
Wer bis zum 7. August 2025 zuschlägt, sichert sich limitierte Tickets zum Sparpreis für die .NET Developer Conference in Köln. Also - nicht mehr warten.
3 Minuten
31. Jul 2025

Das könnte Dich auch interessieren

Sicher ist sicher - Azure DevOps Pipelines Security
Als integraler Bestandteil der Entwicklungsumgebung ist Azure DevOps Pipelines oft Ziel von Angriffen. Da ist es gut zu wissen, wo die Schwachstellen des Systems liegen.
14 Minuten
16. Jun 2025
GitHub Actions lokal ausführen - Automation
Schnelle Rückmeldung erwünscht: So führen Sie GitHub Actions lokal aus.
2 Minuten
16. Jun 2025
Muster-AGB für Software as a Service - 26.09.2016, 00:00 Uhr
Der Bitkom hat seine Empfehlungen für Allgemeine Geschäftsbedingungen (AGB) um eine Vorlage zu Software as a Service (SaaS) erweitert.
3 Minuten
26. Sep 2016
Anzeige
Anzeige
Anzeige
Anzeige
Anzeige