Effizientes Deployment: Ein Blick auf CI/CD mit Azure DevOps

Effizientes Deployment: Ein Blick auf CI/CD mit Azure DevOps

Bereit, die Auslieferung deiner Software zu optimieren? In unserem Blogbeitrag zeigen wir, warum CI/CD in der Software-Entwicklung mittlerweile eine unerlässliche Methode ist, um die Bereitstellung von Software schneller, stabiler und reproduzierbarer umzusetzen. Finde heraus, wie einfach CI/CD mithilfe von Azure DevOps umsetzbar ist und Anwendungen selbst in On-Premise Umgebungen ausgeliefert werden können.

Was ist CI/CD?

CI/CD ist eine Methode, um den Entwicklungsprozess zu automatisieren und zu beschleunigen. Die Abkürzung setzt sich aus drei Teilbegriffen zusammen: Continuous Integration, Continuous Delivery und Continuous Deployment.

Continuous Integration (CI) bezieht sich darauf, dass Codeänderungen regelmäßig auf dem Haupt-Branch des Repositories zusammengeführt und automatisiert getestet werden. Dadurch wird sichergestellt, dass eine neue Software-Version vor der Veröffentlichung funktionsfähig sein muss.

Continuous Delivery (CD) ist eine Erweiterung von Continuous Integration, wodurch eine Software-Version automatisiert auf eine Test- oder sogar die Produktiv-Umgebung ausgeliefert werden kann. Das führt dazu, dass nicht nur automatisiert getestet wird, sondern eine neue Version jederzeit per Knopfdruck veröffentlicht werden kann.

Continuous Deployment (ebenfalls CD) geht noch einen Schritt weiter als Continuous Delivery. Jede neue Version, die alle vorherigen Schritte im Veröffentlichungs-Prozess erfolgreich abgeschlossen hat, wird automatisiert ausgeliefert. Es gibt keine manuelle Bestätigung, nur ein fehlgeschlagener Test würde eine Veröffentlichung verhindern.

Der Unterschied zwischen Continuous Delivery und Continuous Deployment ist somit nur, dass bei Continuous Delivery zu festgelegten Zeiten (nächtlich, wöchentlich, etc.) eine neue Version veröffentlich wird, während bei Continuous Deployment dies bei jeder Änderung geschieht.

Technologien für CI/CD

Bei der Implementierung von CI/CD gibt es verschiedenste Tools und Plattformen wie Jenkins, TeamCity, Travis CI, GitLab CI und viele weitere. Für unseren Arbeitsprozess haben sich aber zwei Plattformen als äußerst effektiv erwiesen: Azure DevOps und GitHub Actions. Diese Plattformen bieten umfassende Funktionen für CI/CD und sind integraler Bestandteil vieler Entwicklungsprozesse.

Azure DevOps in Aktion: Eine erste CI-Pipeline

Wenn man CI/CD mithilfe von Azure DevOps umsetzt, wird der CI/CD Prozess einer Anwendung über eine Pipeline abgebildet. Eine Pipeline kann aus mehreren Phasen (Stage), Jobs und Schritten (Step) bestehen. Ein Schritt ist der kleinste Baustein einer Pipeline und kann zum Beispiel das Kompilieren der Anwendung oder das Ausführen eines Skripts sein. Ein Job besteht aus mehreren Schritten und definiert, auf welchem Agenten (mehr dazu später) diese ausgeführt werden. Phasen können dann dafür genutzt werden, um die Pipeline in mehrere logische Gruppen einzuteilen, zum Beispiel in Dev, QA und Produktion. Das Ergebnis einer Pipeline ist oft auch ein zugehöriges Artefakt, welches die kompilierte Anwendungsversion beinhaltet und für darauffolgende CD-Pipelines verfügbar macht.

Nun genug der Theorie, werfen wir den Blick auf ein konkretes Beispiel. Um zu verdeutlichen, wie einfach CI/CD mit Azure DevOps umgesetzt werden kann, betrachten wir zuerst die folgende CI-Pipeline für eine ASP.NET Core Anwendung:

trigger:
- main

jobs:
- job: build_and_deploy
  displayName: 'Build and Deploy'
  pool:
    vmImage: 'windows-latest'
  steps:
  - task: UseDotNet@2
    displayName: 'Install latest version of .NET 8 SDK'
    inputs:
      version: '8.x'

  - script: dotnet restore
    displayName: 'Restore NuGet packages'

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

  - script: dotnet test --no-restore --no-build --configuration Release 
    displayName: 'Test'

  - task: DotNetCoreCLI@2
    displayName: 'Publish the ASP.NET Core application'
    inputs:
      command: 'publish'
      publishWebProjects: true
      arguments: ' --no-restore --no-build --configuration Release --output $(Build.ArtifactStagingDirectory)'

  - task: PublishBuildArtifacts@1
    displayName: 'Upload Artifact'
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      publishLocation: 'Container'

Das YAML-Dokument definiert eine Pipeline, die aus den folgenden Schritten besteht:

  1. Wiederherstellen der NuGet-Pakete: Dieser Schritt stellt sicher, dass alle benötigten Pakete für das Projekt vorhanden sind.
  2. Kompilieren der Anwendung: Der Code wird in einem eigenen Schritt kompiliert, um Kompilierungsfehler gesondert zu erkennen.
  3. Testen der Anwendung: Definierte Unit-Tests werden ausgeführt, um die Korrektheit wesentlicher Funktionalität zu gewährleisten.
  4. Erstellung des Anwendungs-Artefakts: Die Anwendung wird in dem angegebenen Verzeichnis veröffentlicht. Die Flags --no-build und --no-restore werden verwendet, damit die ersten beiden Schritte nicht nochmal durchgeführt werden.
  5. Speichern des Artefakts: Das Anwendungs-Artefakt wird gespeichert und kann in einer darauffolgenden Pipeline wiederverwendet werden, ohne die Anwendung neu kompilieren zu müssen.

Die Pipeline wird automatisch auf einem Microsoft Hosted-Agent (einem von Microsoft zur Verfügung gestellten Server in der Cloud) ausgeführt, sobald Änderungen im Haupt-Branch (main) des Repositories vorgenommen werden. Dies stellt sicher, dass die Anwendung bei jeder Änderung erfolgreich kompiliert und getestet wird. In weiterer Folge kann die Pipeline noch weiter verbessert werden: Einführung verschiedener Phasen (Dev, QA, Production), Versionierung der Assemblies, Konfiguration von App-Settings, Setzen von Lizenzschlüsseln, etc.

CI/CD mit On-Premise Servern und Azure DevOps

Da Azure DevOps sehr gut mit diversen Hosting-Anbietern integriert ist, wäre ein Deployment z.B. nach Microsoft Azure mit nur einem einzigen weiteren Schritt in der Pipeline möglich (siehe Azure App Service Deployment und Service Connections) - unsere CI-Pipeline würde somit zur CI/CD Pipeline werden. Was aber, wenn unsere Anwendung nicht bei einem gut integrierten Hosting-Anbieter gehostet werden soll, sondern auf einem selbst gewarteten On-Premise Server?

Zum Glück hat Azure DevOps auch für CI/CD mit On-Premise Servern eine Lösung: Environments. Wenn man den On-Premise Server als Environment konfiguriert, kann man die notwendigen CD-Schritte über die Pipeline direkt auf dem On-Premise Server ausführen - vollautomatisiert durch Azure DevOps.

Aber am besten veranschaulichen wir Environments anhand des folgenden Beispiels: Wie würde die vorherige CI-Pipeline aussehen, wenn das Artefakt anschließend auf einen On-Premise IIS-Server ausgeliefert werden soll? Zuerst muss man ein Environment mit einem Namen (hier “Our On-Premise Server”), wie in der Azure DevOps Dokumentation erklärt, erstellen. Danach kann die folgende CI/CD-Pipeline das erstellte Environment nutzen und unsere ASP.NET Core Anwendung dorthin ausliefern:

