Azure DevOps ist ein cloud­ba­sier­ter Ansatz von Micro­soft, mit dem sich der voll­stän­dige Lebens­zy­klus von Soft­ware­ent­wick­lungs­pro­zes­sen jeder Art nach dem DevOps-Prin­zip ver­wal­ten und rea­li­sie­ren lässt. Hierzu stellt Azure DevOps die fol­gen­den fünf Ser­vices zur Verfügung:

Azure DevOps – Migration zu Azure Repos und Azure Pipelines Bild1
Abbil­dung 1 Azure DevOps Services

Azure Boards: Ein Ser­vice für das Pro­jekt­ma­nage­ment, der u.a. Kan­ban-Boards, Back­logs, Dash­boards und Berichte zur Doku­men­ta­tion des Ent­wick­lungs­fort­schritts bereitstellt.

Azure Repos: Hier kann der Quell­code in Git-Repo­si­to­ries oder TFVC-Repo­si­to­ries ver­wal­tet werden.

Azure Pipe­lines: Mit die­sem Ser­vice kön­nen CI / CD – Pipe­lines für das Pro­jekt ent­wi­ckelt werden.

Azure Test Plans: Die­ser Ser­vice dient der Ver­wal­tung von Soft­ware­tests und lässt sich hierzu auch in Azure Pipe­lines integrieren.

Azure Arti­facts: Die­ser Ser­vice ermög­licht die Inte­gra­tion von Pake­ten in Azure Pipelines.

In die­sem Blog soll der Fokus auf Azure DevOps als CI / CD – Tool gelegt wer­den. Hierzu wer­den in ers­ter Linie die Ser­vices Azure Repos und Azure Pipe­lines benö­tigt. Möchte man für ein bereits lau­fen­des Pro­jekt Azure DevOps als CI / CD – Tool nut­zen, besteht daher die Not­wen­dig­keit einer Migra­tion der alten Ser­vices zu Azure Repos bzw. Azure Pipe­lines. Die not­wen­di­gen Schritte einer sol­chen Migra­tion wer­den im Fol­gen­den im Detail erläutert.

Migra­tion Azure Repos

Ein Repo­si­tory aus einer exter­nen Quelle wie Bit­bu­cket oder Git­Hub lässt sich unter den Repo­si­tory-Ein­stel­lun­gen ganz leicht über die zuge­hö­rige URL und einer ggfls. benö­tig­ten Nut­zer­ken­nung impor­tie­ren. Dabei wer­den sämt­li­che Bran­ches mit ihrer Com­mit-His­to­rie nach Azure Repos migriert, jedoch keine Pull Request. Alte Pull Requests sind daher ent­we­der vor der Migra­tion abzu­schlie­ßen oder müs­sen in Azure Repos neu erstellt werden.

Nach der Migra­tion eines Repo­si­to­ries las­sen sich unter den Repo­si­tory-Ein­stel­lun­gen alle nöti­gen Berech­ti­gun­gen ver­wal­ten. So kann man sowohl für Benut­zer­grup­pen als auch für ein­zelne Nut­zer Berech­ti­gun­gen, wie z.B. das Erzeu­gen neuer Bran­ches oder Pull Requests, ver­ge­ben (Allow) oder ver­wei­gern (Deny). Ist eine Berech­ti­gung für einen ein­zel­nen Nut­zer nicht gesetzt (Not set), so ist wird sie impli­zit ver­wei­gert, sofern der Nut­zer kein Mit­glied einer Gruppe ist, in dem die Berech­ti­gung expli­zit ver­ge­ben wurde.

Neben den Berech­ti­gun­gen las­sen sich für ein Repo­si­tory oder für ein­zelne Bran­ches auch Poli­cies fest­le­gen, die für jeden Nut­zer ver­bind­lich sind. So las­sen sich bei­spiels­weise Pushes nach bestimm­ten Regeln blo­ckie­ren oder Pull Requests in den mas­ter Branch wer­den nur akzep­tiert, nach­dem die vor­ge­ge­bene Min­dest­an­zahl an Prü­fern die Ände­run­gen bestä­tigt hat.       

Azure DevOps – Migration zu Azure Repos und Azure Pipelines Bild2
Abbil­dung 2 Repo­si­tory Übersicht
Azure DevOps – Migration zu Azure Repos und Azure Pipelines Bild3
Abbil­dung 3 main Repo­si­tory

