Blog

Untersuchung der Leistung von az-cli auf den gehosteten Azure Pipelines und GitHub Runnern

Jesse Houwing

Aktualisiert Oktober 15, 2025
12 Minuten

Ich habe in den letzten Tagen ein paar weitere Workflows und Pipelines erstellt und mit der az-cli experimentiert. Dabei bin ich auf alle Arten von Leistungsproblemen gestoßen.
Azure CLI ist ein großartiges Tool, um mit Azure und Azure DevOps zu kommunizieren, und es gibt eine AzureCLI@v2 Aufgabe in Azure DevOps, die Ihr Azure-Abonnement vorkonfiguriert. Beim Testen war ich zunehmend frustriert darüber, wie langsam az auf GitHub Actions und Azure Pipelines gehosteten Läufern ist. Aber nur unter Windows.
In der letzten Woche habe ich eine ganze Reihe von Problemen gefunden, ein paar Probleme gemeldet und einige Pull Requests eingereicht. Ich gehe mit Ihnen durch meine Erkenntnisse, die vielleicht auch Ihnen helfen, die Leistung Ihrer Arbeitsabläufe zu verbessern!

Fallfragen

Es hat sich herausgestellt, dass das Gehäuse Ihrer Befehle wichtig ist, wenn es um az. Sie würden es nicht vermuten, aber az version benötigt etwa 12 Sekunden für die Ausführung auf dem gehosteten Runner, einschließlich des Overheads für den Start einer neuen Powershell-Sitzung. Aber az Version dauert etwa eine Minute. Und warum? Nun, es stellt sich heraus, dass az alle Befehle, die von den vielen Plugins zur Verfügung gestellt werden, in einem Zwischenspeicher ablegt und den Befehl nach Schlüssel sucht, um zu sehen, ob er den von Ihnen eingegebenen Befehl erkennt. Bei dieser Suche wird die Groß- und Kleinschreibung berücksichtigt. Ausgabe eingereicht:
Stellen Sie zunächst sicher, dass Sie az devops und nicht az DevOps aufrufen. Verflucht sei die Autokorrektur, die Devops immer in DevOps umwandelt!

az devops login gegen AZURE_DEVOPS_EXT_PAT

Um sich bei Azure DevOps zu authentifizieren, haben Sie einige Optionen zur Auswahl. Sie können AzureCLI@2 mit einer Azure-Dienstverbindung verwenden oder az devops login zu Ihrem Skript hinzufügen oder ein Token über die Umgebungsvariable AZURE_DEVOPS_EXT_PAT übergeben. Die Konfiguration Ihrer Azure Service Connection ist die langsamste von allen. Gefolgt von az devops login, wobei die Umgebungsvariable die schnellste von allen sein sollte. Aber es stellt sich heraus, dass es viel komplexer ist. Das liegt daran, dass az devops login bei der ersten Ausführung versucht, das Python-Paket keyringzu installieren. Auf den Hosted Runnern wurde der Befehl noch nie aufgerufen, so dass der erste Aufruf 10-20 Sekunden länger dauert. Sie haben an einem Pull Request mitgearbeitet, um die Installation von keyring während der Image-Erstellung zu erzwingen:
Und ich habe einen Antrag gestellt, um sicherzustellen, dass dieses Paket standardmäßig in az devops enthalten ist:
Sie könnten also denken, dass das Setzen von AZURE_DEVOPS_EXT_PAT am schnellsten ist, aber was unter der Haube passiert, könnte Sie überraschen: az devops wird zuerst versuchen, az login und az devops login auszuführen , bevor es versucht, die Umgebungsvariable zu verwenden, selbst wenn Sie sie ausdrücklich übergeben. Es sollte eine Möglichkeit geben, die verwendete Konfiguration zu erzwingen. Hat eine Ausgabe eingereicht:

Die erste Ausführung von az auf dem Hosted Runner führt immer zu einem Neuaufbau des Befehlsindexes

