{"id":50721130,"url":"https://github.com/amaanx86/azure-devops-agent-operator","last_synced_at":"2026-06-10T00:01:25.485Z","repository":{"id":356045930,"uuid":"1230682705","full_name":"amaanx86/azure-devops-agent-operator","owner":"amaanx86","description":"Kubernetes-native Azure DevOps agent autoscaling for teams that can't or won't run on Managed DevOps Pools","archived":false,"fork":false,"pushed_at":"2026-06-06T19:39:31.000Z","size":312,"stargazers_count":2,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-06T20:16:35.244Z","etag":null,"topics":["autoscaling","azure-devops","go","kubernetes","operator"],"latest_commit_sha":null,"homepage":"https://azure-devops-agent-operator.readthedocs.io","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amaanx86.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-05-06T08:13:44.000Z","updated_at":"2026-06-06T19:39:34.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/amaanx86/azure-devops-agent-operator","commit_stats":null,"previous_names":["amaanx86/azure-devops-agent-operator"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/amaanx86/azure-devops-agent-operator","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amaanx86%2Fazure-devops-agent-operator","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amaanx86%2Fazure-devops-agent-operator/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amaanx86%2Fazure-devops-agent-operator/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amaanx86%2Fazure-devops-agent-operator/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amaanx86","download_url":"https://codeload.github.com/amaanx86/azure-devops-agent-operator/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amaanx86%2Fazure-devops-agent-operator/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34130642,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-09T02:00:06.510Z","response_time":63,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["autoscaling","azure-devops","go","kubernetes","operator"],"created_at":"2026-06-10T00:01:08.609Z","updated_at":"2026-06-10T00:01:25.458Z","avatar_url":"https://github.com/amaanx86.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003ctable border=\"0\" cellpadding=\"0\" cellspacing=\"0\" style=\"border: none;\"\u003e\n  \u003ctr\u003e\n    \u003ctd width=\"200\" align=\"center\"\u003e\n      \u003cimg src=\".github/assets/azure-devops-agent-operator.png\" alt=\"azure-devops-agent-operator logo\" width=\"180\" /\u003e\n    \u003c/td\u003e\n    \u003ctd valign=\"middle\" style=\"padding-left: 20px;\"\u003e\n      \u003ch1\u003eazure-devops-agent-operator\u003c/h1\u003e\n      \u003cp\u003eA Kubernetes operator for elastically-scalable Azure DevOps self-hosted agents.\u003c/p\u003e\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n\n\u003cp\u003e\n  \u003ca href=\"LICENSE\"\u003e\u003cimg alt=\"License: Apache 2.0\" src=\"https://img.shields.io/badge/License-Apache_2.0-blue.svg\"\u003e\u003c/a\u003e\n  \u003cimg alt=\"Kubernetes 1.29+\" src=\"https://img.shields.io/badge/Kubernetes-1.29%2B-326CE5?logo=kubernetes\u0026logoColor=white\"\u003e\n  \u003cimg alt=\"Built with kubebuilder\" src=\"https://img.shields.io/badge/built%20with-kubebuilder%20%2F%20controller--runtime-00ADD8?logo=go\u0026logoColor=white\"\u003e\n  \u003cimg alt=\"Status: Alpha\" src=\"https://img.shields.io/badge/status-alpha-orange.svg\"\u003e\n\u003c/p\u003e\n\n## Background\n\nAzure DevOps offers several ways to run self-hosted, elastically-scalable\nagents. As of May 2026 the landscape looks like this:\n\n| Option | What it is | Where it falls short for our audience |\n|---|---|---|\n| **[Microsoft-hosted agents](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/hosted)** | Fully managed by Microsoft | Fixed VM sizes, no agent-side caching between jobs, no VNET integration, no custom images |\n| **[Azure VM Scale Set agents](https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/scale-set-agents)** | Self-managed VMSS in your subscription | Slow provisioning (minutes), one agent per VM, high maintenance |\n| **[Managed DevOps Pools (MDP)](https://learn.microsoft.com/en-us/azure/devops/managed-devops-pools/overview)** | Microsoft's fully-managed evolution of VMSS, GA November 2024 | Azure-only; agents run in a Microsoft-owned subscription via the host-on-behalf model; not available in every Azure region; opaque to your observability stack |\n| **[KEDA Azure Pipelines scaler](https://keda.sh/docs/latest/scalers/azure-pipelines/)** | General-purpose K8s autoscaler with an Azure Pipelines scaler | Structural limitations documented below |\n| **[Azure Container Apps Jobs + KEDA](https://learn.microsoft.com/en-us/azure/container-apps/jobs)** | Serverless containers with KEDA-based scale-to-zero | Same KEDA limitations; Azure-only; no Docker-in-Docker without privileged mode |\n| **Purpose-built K8s operators** | What this project is | Previously only [MShekow/azure-pipelines-k8s-agent-scaler](https://github.com/MShekow/azure-pipelines-k8s-agent-scaler), [archived July 4, 2025](https://github.com/MShekow/azure-pipelines-k8s-agent-scaler) (maintainer recommended switching to MDP); [microsoft/azure-pipelines-orchestrator](https://github.com/microsoft/azure-pipelines-orchestrator) and [ogmaresca/azp-agent-autoscaler](https://github.com/ogmaresca/azp-agent-autoscaler), both archived earlier |\n\n### Why not Managed DevOps Pools?\n\nFor most teams, MDP or KEDA is the right answer. This operator targets\nthe residual cases where neither fits.\n\nIf you can use MDP, you probably should — it is the right answer for most\nAzure-native teams. This operator exists for the cases MDP doesn't serve:\n\n- **Multi-cloud and on-prem Kubernetes** — MDP runs in Microsoft Azure. If\n  your organisation has standardised on AWS, GCP, OpenShift, or on-prem\n  Kubernetes for everything else, taking a hard Azure dependency just for\n  CI compute is operationally awkward and creates a single-cloud lock-in\n  for your build infrastructure.\n- **Air-gapped, sovereign, and regulated environments** — financial\n  services back offices, government, defense, and healthcare workloads\n  with data-residency or \"no Microsoft-managed compute\" requirements\n  cannot use MDP's host-on-behalf model. They run Azure DevOps Server\n  on-prem and need agents in their own clusters.\n- **Region-restricted tenants** — MDP isn't available in every Azure\n  region. Teams in unsupported regions still need a Kubernetes-native\n  option.\n- **High-volume CI on existing capacity** — both MDP agents and agents\n  run by this operator are \"self-hosted\" from Azure DevOps' perspective\n  and pay the same standard $15/parallel-job/month Azure DevOps fee. The\n  difference is the underlying compute: MDP additionally charges Azure\n  VM, storage, and egress rates for the agents Microsoft runs on your\n  behalf, while running agents on your existing Kubernetes cluster\n  consumes capacity you already pay for. For high-volume CI workloads\n  with spare cluster capacity, this can be materially cheaper.\n- **Host-level observability** — MDP agents run in a Microsoft-managed\n  substrate: you cannot install custom Prometheus exporters on the\n  host, profile builds at the kernel level, or collect host-level\n  metrics. (MDP does support BYO-VNet and proxy configurations for\n  network integration.) Platform teams that want CI agent telemetry\n  alongside the rest of their workloads in the same Grafana stack\n  benefit from running agents as plain Pods they fully own.\n\n### Why not KEDA?\n\nIf MDP is off the table, KEDA's first-party Azure Pipelines scaler is\nthe sanctioned alternative. It is also the path Microsoft pointed users\nat when they archived [`azure-pipelines-orchestrator`](https://github.com/microsoft/azure-pipelines-orchestrator).\nKEDA works, and for the simplest workloads it is the right tool. But it\nhas structural limitations the operator pattern can solve cleanly:\n\n- **Multi-container agents are cumbersome.** You cannot use Azure\n  Pipelines' native *demands / capabilities* feature to route jobs to\n  pods with different toolchains. Instead you have to create a dedicated\n  agent pool per toolchain and maintain a parallel set of KEDA\n  `ScaledJob` manifests for each. This scales poorly past a handful of\n  toolchains.\n- **Dynamically-defined containers from pipeline YAML are not\n  supported.** If job #1 builds an image with a tag derived from a\n  pipeline variable and job #2 needs to run inside that image, the only\n  KEDA-compatible workaround is an *ephemeral container* injected into\n  a running pod — which can't be protected via `preStop` lifecycle\n  hooks, is invisible to most tooling, and whose resource usage is not\n  accounted for via `requests`/`limits`.\n- **True scale-to-zero requires manual dummy-agent management.** KEDA\n  requires `minReplicaCount \u003e 0` for each agent pool, otherwise the\n  Azure Pipelines platform won't dispatch jobs at all (this is an Azure\n  Pipelines platform behavior, not a KEDA bug). To scale to zero you\n  have to register fake/offline dummy agents yourself for every pool\n  and every demand combination.\n- **`ScaledObject` mode can kill long-running agent pods mid-job.**\n  When using KEDA's Deployment-based `ScaledObject` scaler, scaling\n  decisions are based on pending-job count alone. If two jobs are\n  pending and KEDA schedules two pods, then one finishes quickly, KEDA\n  reduces the desired replica count and Kubernetes may pick the\n  still-running pod to terminate. KEDA's `ScaledJob` mode (one Job per\n  pending pipeline job) avoids this — but at the cost of the next\n  bullet:\n- **Ephemeral `ScaledJob` pods cannot safely share cache volumes.**\n  Running agents as ephemeral `Kubernetes Job`s with the AZP agent's\n  `--once` flag is the recommended KEDA pattern, and it does avoid the\n  mid-job-kill class of bug. Kubernetes' `ReadWriteOncePod` (RWOP)\n  access mode (GA in Kubernetes 1.29) can enforce exclusive single-pod\n  mounting, but it does not solve warm-cache reuse: with KEDA's\n  ephemeral `ScaledJob` `--once` pattern, an RWOP cache volume only\n  serialises jobs (the next job blocks until the volume is released).\n  Sharing a warm cache across ephemeral jobs still forces either a\n  cold-cache penalty per job or `ReadWriteMany` storage and its own\n  trade-offs. This operator instead manages a pool of warm cache\n  volumes bound exclusively to recycled agent pods.\n\n### What this project does\n\n`azure-devops-agent-operator` (this project) is a pure Kubernetes\noperator that solves the above with controller-managed pod lifecycle,\ndemand-aware capability matching, true scale-to-zero with automatic\ndummy-agent management, and exclusive cache-volume binding per pod. The\noriginal solution to this shape of problem was MShekow's\n[`azure-pipelines-k8s-agent-scaler`](https://github.com/MShekow/azure-pipelines-k8s-agent-scaler);\nthat project was archived on July 4, 2025 with the maintainer\nrecommending Managed DevOps Pools as the replacement. For the audiences\nlisted above that cannot or will not use MDP, no actively-maintained\nKubernetes-native option remained — which is why this project exists.\n\nThis is **not** a fork or rewrite of MShekow's code. The architecture\nand API design are independent. Where MShekow made design choices\ndocumented in his blog and README, those documents have been valuable\nprior art for understanding the problem space.\n\n### Known limitations\n\n- **Undocumented AZP jobs API** - The Azure Pipelines job-queue\n  endpoint this operator polls is the same undocumented Microsoft API\n  that KEDA uses. The KEDA project explicitly warns that its shape can\n  change without notice and that query parameters like `$top` alter the\n  JSON structure in ways that break agent matching. The demand-aware\n  capability matching feature is the part of this operator most exposed\n  to that fragility.\n\n- **Offline dummy-agent requirement** - True scale-to-zero requires\n  pre-registered offline agents so the Azure Pipelines platform queues\n  jobs when no live agents exist. This is an Azure Pipelines platform\n  constraint that affects this operator exactly as it affects KEDA. The\n  operator automates dummy-agent registration, but the underlying\n  platform dependency remains.\n\n### Acknowledgments\n\nThis project owes a substantial intellectual debt to:\n\n- Marius Shekow's [blog post](https://www.augmentedmind.de/2023/12/10/azure-pipelines-agents-kubernetes-operator/)\n  and the archived [`azure-pipelines-k8s-agent-scaler`](https://github.com/MShekow/azure-pipelines-k8s-agent-scaler)\n  project, which mapped the problem space clearly\n- The KEDA project for the [Azure Pipelines scaler](https://keda.sh/docs/latest/scalers/azure-pipelines/),\n  which establishes the queue-polling pattern this operator builds on\n- Microsoft's [`azure-pipelines-orchestrator`](https://github.com/microsoft/azure-pipelines-orchestrator)\n  (also archived), which validated the operator pattern was viable\n\n## Contributing\n\nSee [CONTRIBUTING.md](CONTRIBUTING.md) for guidelines on how to contribute.\n\n## License\n\nCopyright 2026 Amaan Ul Haq Siddiqui.\n\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famaanx86%2Fazure-devops-agent-operator","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famaanx86%2Fazure-devops-agent-operator","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famaanx86%2Fazure-devops-agent-operator/lists"}