Migra­tion Azure Pipelines

Sobald alle nöti­gen Repo­si­to­ries nach Azure Repos migriert wor­den sind, kann mit dem Auf­bau der CI / CD – Pipe­lines in Azure Pipe­lines begon­nen wer­den. Zuerst muss fest­ge­legt wer­den, ob man von Micro­soft gehos­tete Build Agents oder selbst gehos­tete Build Agents nut­zen möchte. Im ers­ten Fall wird für jeden Job eine vir­tu­elle Maschine in der Cloud gestar­tet, um die Pipe­line aus­zu­füh­ren. Im zwei­ten Fall wird die Pipe­line auf einer selbst ein­ge­rich­te­ten Maschine aus­ge­führt, bei der es sich sowohl um ein On-Pre­mise Sys­tem han­deln kann als auch um eine Maschine in einer Cloud, z.B. einer EC2-Instanz in AWS. Um meh­rere Jobs par­al­lel aus­füh­ren zu kön­nen, müs­sen ent­spre­chend meh­rere Build Agents gehos­tet und in einem Agent-Pool zusam­men­ge­fasst wer­den. Die maxi­male Anzahl an mög­li­chen Par­al­lel-Jobs lässt sich dabei in den Pro­jekt-Ein­stel­lun­gen kon­fi­gu­rie­ren, wobei zusätz­li­che Par­al­lel-Jobs kos­ten­pflich­tig sind.

Nach­dem die Build Agents auf­ge­setzt sind, kann mit der Imple­men­tie­rung der Pipe­lines begon­nen wer­den. Hierzu muss zuerst das Repo­si­tory aus­ge­wählt wer­den, wel­ches beim Aus­füh­ren der Pipe­line auf dem Build Agent aus­ge­checkt wird. Die Pipe­line selbst wird mit­hilfe einer YAML-Datei defi­niert und in die­sem Repo­si­tory abge­legt. Dabei kann man zwi­schen vor­de­fi­nier­ten Tem­pla­tes oder einer lee­ren YAML-Datei wäh­len. Eine aus­führ­li­che Doku­men­ta­tion aller ver­füg­ba­ren YAML-Key­words zur Defi­ni­tion von Azure Pipe­lines wird von Micro­soft unter dem Link

YAML schema – Azure Pipe­lines | Micro­soft Docs

zur Ver­fü­gung gestellt. Im Fol­gen­den soll auf die wich­tigs­ten Key­words ein­ge­gan­gen wer­den, die für prak­tisch alle Pipe­lines rele­vant sind:

  • stages, jobs und steps:
    Die Arbeits­schritte einer Pipe­line sind in Stages, Jobs und Steps orga­ni­siert. Stages sind dabei die höchste Stufe die­ser Hier­ar­chie und bestehen aus einem oder meh­re­ren Jobs. Stan­dard­mä­ßig wer­den Stages sequen­ti­ell aus­ge­führt, wäh­rend Jobs im sel­ben Stage par­al­lel aus­ge­führt wer­den. Somit eig­nen sich Stages zur Grup­pie­rung äqui­va­len­ter Jobs wie etwa das Aus­füh­ren eines Build-Pro­zes­ses in ver­schie­de­nen Umge­bun­gen. Ein ein­zel­ner Job besteht wie­derum aus einem oder meh­re­ren Steps, die sequen­ti­ell aus­ge­führt wer­den. Hier­bei han­delt es sich um von­ein­an­der iso­lierte Pro­zesse, wel­che die ein­zel­nen Arbeits­schritte der Pipe­line dar­stel­len. Bei­spiele für einen Step sind etwa das Aus­füh­ren eines Shell-Skripts oder der Build eines Docker-Images. Azure Pipe­lines bie­tet dafür eine Viel­zahl an Tem­pla­tes an, die über einen Assis­ten­ten in den aktu­el­len YAML-Code ein­ge­baut wer­den können.
  • trig­ger:
    Über­nimmt das Key­word none oder eine Liste von Bran­ches. Wird ein Com­mit in einen die­ser Bran­ches gepu­shed, so wird die Pipe­line auto­ma­tisch gestar­tet. In der Stan­dard­ein­stel­lung lösen alle Pushs ein Trig­gern der Pipe­line auf dem ent­spre­chen­den Branch aus.
  • pr:
    Über­nimmt das Key­word none oder eine Liste von Bran­ches. Wird ein Pull Request in einen die­ser Bran­ches gemer­ged, so star­tet die Pipe­line auto­ma­tisch. In der Stan­dard­ein­stel­lung trig­gert jeder Merge die Pipe­line auf dem ent­spre­chen­den Branch.
  • pool:
    Gibt den Namen des Agent-Pools an, auf dem die Pipe­line aus­ge­führt wer­den soll. Das pool Key­word kann sowohl glo­bal defi­niert wer­den als auch auf Stage- oder Job-Ebene, falls ein Ver­tei­len der Stages bzw. Jobs auf unter­schied­li­che Agent-Pools nötig ist.

Das fol­gende Code­bei­spiel zeigt ein Mini­mal­bei­spiel einer Pipe­line. Die stage und jobs Key­words kön­nen ent­fal­len, falls die Pipe­line nur einen Stage bzw. Job enthält.

trigger:
- main
- develop

pr: none

pool:
  name: Muster - Pool

stages:
- stage: Build
  jobs:
  - job: BuildContainerA
    steps:
    # Settings
    - task: CmdLine@2
      inputs:
        script: 'echo Hello world'

Da ein sol­ches Mini­mal­bei­spiel für prak­ti­sche Anwen­dun­gen unzu­rei­chend ist, soll im Fol­gen­den auf drei wei­tere Fea­tures ein­ge­gan­gen wer­den, die für die meis­ten pra­xis­taug­li­chen CI / CD – Pipe­lines unver­zicht­bar sind.

Ver­bin­dung mit Cloud Diensten

In der Regel benö­tigt eine Pipe­line Zugang zu den Diens­ten eines Clou­dan­bie­ters. Dabei ver­folgt Azure DevOps einen Multi-Cloud-Ansatz, d.h. neben der Azure Cloud kann eine Azure Pipe­line auch Ver­bin­dun­gen zu ande­ren Clou­dan­bie­tern, wie z.B. Ama­zon Web Ser­vices (AWS), her­stel­len und auf des­sen Ser­vices zugrei­fen. Hierzu muss man in den Pro­jekt-Ein­stel­lun­gen die zuge­hö­rige Ser­vice Con­nec­tion aus­wäh­len. Für eine Ver­bin­dung zur Azure Cloud benö­tigt die Ser­vice Con­nec­tion eine Sub­scrip­tion sowie einen Benut­zer­na­men, der über eine ange­hängte Azure Active Direc­tory authen­ti­fi­ziert wer­den kann. Möchte man sich dage­gen mit der AWS Cloud ver­bin­den, benö­tigt man für die Ser­vice Con­nec­tion eine Access Key ID, einen Secret Access Key und ggfls. eine IAM-Rolle, wel­che die Pipe­line beim Nut­zen der AWS-Ser­vices anneh­men soll.

Ver­wen­dung von Para­me­tern und Variablen

Für eine fle­xi­ble Nut­zung der Pipe­lines bie­tet Azure DevOps die Mög­lich­keit, Para­me­ter zu defi­nie­ren des­sen Stan­dard­werte bei einem manu­el­len Trig­ger der Pipe­line über­schrie­ben wer­den kön­nen. Zudem gibt es die Mög­lich­keit, vor­de­fi­nierte Sys­tem­va­ria­blen oder benut­zer­de­fi­nierte Varia­blen zu nut­zen. Mit­hilfe der Sys­tem­va­ria­blen kann man z.B. den aktu­el­len Branch­na­men oder die aktu­elle Com­mit ID in den Code ein­set­zen. Eine voll­stän­dige Liste der Sys­tem­va­ria­blen wird von Micro­soft unter dem fol­gen­den Link zur Ver­fü­gung gestellt:

Pre­de­fi­ned varia­bles – Azure Pipe­lines | Micro­soft Docs

Ein Anwen­dungs­fall für benut­zer­de­fi­nierte Varia­blen liegt z.B. vor, wenn Build-Pro­zesse in Ent­wick­lungs­um­ge­bun­gen mit ande­ren Wer­ten aus­ge­führt wer­den sol­len als in der Pro­duk­tiv­um­ge­bung. Hierzu benö­tigt man eine YAML-Datei, in der die ent­spre­chen­den Werte durch Varia­blen ersetzt sind. Diese YAML-Datei kann anschlie­ßend von ver­schie­de­nen Pipe­lines mit jeweils unter­schied­li­chen Wer­ten auf­ge­ru­fen wer­den. Zur leich­te­ren Grup­pie­rung der Varia­blen und ihren Wer­ten bie­tet Azure DevOps das Tool „Varia­ble Groups“ an. So kann man z.B. für Ent­wick­lung und Pro­duk­tion je eine Varia­ble Group anle­gen und inner­halb die­ser Varia­ble Groups den ein­zel­nen Varia­blen den zur Umge­bung pas­sen­den Wert zuweisen.

Azure DevOps – Migration zu Azure Repos und Azure Pipelines Bild5
Abbil­dung 4 Varia­ble Groups
Secrets Manage­ment

Für den Zugang zu ande­ren Diens­ten benö­tigt die Pipe­line oft sicher­heits­re­le­vante Infor­ma­tio­nen wie z.B. Pass­wör­ter. Azure Pipe­lines bie­tet meh­rere Wege an, sol­che Infor­ma­tio­nen an die Pipe­line zu über­tra­gen, ohne diese auf unsi­chere Weise ins Repo­si­tory hoch­zu­la­den. Zum einen kön­nen benut­zer­de­fi­nierte Varia­blen als Secret defi­niert wer­den, sodass Nut­zer ohne Admi­nis­tra­tor­rechte kei­nen Zugriff auf den Wert der Varia­ble erhal­ten. Eine wei­tere Mög­lich­keit, die Azure DevOps anbie­tet, sind die Secure Files. Dies sind Dateien, die außer­halb der Repo­si­to­ries in Azure DevOps abge­legt wer­den und nur Nut­zern sowie Pipe­lines mit ent­spre­chen­der Berech­ti­gung zur Ver­fü­gung ste­hen. Über den „Down­load Secure File“ Step kann die Secure File dann von der Pipe­line ein­ge­le­sen wer­den. Zusätz­lich gibt es die Mög­lich­keit, den Azure Ser­vice „Azure Key Vault“ zu nut­zen, um sen­si­ble Daten sicher in der Azure Cloud zu hin­ter­le­gen. Mit­hilfe des „Azure Key Vault“ Steps erhält die Pipe­line Zugriff auf die nöti­gen Key Vaults. Eine detail­lierte Beschrei­bung die­ses Ser­vice fin­det man in dem fol­gen­den Blog:

Azure Secu­rity Hand­ling with Azure Key Vault Ser­vice – saracus consulting

Fazit

Durch eine Migra­tion nach Azure DevOps las­sen sich alle Auf­ga­ben eines DevOps Zykels in einem zen­tra­len Tool ver­bin­den und ver­wal­ten. Zwar ist die Migra­tion der Pipe­lines mit einem gewis­sen Auf­wand ver­bun­den, jedoch lässt sich alter Code oft mit gering­fü­gi­gen Anpas­sun­gen in den Steps der Azure Pipe­lines wie­der­ver­wen­den. Zudem wird mit­hilfe von Para­me­tern und Varia­blen sowie einem geeig­ne­ten Secret Manage­ment eine fle­xi­ble und sichere Nut­zung der Pipe­lines für ver­schie­denste Anwen­dungs­fälle in belie­bi­gen Umge­bun­gen ermöglicht.

Den größ­ten Vor­teil einer Migra­tion zu Azure DevOps bie­tet jedoch der Multi-Cloud-Ansatz. Die­ser ermög­licht eine schnel­lere und leich­tere Migra­tion, da die benö­tig­ten Res­sour­cen nicht erst nach Azure migriert wer­den müs­sen. Dadurch wird der Auf­wand auch bei weit fort­ge­schrit­te­nen Pro­jek­ten nicht zu groß, sodass eine Migra­tion zu Azure DevOps jeder­zeit eine inter­es­sante Option zur Ver­bes­se­rung der CI / CD – Pro­zesse im Pro­jekt darstellt.