Ähnlich wie bei der obigen Frage zu Fallangelegenheiten, ist das erste Mal az auf den gehosteten Läufern aufgerufen wird, wird ein Befehl zum Neuaufbau des Index ausgelöst. Und warum? Weil der Befehlsindex im Benutzerprofil des Benutzers, der Ihren Workflow ausführt, nicht existiert. Diese Dateien sind normalerweise in Ihrem Profilverzeichnis unter ~/.azure und ~/.azure-devops gespeichert. Da Ihr Workflow jedoch in einem unberührten Benutzerprofil ausgeführt wird, sind diese zwischengespeicherten Dateien nicht mehr vorhanden. Ich habe einen Pull Request eingereicht, um diese Ordner an einen dauerhaften Ort zu verschieben:

Die erste Ausführung von az auf einem gehosteten Läufer verursacht eine Python-Kompilierung

Da der Code zur Bilderzeugung nie wirklich ausgeführt wird az und az devops und weil die ~/.azure Verzeichnis und ~/.azure-devops werden dem Benutzer, der den Workflow ausführt, nicht zur Verfügung gestellt. Beim ersten Durchlauf werden mehrere Python-Module kompiliert und das Ergebnis zwischengespeichert. Dies wird auch durch das Setzen von Umgebungsvariablen und den Aufruf von az --help und az devops --help während der Bilderzeugung behoben. Ich habe einen Pull Request eingereicht, um diese Ordner an einen dauerhaften Ort zu verschieben und az und az devops aufzuwärmen:
Einige der Daten, die az devops benötigt, sind nicht unter ~/.azure gespeichert, sondern haben ihren eigenen Konfigurationspfad unter ~/.azure-devops. Bei dem Versuch, diesen Ordner und den darunter liegenden Ordner ./python/cache umzuleiten, habe ich festgestellt, dass nicht alle Möglichkeiten, diese Ordner umzuleiten, genau die gleiche Wirkung haben, da der Speicherort des Cache-Ordners auf unterschiedliche Weise berechnet wird. Also habe ich eine weitere Ausgabe eingereicht:

Standardkonfiguration von az

Die Standardkonfiguration von az lässt mehrere Einstellungen aktiviert oder unkonfiguriert:
  • Telemetrie
  • Festplattenprotokollierung
  • Fortschrittsbalken
  • Farben der Konsole
  • Umfrage-Eingabeaufforderungen
  • Auto-Upgrade
Es gibt einen ziemlich unbekannten Befehl az init, der Sie fragt, ob Sie in einer interaktiven oder einer automatisierten Umgebung arbeiten möchten, und der eine Reihe dieser Einstellungen auf "Aus" setzt. Also habe ich einen Pull-Request eingereicht, um das Gleiche auf dem Hosted Runner zu tun:
Diese Änderungen werden derzeit geprüft, da sie nicht die gleichen Auswirkungen haben wie die anderen oben erwähnten Änderungen(einige davon könnten zu Störungen führen). Wenn Sie möchten, können Sie diese Änderungen in Ihren eigenen Arbeitsabläufen vornehmen. Und ich habe einen Pull-Request eingereicht, um das Auto-Upgrade in az init zu deaktivieren:

AzureCLI@2 führt eine Prüfung auf Versionsaktualisierung durch

Wenn Sie die AzureCLI@2 Aufgabe ausführt, wird als erstes die
az --version
Diese wiederum führt eine Aktualisierungsprüfung durch und speichert die Ergebnisse im Cache. Aber wie Sie sich denken können, ist dieses zwischengespeicherte Ergebnis nicht im ursprünglichen Profil des Benutzers verfügbar, der Ihren Workflow ausführt. Ich bin der Meinung, dass AzureCLI@2 ohnehin keinen Update-Check durchführen sollte, und es stellt sich heraus, dass es einen zweiten Befehl az version gibt (beachten Sie das Fehlen von --). Er macht im Wesentlichen das Gleiche, überspringt aber die Update-Prüfung. Hat einen weiteren Pull Request eingereicht:
Und obwohl es schön ist, dass es den alternativen Befehl gibt, scheint es mir besser zu sein, die Versionsprüfung konfigurierbar zu machen:

Die automatische Erkennung Ihrer Azure DevOps Organisation und Ihres Projekts ist langsamer

Viele az devops müssen Sie den Standort Ihrer Azure DevOps-Organisation und den Namen des Projekts, mit dem Sie sich verbinden, kennen. Wenn Sie diese nicht explizit angeben, versucht az devops, git remote --verbose aufzurufen, um die Details zu ermitteln. Das ist ein weiterer kleiner Mehraufwand. Da Azure Pipelines die Organisation und das Projekt bereits kennt und Umgebungsvariablen für Sie setzt, können Sie diese auch explizit übergeben:
- pwsh: |
    az pipelines runs show --id $env:BUILD_BUILDID --query "definition.id" `
      --organization $env:SYSTEM_COLLECTIONURI `
      --project $env:SYSTEM_TEAMPROJECT
Es wäre schön, wenn --detect diese Umgebungsvariablen verwenden würde, wenn sie verfügbar sind. Ausgabe eingereicht:

Bei Azure Pipelines läuft alles innerhalb von PowerShell 5.1

In Azure Pipelines werden alle Aufgaben mit Ausnahme von Node-basierten Aufgaben innerhalb von PowerShell 5.1 ausgeführt. Sogar eine Windows Shell-Aufgabe (CmdLine@2) wird innerhalb von PowerShell 5.1 ausgeführt:
Starting: CmdLine
==============================================================================
Task         : Command line
Description  : Run a command line script using Bash on Linux and macOS and cmd.exe on Windows
Version      : 2.212.0
Author       : Microsoft Corporation
Help         : https://docs.microsoft.com/azure/devops/pipelines/tasks/utility/command-line
==============================================================================
##[debug]VstsTaskSdk 0.9.0 commit 6c48b16164b9a1c9548776ad2062dad5cd543352
##[debug]Entering D:a_tasksCmdLine_d9bafed4-0b18-4f58-968d-86655b4d2ce92.212.0cmdline.ps1.
========================== Starting Command Output ===========================
##[debug]Entering Invoke-VstsTool.
##[debug] Arguments: '/D /E:ON /V:OFF /S /C "CALL "D:a_temp5cf768ae-b47d-41b4-a176-02a9957e0e28.cmd""'
##[debug] FileName: 'C:Windowssystem32cmd.exe'
##[debug] WorkingDirectory: 'D:a1s'
Im Gegensatz zu GitHub Actions läuft sogar pwsh innerhalb von PowerShell 5.1 in Azure Pipelines. Das macht script: und pwsh: auf Azure Pipelines ein klein wenig langsamer als die Verwendung von powershell: Aber unter bestimmten Bedingungen (wie z.B. unten) kann dieses winzige bisschen langsamer eine ziemlich deutliche zusätzliche Verzögerung von 6s bis 10s bedeuten. Ich habe beantragt, dass dem Azure Pipelines Agent ein geeigneter PowerShell Core Execution Handler hinzugefügt wird.

Windows versucht vielleicht, hilfreich zu sein

Bei einem Gespräch mit einem Ingenieur des actions/runner-images-Teams sprachen wir über einige Probleme, die in letzter Zeit den gehosteten Runner geplagt haben. Es stellte sich heraus, dass Windows Server einige weitere Möglichkeiten hinzugefügt hat, um sicherzustellen, dass Windows Update immer läuft und um einige Aufträge auszuführen, damit Windows optimal funktioniert. Die gehosteten Runner-Images werden sehr häufig aktualisiert und wenn das Image ausgeführt wird, muss es nur einen Auftrag ausführen, bevor es gelöscht wird. Daher werden diese Dienste während der Erstellung der Windows-Runner-Images deaktiviert. Das heißt, bis vor kurzem. Eine Änderung in Windows hat dazu geführt, dass der "Storage Service" und der "Windows Update Service" wieder aktiviert und neu gestartet werden, sobald das Image hochfährt. Und wenn Sie Pech haben, verlangsamen sie den Hosted Runner, während sie ihre beabsichtigte Aufgabe erfüllen. In Kürze wird ein neues Image auf den Markt kommen, das diese Dienste erneut abschaltet, dieses Mal jedoch ein wenig energischer:
In der Zwischenzeit können Sie dieses Snippet in Ihrem Arbeitsablauf verwenden, um diese Dienste wieder zu deaktivieren:

Azure Pipelines:

#   Azure Pipelines
steps: 
  - pwsh: |
      $services = @("WaasMedicSvc", "StorSvc", "wuauserv")

      $services | %{
        $action = "''/30000/''/30000/''/30000"
        $output = sc.exe failure $_ actions=$action reset=4000

        stop-service $_ -force
        if ($_ -ne "WaasMedicSvc")
        {
          Set-Service -StartupType Disabled $_
        }

        if ((get-service $_).Status -ne "Stopped")
        {
          $id = Get-CimInstance -Class Win32_Service -Filter "Name LIKE '$_'" | 
          Select-Object -ExpandProperty ProcessId
          $process = Get-Process -Id $id
          $process.Kill()
        }
      }

      $services | %{
        write-host $_ $((get-service $_).Status)
      }

  - checkout: self

GitHub-Aktionen:

# ▶️ GitHub Actions
steps: 
  - script: |
      $services = @("WaasMedicSvc", "StorSvc", "wuauserv")

      $services | %{
        $action = "''/30000/''/30000/''/30000"
        $output = sc.exe failure $_ actions=$action reset=4000

        stop-service $_ -force
        if ($_ -ne "WaasMedicSvc")
        {
          Set-Service -StartupType Disabled $_
        }

        if ((get-service $_).Status -ne "Stopped")
        {
          $id = Get-CimInstance -Class Win32_Service -Filter "Name LIKE '$_'" | 
          Select-Object -ExpandProperty ProcessId
          $process = Get-Process -Id $id
          $process.Kill()
        }
      }

      $services | %{
        write-host $_ $((get-service $_).Status)
      }

  - uses: actions/checkout@v4
Einige Leute mögen Microsoft generell für diese Probleme verantwortlich machen, aber natürlich ist dies eine Wechselwirkung, die durch Änderungen von 2 Teams mit sehr unterschiedlichen Zielen verursacht wurde. Während das Windows-Team versucht, Windows so sicher und aktuell wie möglich zu machen, versucht das Runners-Team, ein möglichst schlankes, aktuelles und sicheres Abbild von Windows zu schaffen, das gleichzeitig Hunderte verschiedener Tools von .NET 4 bis .NET 8 und von Python bis Rust sowie Node und PHP ausführen kann. Seltsame Interaktionen sind fast unmöglich zu verhindern.

az devops Daten auf Instanzebene zwischenspeichert

Wann immer Sie eine Verbindung zu einer Azure DevOps-Organisation herstellen, az devops wird 2 Anrufe tätigen:
OPTIONS /{{organization}/_apis
GET /{organization}/_apis/ResourceAreas
Diese Aufrufe geben alle Endpunkte und ihre Aufrufkonvention zurück. Aber diese werden sich wahrscheinlich nicht oft ändern. Ich habe gesehen, dass diese Aufrufe 10-30 Sekunden zum ersten Aufruf von az devops hinzufügen, die einen API-Aufruf tätigen. Also habe ich nach Möglichkeiten gesucht, die Daten zwischenzuspeichern. Ich habe noch nicht die ideale Lösung gefunden, aber dies ist ein funktionierender Prototyp, mit dem az devops die Daten aus einem früheren Workflow-Lauf wiederverwenden kann:
# ⚠️ WARNING work in progress
steps:
    - task: Cache@2
      inputs:
        key: '".az-devops"'
        path: 'C:UsersVssAdministrator.azure-devops'
      displayName: "Restore ~/.azure-devops"

    - pwsh: |
        (dir C:UsersVssAdministrator.azure-devopspython-sdkcache*.json) | %{$_.LastWriteTime = Get-Date} 
      displayName: "Reset last modified on ~/.azure-devops/cache"
Leider funktioniert es noch nicht unter allen Umständen, da die Dateien möglicherweise nicht immer vorhanden sind. Und der Pull Request, der den Speicherort der .azure-devops Ordner ändern wird, wird auch die Pfade im obigen Ausschnitt ändern. Es könnte sich lohnen, eine benutzerdefinierte Aufgabe zu erstellen, die diese Dateien zwischenspeichert. Leider scheinen benutzerdefinierte Aufgaben nicht auf den Cache in Azure Pipelines zugreifen zu können (zumindest nicht über einen Mechanismus im Agenten oder in der Task-Lib) Ich habe einen Antrag gestellt, um Aufgaben das Zwischenspeichern von Daten zu ermöglichen:

Fazit

Sie können die Ergebnisse all dieser Änderungen unten sehen:
Vergleichen Sie die Ausführungszeit mit den zwischengespeicherten Dateien (-on) und ohne sie (-off).
Die Kombination der Änderungen reduziert die Zeit, die für die Ausführung von az zum ersten Mal in einem Arbeitsablauf um etwa 1 Minute. Selbst mit dem zusätzlichen Aufwand für die Einrichtung des Benutzerprofils ist der gesamte Arbeitsablauf schneller. Einige dieser Probleme sind auch nicht auf Windows beschränkt. Das Problem der Groß-/Kleinschreibung, die Installation des Schlüsselbundes, die Standardeinstellungen von Azure CLI und die automatische Erkennung von Azure DevOps Organization gelten auch für Ubuntu- und MacOS-Ausführer. Wenn Sie selbst ein wenig damit herumspielen möchten, finden Sie hier eine Azure Pipelines-Datei, mit der ich die meisten dieser Szenarien getestet habe. Der Hosted Runner ist eine komplexe Sache. Er ist auf Bequemlichkeit ausgelegt und verfügt über Hunderte von Tools, die auf ihm vorinstalliert sind. Viele dieser Tools haben seltsame Wechselwirkungen miteinander. All diese Tools laufen auf einem Betriebssystem, das sich ebenfalls ständig ändert. Viele der Tools, auf die wir uns bei der Entwicklung und Bereitstellung unseres Codes verlassen, sind flexibel, erweiterbar und konfigurierbar und nicht immer darauf ausgelegt, auf einem temporären Server zu laufen, der das Tool nur einmal ausführt. Gleichzeitig muss az jeden Tag Tausende, wenn nicht Millionen Mal auf den Windows Hosted Runnern ausgeführt werden, und die oben genannten Probleme führen dazu, dass es regelmäßig mehr als eine Minute länger als nötig läuft. Ich hoffe, dass die Arbeit, die ich in dieser Woche geleistet habe, nicht nur Ihre Arbeitsabläufe beschleunigt, sondern uns allen Zeit und Geld spart. Und - vor allem - wird es hoffentlich die Kohlenstoffemissionen und den Wasserverbrauch reduzieren. Diese Untersuchung befasste sich hauptsächlich mit einem einzigen Tool und seinen Erweiterungen. Es kann sein, dass eine unbekannte Anzahl ähnlicher Probleme in den Tools lauert, auf die Sie sich verlassen. Bitte nehmen Sie sich die Zeit, diese zu untersuchen und Probleme zu melden, Pull Requests einzureichen und dabei zu helfen, diese Systeme für uns alle zu verbessern. Ein großes Lob an ilia-shipitsin, der sich dieser Probleme angenommen und sie an tatsächlich gehosteten Läufern getestet hat.

Verfasst von

Jesse Houwing

Jesse is a passionate trainer and coach, helping teams improve their productivity and quality all while trying to keep work fun. He is a Professional Scrum Trainer (PST) through Scrum.org, Microsoft Certified Trainer and GitHub Accredited Trainer. Jesse regularly blogs and you'll find him on StackOverflow, he has received the Microsoft Community Contributor Award three years in a row and has been awarded the Microsoft Most Valuable Professional award since 2015. He loves espresso and dark chocolate, travels a lot and takes photos everywhere he goes.

Contact

Let’s discuss how we can support your journey.