trigger:
- main

jobs:
- job: build
  displayName: 'Build Artifact'
  pool:
    vmImage: 'windows-latest'
  steps:
  - task: UseDotNet@2
    inputs:
      version: '8.x'

  - script: dotnet restore

  - script: dotnet build --no-restore --configuration Release

  - script: dotnet test --no-restore --no-build --configuration Release 

  - task: DotNetCoreCLI@2
    inputs:
      command: 'publish'
      publishWebProjects: true
      arguments: ' --no-restore --no-build --configuration Release --output $(Build.ArtifactStagingDirectory)'

  - task: PublishBuildArtifacts@1
    inputs:
      PathtoPublish: '$(Build.ArtifactStagingDirectory)'
      publishLocation: 'Container'

- deployment: deploy
  displayName: 'Deploy Artifact'
  environment: 
    name: 'Our On-Premise Server'
  variables:
    iisSiteName: '<Name of the IIS site>'
    iisAppPool: '<Name of the IIS app pool>'
  strategy: 
    runOnce:
      deploy:
        steps:
        - task: IISWebAppManagementOnMachineGroup@0
          displayName: Stop IIS-Site
          inputs:
            IISDeploymentType: 'IISWebsite'
            ActionIISWebsite: 'StopWebsite'
            StartStopWebsiteName: '$(iisSiteName)'

        - task: IISWebAppManagementOnMachineGroup@0
          displayName: Stop IIS-AppPool
          inputs:
            IISDeploymentType: 'IISApplicationPool'
            ActionIISApplicationPool: 'StopAppPool'
            StartStopRecycleAppPoolName: '$(iisAppPool)'

        - task: IISWebAppDeploymentOnMachineGroup@0
          displayName: Deploy IIS-Site
          inputs:
            WebSiteName: '$(iisSiteName)'
            Package: '$(Pipeline.Workspace)\**\*.zip'

        - task: IISWebAppManagementOnMachineGroup@0
          displayName: Start IIS-AppPool
          inputs:
            IISDeploymentType: 'IISApplicationPool'
            ActionIISApplicationPool: 'StartAppPool'
            StartStopRecycleAppPoolName: '$(iisAppPool)'

        - task: IISWebAppManagementOnMachineGroup@0
          displayName: Start IIS-Site
          inputs:
            IISDeploymentType: 'IISWebsite'
            ActionIISWebsite: 'StartWebsite'
            StartStopWebsiteName: '$(iisSiteName)'

Das Erstellen des Artefakts passiert weiterhin auf einem Microsoft Hosted-Agent im build-Job, lediglich die Schritte im deploy-Job werden auf dem On-Premise Server direkt ausgeführt. Um die Seite schlussendlich auf die IIS-Instanz auszuliefern, werden folgende Schritte im deploy-Job durchgeführt:

  1. Die Anwendung (Site und App Pool) wird angehalten.
  2. Das Build-Artefakt aus dem vorherigen Job wird ausgeliefert.
  3. Die Anwendung wird neu gestartet.

Achtung: Unser Deployment-Job ist sehr minimalistisch umgesetzt und würde dazu führen, dass während des Deployment unsere ASP.NET Core Anwendung nicht mehr erreichbar ist. Wie ein rolling deployment mit zero-downtime umgesetzt werden kann, ist hier in der Azure DevOps Dokumentation detailliert beschrieben.

Zusammenfassung

CI/CD ist mittlerweile eine unerlässliche Methode in der Software-Entwicklung, um den Entwicklungsprozess zu optimieren. Neue Software-Versionen werden durch CI/CD regelmäßig erstellt und deren Veröffentlichung automatisiert. Neue Versionen werden dadurch regelmäßiger und stabiler ausgeliefert und Benutzer:innen können schneller Feedback geben. Selbst nicht integrierte Hosting-Lösungen können über die verfügbaren Werkzeuge (wie Azure DevOps) leicht in einen CI/CD Prozess integriert werden.

Quellen: