<?xml version="1.0" encoding="utf-8"?><?xml-stylesheet type="text/xsl" href="rss.xsl"?>
<rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/">
    <channel>
        <title>AKS Engineering Blog</title>
        <link>https://blog.aks.azure.com</link>
        <description>AKS Engineering Blog Blog</description>
        <lastBuildDate>Wed, 30 Sep 2026 00:00:00 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <language>en</language>
        <item>
            <title><![CDATA[AKS on bare metal now on Ubuntu]]></title>
            <link>https://blog.aks.azure.com/2026/09/25/aks-baremetal-ubuntu</link>
            <guid>https://blog.aks.azure.com/2026/09/25/aks-baremetal-ubuntu</guid>
            <pubDate>Wed, 30 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Run Azure Kubernetes Service on customer-owned Ubuntu infrastructure with bare metal performance, flexible hardware choices, and consistent Azure management.]]></description>
            <content:encoded><![CDATA[<p>Run Azure Kubernetes Service directly on customer-owned Ubuntu infrastructure with bare metal performance, flexible hardware choices, and consistent management through Azure.</p>
<p>Our vision for Azure Kubernetes Service is simple: customers should be able to run AKS wherever their applications and data need to be.</p>
<p>Today, we are announcing the public preview of AKS on bare metal Ubuntu. Customers can now deploy AKS directly on their Ubuntu infrastructure, without installing or operating a hypervisor between Kubernetes and the underlying hardware.</p>
<p>This release extends the AKS experience to organizations that need Kubernetes close to their applications, devices, and data. It is designed for environments such as retail locations, factories, branch sites, telecommunications infrastructure, and on-premises AI systems, where performance, latency, hardware access, or data residency are critical.</p>
<p>Earlier this year, we introduced AKS running directly on bare metal Azure Linux through Azure Local small form factor. Support for Ubuntu expands that vision by giving customers another way to run AKS on infrastructure they own while maintaining a consistent Kubernetes and Azure management experience.</p>
<p><img decoding="async" loading="lazy" alt="AKS on bare metal Ubuntu high level architecture and key capabilities" src="https://blog.aks.azure.com/assets/images/bare-metal-ubuntu-8ce9b285ed76e51ec55bda43e8ea16ba.png" width="3188" height="1756" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="run-aks-directly-on-ubuntu-infrastructure">Run AKS directly on Ubuntu infrastructure<a href="https://blog.aks.azure.com/2026/09/25/aks-baremetal-ubuntu#run-aks-directly-on-ubuntu-infrastructure" class="hash-link" aria-label="Direct link to Run AKS directly on Ubuntu infrastructure" title="Direct link to Run AKS directly on Ubuntu infrastructure" translate="no">​</a></h2>
<p>Ubuntu is widely deployed across enterprise datacenters, edge environments, and AI infrastructure. Many organizations already have physical Ubuntu servers, established operational practices, and applications built and validated for the Ubuntu ecosystem.</p>
<p>AKS on bare metal Ubuntu allows customers to build on those existing investments. Platform teams can deploy and manage AKS directly on Ubuntu servers, while developers continue using standard Kubernetes APIs and tools.</p>
<p>Removing the virtualization layer also enables Kubernetes workloads to use the available CPU, memory, storage, networking, and accelerator capacity directly. This can improve infrastructure efficiency and provide the hardware access required by performance-sensitive applications.</p>
<p>The Kubernetes control plane, worker capacity, applications, and data remain in the customer’s environment, helping organizations address requirements for low latency, local processing, data sovereignty, and operation in distributed locations.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="built-for-ai-infrastructure-choice">Built for AI infrastructure choice<a href="https://blog.aks.azure.com/2026/09/25/aks-baremetal-ubuntu#built-for-ai-infrastructure-choice" class="hash-link" aria-label="Direct link to Built for AI infrastructure choice" title="Direct link to Built for AI infrastructure choice" translate="no">​</a></h2>
<p>AI applications have diverse infrastructure requirements.</p>
<p>Customers might already own GPUs or specialized accelerators. They might need a particular driver version, device plugin, container runtime, or toolkit for their models and applications. The right configuration can vary significantly based on the hardware, AI framework, and workload.</p>
<p>AKS on bare metal Ubuntu gives customers control over that infrastructure stack. They can select hardware based on workload requirements and configure the supporting GPU software, including:</p>
<ul>
<li class="">GPU drivers</li>
<li class="">Kubernetes device plugins</li>
<li class="">Container runtimes</li>
<li class="">AI frameworks, libraries, and toolkits</li>
</ul>
<p>This flexibility allows organizations to align Kubernetes with their existing hardware investments and application dependencies rather than adopting a predefined GPU configuration.</p>
<p>Direct access to local hardware and data makes AKS on bare metal Ubuntu well suited for AI inference, computer vision, industrial automation, intelligent retail, telecommunications, and data processing at the edge.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="get-started-with-a-single-command">Get started with a single command<a href="https://blog.aks.azure.com/2026/09/25/aks-baremetal-ubuntu#get-started-with-a-single-command" class="hash-link" aria-label="Direct link to Get started with a single command" title="Direct link to Get started with a single command" translate="no">​</a></h2>
<p>The fastest way to try AKS on bare metal Ubuntu is to run a single command on a customer-owned Ubuntu machine:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -sSL https://aka.ms/aksbm | bash -s -- -s &lt;subscription-id&gt; -t &lt;tenant-id&gt;</span><br></div></code></pre></div></div>
<p>This end-to-end quickstart script maintained by the AKS team installs the required Azure tooling, signs the machine into Azure, connects it to Azure Arc if it is not already connected, configures the required Azure providers, and deploys an AKS cluster directly onto the physical server.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="azure-cli">Azure CLI<a href="https://blog.aks.azure.com/2026/09/25/aks-baremetal-ubuntu#azure-cli" class="hash-link" aria-label="Direct link to Azure CLI" title="Direct link to Azure CLI" translate="no">​</a></h3>
<p>The public preview provides a streamlined Azure CLI path from a provisioned, Arc enabled Ubuntu machine to a running AKS cluster. With a single Azure Resource Manager deployment command, customers can create the required Azure resources and deploy AKS directly on their physical server.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aksarc deploy --resource-group &lt;resource-group-name&gt; --arc-machine-names &lt;machine-name&gt;</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="consistent-management-through-azure">Consistent management through Azure<a href="https://blog.aks.azure.com/2026/09/25/aks-baremetal-ubuntu#consistent-management-through-azure" class="hash-link" aria-label="Direct link to Consistent management through Azure" title="Direct link to Consistent management through Azure" translate="no">​</a></h2>
<p>Although applications and data run locally, customers can use familiar AKS and Azure CLI workflows to create and manage their clusters.</p>
<p>AKS on bare metal Ubuntu provides a consistent foundation for:</p>
<ul>
<li class="">Cluster creation and lifecycle management</li>
<li class="">Kubernetes upgrades</li>
<li class="">Identity and access control</li>
<li class="">Governance and policy</li>
<li class="">Monitoring and observability</li>
<li class="">Security management</li>
<li class="">Application delivery using GitOps</li>
</ul>
<p>This gives platform teams centralized visibility and governance without requiring applications or data to leave the customer’s environment. Developers continue using familiar Kubernetes APIs and tools, while infrastructure teams gain an AKS experience that extends to their Ubuntu infrastructure.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="available-now-in-public-preview">Available now in public preview<a href="https://blog.aks.azure.com/2026/09/25/aks-baremetal-ubuntu#available-now-in-public-preview" class="hash-link" aria-label="Direct link to Available now in public preview" title="Direct link to Available now in public preview" translate="no">​</a></h2>
<p>AKS on bare metal Ubuntu is now available in public preview. Customers can deploy AKS directly on supported, customer-owned Ubuntu servers without a hypervisor and manage their clusters using familiar AKS, Azure, and Kubernetes tools.</p>
<p>This preview expands where organizations can run AKS while retaining control over their hardware, GPU software stack, applications, and data.</p>
<p>Get started at <a href="https://aka.ms/aks-bare-metal" target="_blank" rel="noopener noreferrer" class="">aka.ms/aks-bare-metal</a>.</p>]]></content:encoded>
            <category>AKS on bare metal</category>
            <category>AI</category>
            <category>GPU</category>
        </item>
        <item>
            <title><![CDATA[AKS desktop evolves with AI troubleshooting and plugins]]></title>
            <link>https://blog.aks.azure.com/2026/09/24/aks-desktop-ai-troubleshooting</link>
            <guid>https://blog.aks.azure.com/2026/09/24/aks-desktop-ai-troubleshooting</guid>
            <pubDate>Thu, 24 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[See how AKS desktop v0.10.0 uses AI-assisted troubleshooting, cluster context, proactive diagnosis, and plugins to help teams resolve Kubernetes issues.]]></description>
            <content:encoded><![CDATA[<p>As we have talked with more customers about AKS desktop, one theme has come through clearly: people want help when something goes wrong.</p>
<p>It is easy to show a healthy Kubernetes cluster. The harder and more important moment is when a developer opens an application and asks, “Why is my pod broken, and how do I fix it?”</p>
<p>That question shaped AKS desktop v0.10.0. This is our biggest release so far, not because it has the longest list of features, but because it represents where we believe AKS desktop can provide the most value. We are doubling down on AI-assisted troubleshooting and making it easier to move from seeing a problem to understanding what happened and deciding what to do next.</p>
<p><img decoding="async" loading="lazy" alt="AKS desktop showing unhealthy Kubernetes workloads alongside an AI Assistant investigation with recommended checks" src="https://blog.aks.azure.com/assets/images/ai-troubleshooting-2db4732a972a09fe95313b6c184c6f6c.png" width="1200" height="689" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="start-with-the-problem-not-the-tool">Start with the problem, not the tool<a href="https://blog.aks.azure.com/2026/09/24/aks-desktop-ai-troubleshooting#start-with-the-problem-not-the-tool" class="hash-link" aria-label="Direct link to Start with the problem, not the tool" title="Direct link to Start with the problem, not the tool" translate="no">​</a></h2>
<p>Troubleshooting Kubernetes can require a lot of context. A pod might be restarting, but the cause could be its configuration, a missing dependency, a failed health check, insufficient resources, or something elsewhere in the application.</p>
<p>Finding the answer often means moving between resource views, logs, events, metrics, YAML, and documentation. You need to know where to look before you can begin making progress.</p>
<p>We want AKS desktop to shorten that path by bringing the investigation together without requiring you to switch between tools.</p>
<p>With the AI Assistant, you can start from the resource that is showing a problem and ask a direct question. The assistant can use the Kubernetes context available in AKS desktop—including resource state, logs, events, and metrics—to help explain what is happening and recommend the next troubleshooting steps.</p>
<p>The goal is not to replace the developer or operator making the decision. It is to help them get oriented faster. Instead of beginning with “Which command should I run?” they can begin with “Why is this failing?”</p>
<p>For example, if a pod is repeatedly restarting, the assistant can help bring together the warning events, recent logs, and workload configuration that might explain the failure. From there, the user can review the evidence, understand the recommendation, and decide how to proceed.</p>
<p><strong>Watch the walkthrough:</strong> See how AKS desktop helps investigate and fix a broken Kubernetes app in <a href="https://youtu.be/Z9eCdpk2hlk" target="_blank" rel="noopener noreferrer" class="">Debug Kubernetes with AI: Fix a Broken App in AKS desktop</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="make-the-first-investigation-easier">Make the first investigation easier<a href="https://blog.aks.azure.com/2026/09/24/aks-desktop-ai-troubleshooting#make-the-first-investigation-easier" class="hash-link" aria-label="Direct link to Make the first investigation easier" title="Direct link to Make the first investigation easier" translate="no">​</a></h2>
<p>We also heard that an AI troubleshooting experience is only useful if it is easy to start using.</p>
<p>AKS desktop can now automatically detect GitHub Copilot as an AI provider. To use Auto Detect, install the GitHub CLI, sign in to a GitHub account with an active GitHub Copilot subscription, and let AKS desktop detect the authenticated session and available model catalog—including supported Claude models.</p>
<p>You can also connect directly to Anthropic and use Claude with your own API key. AKS desktop continues to support other providers, including Azure OpenAI, OpenAI, Google Gemini, Mistral AI, DeepSeek, and locally hosted or OpenAI-compatible models.</p>
<p><img decoding="async" loading="lazy" alt="AKS desktop AI Assistant settings showing GitHub Copilot Auto Detect and Proactive Diagnosis" src="https://blog.aks.azure.com/assets/images/ai-assistant-settings-e40de6c2d9ba2d24f760838eae3ad803.png" width="1200" height="626" class="img_ev3q"></p>
<p>This flexibility lets individuals and organizations use the provider that fits their existing subscriptions, model preferences, compliance requirements, and data residency needs.</p>
<p>Whichever provider you choose, the important part is the connection between the model and the environment you are already investigating. The assistant can work with the current cluster context instead of relying only on a manually written description of the problem.</p>
<p>It also stays within the permissions of the active Kubernetes identity. Users choose whether to enable Kubernetes API requests, and the assistant does not receive broader cluster access simply because AI is being used.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="bring-more-of-your-operational-knowledge-into-the-experience">Bring more of your operational knowledge into the experience<a href="https://blog.aks.azure.com/2026/09/24/aks-desktop-ai-troubleshooting#bring-more-of-your-operational-knowledge-into-the-experience" class="hash-link" aria-label="Direct link to Bring more of your operational knowledge into the experience" title="Direct link to Bring more of your operational knowledge into the experience" translate="no">​</a></h2>
<p>Troubleshooting is rarely one-size-fits-all. Teams develop their own checks, conventions, tools, and ways of diagnosing common failures.</p>
<p><a href="https://agentskills.io/" target="_blank" rel="noopener noreferrer" class="">Skills</a> are reusable packages of instructions and domain knowledge that help an AI assistant handle specialized tasks. AKS desktop enables the Microsoft-authored <a href="https://github.com/microsoft/azure-skills/tree/main/skills/azure-kubernetes" target="_blank" rel="noopener noreferrer" class="">AKS skill</a> by default, giving the AI Assistant AKS-specific guidance for troubleshooting, networking, security, upgrades, and day-to-day operations. Teams can also enable additional skill repositories or add their own to reflect the practices and knowledge specific to their environments.</p>
<p>We are also introducing support for Model Context Protocol (MCP) servers, which can connect the assistant to additional tools and data sources. These integrations are opt-in, and users can control approval for actions requested by the assistant.</p>
<p>Proactive Diagnosis takes another step toward reducing the time between a problem appearing and someone beginning an investigation. It can surface recent warning and error events with recommendations, giving users a place to start before they have manually assembled all the context themselves.</p>
<p>These capabilities are part of the same direction: make troubleshooting more contextual, more repeatable, and easier to begin.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="keep-aks-desktop-focused-while-making-it-extensible">Keep AKS desktop focused while making it extensible<a href="https://blog.aks.azure.com/2026/09/24/aks-desktop-ai-troubleshooting#keep-aks-desktop-focused-while-making-it-extensible" class="hash-link" aria-label="Direct link to Keep AKS desktop focused while making it extensible" title="Direct link to Keep AKS desktop focused while making it extensible" translate="no">​</a></h2>
<p>AI troubleshooting is the center of this release, but teams can also extend AKS desktop with workflows that fit their environments.</p>
<p>AKS desktop is built on the open-source Headlamp project, and this release includes an integrated Plugin Catalog (preview). You can browse, install, update, and manage plugins directly in AKS desktop instead of setting up each integration separately.</p>
<p>The initial catalog includes plugins for Flux GitOps workflows, cert-manager certificate management, and Kubernetes Event-driven Autoscaling (KEDA). Headlamp plugins can also add dashboards, resource views, visualizations, actions, and integrations with external operational tools.</p>
<p><img decoding="async" loading="lazy" alt="AKS desktop Plugin Catalog showing AKS optimized plugins for Kubernetes workflows" src="https://blog.aks.azure.com/assets/images/plugin-catalog-311023e192272828b7d0d4af964f2854.png" width="1200" height="296" class="img_ev3q"></p>
<p>The catalog initially highlights <strong>AKS desktop optimized</strong> plugins that have passed Microsoft accessibility and localization checks. Turn off that filter to explore Official plugins published by the upstream Headlamp project. Catalog availability can change as plugins are added, updated, deprecated, or removed.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-release-matters-to-me-">Why this release matters (to me 🤗)<a href="https://blog.aks.azure.com/2026/09/24/aks-desktop-ai-troubleshooting#why-this-release-matters-to-me-" class="hash-link" aria-label="Direct link to Why this release matters (to me 🤗)" title="Direct link to Why this release matters (to me 🤗)" translate="no">​</a></h2>
<p>What makes this release important is not any single setting or integration. It is that AKS desktop is becoming more useful in the moment when users need the most help.</p>
<p>When an application is healthy, almost any dashboard can show green status. The real test is what happens when a pod fails, an event raises a warning, or a developer does not know which part of the system to investigate first.</p>
<p>This is our biggest release so far because it moves us closer to a simpler troubleshooting workflow: find the problem, bring the relevant context together, understand the likely cause, and take the next step with confidence.</p>
<p>That is the experience we are working to improve, and I hope that matters to you too!</p>
<p>Download the latest version from the <a href="https://github.com/Azure/aks-desktop/releases" target="_blank" rel="noopener noreferrer" class="">AKS desktop Releases page</a>, try the new AI troubleshooting experience, and tell us where it helps—and where we should keep improving—by <a href="https://github.com/Azure/aks-desktop/issues/new/choose" target="_blank" rel="noopener noreferrer" class="">opening an issue</a>.</p>]]></content:encoded>
            <category>AI</category>
            <category>Troubleshooting</category>
            <category>Observability</category>
            <category>Open Source</category>
        </item>
        <item>
            <title><![CDATA[Announcing the public preview of flex nodes for AKS]]></title>
            <link>https://blog.aks.azure.com/2026/09/22/flex-nodes-for-aks</link>
            <guid>https://blog.aks.azure.com/2026/09/22/flex-nodes-for-aks</guid>
            <pubDate>Tue, 22 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Extend one AKS control plane to run worker nodes securely across Azure regions, edge locations, and on-premises hardware with flex nodes for AKS (preview).]]></description>
            <content:encoded><![CDATA[<p>Today, we’re announcing the public preview of flex nodes for AKS, a new capability that lets you extend an existing Azure Kubernetes Service (AKS) cluster with worker nodes in other Azure regions, on-premises or edge environments. These nodes connect to your cluster’s API server over an encrypted overlay network and operate as standard node pools - extending the Kubernetes scheduler, RBAC, policy, and observability you already use without adding another cluster to manage.</p>
<p>Customers have told us that compute location is rarely a simple choice. Sometimes data cannot contractually leave a specific facility. Sometimes the preferred Azure region is constrained even though suitable capacity is available in another Azure region or on-premises. And sometimes a workload needs to run close to the data, users, or devices it serves to avoid extra latency. Today, each of these situations often means waiting, overprovisioning, or operating another Kubernetes cluster: with another control plane, upgrade cycle, and set of policies. Flex nodes for AKS (preview) is designed to bring that distributed compute into one familiar operating model. Below, we'll explore these common scenarios in more detail.</p>
<p><img decoding="async" loading="lazy" alt="Flex nodes high level architecture and key capabilities" src="https://blog.aks.azure.com/assets/images/flex-nodes-diagram-3765ec7caafd896b9f14b24bfd515021.png" width="1200" height="684" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="addressing-regional-cpu-and-gpu-capacity-limits">Addressing regional CPU and GPU capacity limits<a href="https://blog.aks.azure.com/2026/09/22/flex-nodes-for-aks#addressing-regional-cpu-and-gpu-capacity-limits" class="hash-link" aria-label="Direct link to Addressing regional CPU and GPU capacity limits" title="Direct link to Addressing regional CPU and GPU capacity limits" translate="no">​</a></h2>
<p>GPU-accelerated training and fine-tuning jobs are often blocked not by cost or quota, but by regional SKU availability - a familiar <code>SkuNotAvailable</code> or <code>AllocationFailed</code> error when a specific VM size is exhausted in a region. Historically, resolving this meant over-provisioning in advance or standing up and operating a second cluster in a region where capacity exists.</p>
<p>With flex nodes, cluster operators can register capacity from another Azure region, an edge location, or an on-premises data center as a node pool that joins their existing AKS cluster. The scheduler treats these nodes like any other, respecting the labels, taints, and tolerations already defined for the cluster, so pending workloads can be placed on available capacity without standing up and maintaining a parallel cluster and its associated CI/CD or identity configuration.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="aggregating-compute-across-regions-under-a-single-control-plane">Aggregating compute across regions under a single control plane<a href="https://blog.aks.azure.com/2026/09/22/flex-nodes-for-aks#aggregating-compute-across-regions-under-a-single-control-plane" class="hash-link" aria-label="Direct link to Aggregating compute across regions under a single control plane" title="Direct link to Aggregating compute across regions under a single control plane" translate="no">​</a></h2>
<p>Many organizations run compute across Azure regions and private data centers but manage each location as a separate cluster stitched together with GitOps and service mesh tooling. Flex nodes bring that distributed capacity into a single AKS control plane, supporting a range of Azure authentication methods like managed identity, Azure Arc, and service principal. Operators retain a single view of the cluster's full footprint through standard tools like kubectl, while workload placement continues to use native Kubernetes primitives, including node affinity, taints and tolerations, and topology spread constraints.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="workload-placement-for-data-residency-on-premises">Workload placement for data residency on-premises<a href="https://blog.aks.azure.com/2026/09/22/flex-nodes-for-aks#workload-placement-for-data-residency-on-premises" class="hash-link" aria-label="Direct link to Workload placement for data residency on-premises" title="Direct link to Workload placement for data residency on-premises" translate="no">​</a></h2>
<p>Regulated workloads in healthcare, financial services, and the public sector frequently require that data remain within a specific facility, without managing the lifecycle of a separate on-premises Kubernetes stack. Flex nodes allows on-premises servers to join an AKS cluster as worker nodes. Operators can then use node selectors and taints to pin workloads that must remain local to that hardware, while the remainder of the cluster's workloads run in Azure. Cluster-wide setup remains unified across both environments, and only the data and the compute that processes it stay on-premises.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="get-started-today">Get started today<a href="https://blog.aks.azure.com/2026/09/22/flex-nodes-for-aks#get-started-today" class="hash-link" aria-label="Direct link to Get started today" title="Direct link to Get started today" translate="no">​</a></h2>
<p>Flex nodes for AKS is available today in public preview. Documentation, including setup guidance and supported scenarios, is available on Microsoft Learn: <a href="https://aka.ms/aks/flexnodeforaks" target="_blank" rel="noopener noreferrer" class="">flex nodes for AKS overview</a>.
As with all AKS preview features, this release is shaped directly by customer feedback. We're particularly interested in hearing about your capacity and networking use cases, additional provider and hardware scenarios, and any gaps you encounter while testing. Please share feedback through the <a href="https://aka.ms/aks-flex-node/github" target="_blank" rel="noopener noreferrer" class="">AKS Flex Node GitHub repository</a> and we look forward to seeing what you build!</p>]]></content:encoded>
            <category>Operations</category>
            <category>DevOps</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[Running GitHub Actions Runner Controller on AKS Automatic]]></title>
            <link>https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic</link>
            <guid>https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic</guid>
            <pubDate>Thu, 17 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Create an AKS Automatic cluster, install Actions Runner Controller, and run a GitHub Actions workflow on ephemeral runner pods in your Azure network.]]></description>
            <content:encoded><![CDATA[<p>GitHub Actions Runner Controller, also known as GitHub ARC or ARC for short, is a popular way to run self-hosted GitHub Actions runners on Kubernetes. In this walkthrough, we’ll set up ARC on <a href="https://learn.microsoft.com/azure/aks/intro-aks-automatic" target="_blank" rel="noopener noreferrer" class="">AKS Automatic</a> and run a real GitHub Actions job on an ephemeral runner pod.</p>
<p>The combination of ARC and AKS Automatic gives you the power of ARC on a production-ready AKS cluster with managed node pools, built-in monitoring, scaling, security settings, and other defaults that follow <a href="https://learn.microsoft.com/azure/well-architected/service-guides/azure-kubernetes-service" target="_blank" rel="noopener noreferrer" class="">AKS best practices</a>. For runner workloads specifically, the <a href="https://learn.microsoft.com/azure/aks/intro-aks-automatic#pod-readiness-sla" target="_blank" rel="noopener noreferrer" class="">pod readiness SLA</a> is also a strong fit because CI/CD jobs depend on predictable pod startup.</p>
<p>ARC runner scale sets work extremely well on AKS Automatic, and the secure-by-default posture of AKS Automatic helps ensure your configuration is optimized and secure. You get the core ARC benefits: GitHub-native CI jobs, Kubernetes-native ephemeral runners, and runners that can live inside your Azure network, including private virtual networks. That means build jobs can reach private endpoints, internal services, and locked-down dependencies without exposing those resources to the public internet.</p>
<p>AKS Automatic is secure by default and has production-minded safeguards enabled. That's a good thing, but it also means the default public ARC Helm chart values need a little tuning. In particular, we need to be explicit about resource requests and image tags.</p>
<p>Let’s walk through the full setup.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-well-build">What we'll build<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#what-well-build" class="hash-link" aria-label="Direct link to What we'll build" title="Direct link to What we'll build" translate="no">​</a></h2>
<p>In this lab we'll deploy:</p>
<ul>
<li class="">An AKS Automatic cluster.</li>
<li class="">The ARC runner scale set controller.</li>
<li class="">A repository-scoped runner scale set.</li>
<li class="">A sample GitHub Actions workflow that targets the runner scale set.</li>
</ul>
<p>The goal is pretty simple. We'll create a workflow where <code>runs-on</code> maps to the ARC runner scale set. When the job queues, ARC creates an ephemeral runner pod on AKS Automatic, the runner connects to GitHub, the job runs, and then the runner goes away.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerequisites">Prerequisites<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites" translate="no">​</a></h2>
<p>First, make sure you have the <a href="https://learn.microsoft.com/cli/azure/install-azure-cli" target="_blank" rel="noopener noreferrer" class="">Azure CLI</a>, <a href="https://cli.github.com/" target="_blank" rel="noopener noreferrer" class="">GitHub CLI</a>, <a href="https://kubernetes.io/docs/tasks/tools/" target="_blank" rel="noopener noreferrer" class=""><code>kubectl</code></a>, <a href="https://azure.github.io/kubelogin/install.html" target="_blank" rel="noopener noreferrer" class="">kubelogin</a>, and <a href="https://helm.sh/docs/intro/install/" target="_blank" rel="noopener noreferrer" class="">Helm</a> installed.</p>
<p>Sign in to Azure and GitHub:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az login</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">gh auth login</span><br></div></code></pre></div></div>
<p>Install or update the AKS preview extension:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az extension add --name aks-preview --upgrade</span><br></div></code></pre></div></div>
<p>Check that the local Kubernetes, Azure auth, Helm, and GitHub CLIs are ready:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl version --client</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubelogin --version</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">helm version</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">gh auth status</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>You'll also need a GitHub token with permission to manage Actions runners for the target repository. For a quick repo-scoped lab, a classic PAT with <code>repo</code> scope is enough for a private repo. For anything production-ish, I'd use a <a href="https://docs.github.com/apps/creating-github-apps/about-creating-github-apps/about-creating-github-apps" target="_blank" rel="noopener noreferrer" class="">GitHub App</a> instead so the permissions and rotation story are cleaner.</p></div></div>
<p>Read the token interactively so it doesn't end up in your shell history:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">read -rsp "GitHub token: " GITHUB_TOKEN</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="set-environment-variables">Set environment variables<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#set-environment-variables" class="hash-link" aria-label="Direct link to Set environment variables" title="Direct link to Set environment variables" translate="no">​</a></h2>
<p>Now set up a few variables. Update these for your subscription and GitHub repo:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">export LOCATION=eastus</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export RG=rg-arc-auto-lab</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export CLUSTER=arc-auto-lab</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export GITHUB_OWNER="github-owner"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export GITHUB_REPO="github-repo"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export GITHUB_CONFIG_URL="https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export RUNNER_SET_NAME=arc-auto-runners</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export ARC_SYSTEMS_NAMESPACE=arc-systems</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export ARC_RUNNERS_NAMESPACE=arc-runners</span><br></div></code></pre></div></div>
<p>The <code>RUNNER_SET_NAME</code> value is important. This is also the value we'll use in the workflow <code>runs-on</code>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-or-clone-the-target-github-repo">Create or clone the target GitHub repo<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#create-or-clone-the-target-github-repo" class="hash-link" aria-label="Direct link to Create or clone the target GitHub repo" title="Direct link to Create or clone the target GitHub repo" translate="no">​</a></h2>
<p>The validation workflow needs to live in a GitHub repository. If you're starting with a new repo for the lab, create it and clone it locally:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">gh repo create "${GITHUB_OWNER}/${GITHUB_REPO}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --private \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --clone</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd "${GITHUB_REPO}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export GITHUB_BRANCH=$(git branch --show-current)</span><br></div></code></pre></div></div>
<p>If you already have a repo, clone it instead:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">gh repo clone "${GITHUB_OWNER}/${GITHUB_REPO}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd "${GITHUB_REPO}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export GITHUB_BRANCH=$(git branch --show-current)</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="create-the-aks-automatic-cluster">Create the AKS Automatic cluster<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#create-the-aks-automatic-cluster" class="hash-link" aria-label="Direct link to Create the AKS Automatic cluster" title="Direct link to Create the AKS Automatic cluster" translate="no">​</a></h2>
<p>First, create a resource group:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az group create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name "${RG}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --location "${LOCATION}"</span><br></div></code></pre></div></div>
<p>Now create the Automatic cluster:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aks create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group "${RG}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name "${CLUSTER}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --location "${LOCATION}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --sku automatic \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --no-ssh-key</span><br></div></code></pre></div></div>
<p>AKS Automatic uses Azure RBAC for Kubernetes authorization and disables local accounts. Assign your signed-in identity a scoped AKS RBAC role before using <code>kubectl</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">AKS_ID=$(az aks show \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group "${RG}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name "${CLUSTER}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --query id \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output tsv)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">SIGNED_IN_USER_ID=$(az ad signed-in-user show \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --query id \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output tsv)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az role assignment create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --assignee "${SIGNED_IN_USER_ID}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --role "Azure Kubernetes Service RBAC Cluster Admin" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --scope "${AKS_ID}"</span><br></div></code></pre></div></div>
<p>Once the cluster is ready and the role assignment is in place, get credentials using the exec credential flow:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aks get-credentials \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group "${RG}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name "${CLUSTER}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --format exec \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --overwrite-existing</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># check connectivity</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get nodes</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="install-the-arc-controller">Install the ARC controller<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#install-the-arc-controller" class="hash-link" aria-label="Direct link to Install the ARC controller" title="Direct link to Install the ARC controller" translate="no">​</a></h2>
<p>First up is the ARC controller. For AKS Automatic, we'll give the controller explicit resource requests and limits. Create a values file:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">cat &gt; arc-controller-values.yaml &lt;&lt;'EOF'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resources:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  requests:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    cpu: 100m</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    memory: 128Mi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  limits:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    cpu: 500m</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    memory: 512Mi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></div></code></pre></div></div>
<p>Now install the controller:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">helm upgrade --install arc \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set-controller \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace "${ARC_SYSTEMS_NAMESPACE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --create-namespace \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --wait \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --timeout 10m \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -f arc-controller-values.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl rollout status \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  deployment/arc-gha-rs-controller \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace "${ARC_SYSTEMS_NAMESPACE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --timeout 5m</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="store-the-github-token-in-kubernetes">Store the GitHub token in Kubernetes<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#store-the-github-token-in-kubernetes" class="hash-link" aria-label="Direct link to Store the GitHub token in Kubernetes" title="Direct link to Store the GitHub token in Kubernetes" translate="no">​</a></h2>
<p>Now let's create a namespace for the runner resources and store the GitHub token in a Kubernetes secret:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl create namespace "${ARC_RUNNERS_NAMESPACE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --dry-run=client \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output yaml | kubectl apply -f -</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">printf '%s' "${GITHUB_TOKEN}" | kubectl create secret generic github-pat \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace "${ARC_RUNNERS_NAMESPACE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --from-file=github_token=/dev/stdin</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>warning</div><div class="admonitionContent_BuS1"><p>For production, wire this into your normal secret-management process. Don't hard-code this token into Helm values or source control.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="install-the-runner-scale-set">Install the runner scale set<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#install-the-runner-scale-set" class="hash-link" aria-label="Direct link to Install the runner scale set" title="Direct link to Install the runner scale set" translate="no">​</a></h2>
<p>This is the most important part of the setup. The default runner scale set values are close, but AKS Automatic expects a few things to be explicit because Deployment Safeguards block common Kubernetes anti-patterns before they land in the cluster:</p>
<ol>
<li class="">Resource requests and limits for the listener pod.</li>
<li class="">Resource requests and limits for the runner pod.</li>
<li class="">An explicit, non-<code>latest</code> runner image tag.</li>
</ol>
<p>Create <code>arc-runner-set-values.yaml</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">cat &gt; arc-runner-set-values.yaml &lt;&lt;EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">githubConfigUrl: ${GITHUB_CONFIG_URL}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">githubConfigSecret: github-pat</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">minRunners: 0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">maxRunners: 3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">listenerTemplate:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  spec:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    containers:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      - name: listener</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        resources:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          requests:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            cpu: 100m</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            memory: 128Mi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          limits:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            cpu: 500m</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            memory: 512Mi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">template:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  spec:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    containers:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      - name: runner</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        image: ghcr.io/actions/actions-runner:2.336.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        command: ["/home/runner/run.sh"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        resources:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          requests:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            cpu: "2"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            memory: 4Gi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          limits:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            cpu: "2"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            memory: 4Gi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></div></code></pre></div></div>
<p>This example pins <code>ghcr.io/actions/actions-runner:2.336.0</code>, which was the current GitHub runner release when I validated the walkthrough. Runner versions do age out, so check <a href="https://github.com/actions/runner/releases" target="_blank" rel="noopener noreferrer" class="">the GitHub runner releases</a> and update the tag when needed.</p>
<p>Now install the runner scale set:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">helm upgrade --install "${RUNNER_SET_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace "${ARC_RUNNERS_NAMESPACE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --create-namespace \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --wait \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --timeout 10m \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -f arc-runner-set-values.yaml</span><br></div></code></pre></div></div>
<p>Check the install:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get pods --namespace "${ARC_SYSTEMS_NAMESPACE}" --output wide</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get autoscalingrunnersets,ephemeralrunnersets,ephemeralrunners \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace "${ARC_RUNNERS_NAMESPACE}"</span><br></div></code></pre></div></div>
<p>Before any jobs run, the controller and listener pods should be running and the <code>AutoscalingRunnerSet</code> should exist. You should not see runner pods yet. ARC creates those only when jobs queue.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="add-a-validation-workflow">Add a validation workflow<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#add-a-validation-workflow" class="hash-link" aria-label="Direct link to Add a validation workflow" title="Direct link to Add a validation workflow" translate="no">​</a></h2>
<p>Now add a simple workflow to the target GitHub repo. Create <code>.github/workflows/arc-automatic-validation.yml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ARC AKS Automatic validation</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">on</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">workflow_dispatch</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">jobs</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">validate</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">runs-on</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> arc</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">auto</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">runners</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">steps</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Print runner context</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">run</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          echo "ARC runner reached workflow execution"</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          echo "Runner name: $RUNNER_NAME"</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          uname -a</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          df -h</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Exercise container tooling</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">run</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          docker --version || true</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          echo "Validation complete"</span><br></div></code></pre></div></div>
<p>If you changed <code>RUNNER_SET_NAME</code>, update <code>runs-on</code> to match it. Then commit and push the workflow:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git add .github/workflows/arc-automatic-validation.yml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git commit -m "Add AKS Automatic ARC validation workflow"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">git push --set-upstream origin "${GITHUB_BRANCH}"</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="run-the-workflow">Run the workflow<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#run-the-workflow" class="hash-link" aria-label="Direct link to Run the workflow" title="Direct link to Run the workflow" translate="no">​</a></h2>
<p>If this is a new repository or workflow file, give GitHub a few seconds to index the workflow. Confirm that the workflow appears before dispatching it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">gh workflow list \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --repo "${GITHUB_OWNER}/${GITHUB_REPO}"</span><br></div></code></pre></div></div>
<p>Trigger the workflow:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">gh workflow run arc-automatic-validation.yml \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --repo "${GITHUB_OWNER}/${GITHUB_REPO}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --ref "${GITHUB_BRANCH}"</span><br></div></code></pre></div></div>
<p>Watch the run:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">RUN_ID=$(gh run list \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --repo "${GITHUB_OWNER}/${GITHUB_REPO}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --workflow arc-automatic-validation.yml \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --json databaseId \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --jq '.[0].databaseId')</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">gh run watch "${RUN_ID}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --repo "${GITHUB_OWNER}/${GITHUB_REPO}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --interval 10 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --exit-status</span><br></div></code></pre></div></div>
<p>In another terminal, watch ARC scale the runner set:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get autoscalingrunnersets,ephemeralrunnersets,ephemeralrunners \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace "${ARC_RUNNERS_NAMESPACE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --watch</span><br></div></code></pre></div></div>
<p>When everything is working, you should see:</p>
<ul>
<li class="">The GitHub Actions job move from <code>queued</code> to <code>in_progress</code> to <code>completed</code>.</li>
<li class="">An ephemeral runner object and pod appear in <code>arc-runners</code>.</li>
<li class="">The runner pod use your pinned <code>ghcr.io/actions/actions-runner</code> image.</li>
<li class="">The workflow log print <code>ARC runner reached workflow execution</code>.</li>
</ul>
<p>In the validation run, you'll see the job ran on a pod named <code>arc-auto-runners-&lt;id&gt;-runner-&lt;id&gt;</code>, reported runner version <code>2.336.0</code>, and completed successfully.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="troubleshooting-the-aks-automatic-specific-bits">Troubleshooting the AKS Automatic-specific bits<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#troubleshooting-the-aks-automatic-specific-bits" class="hash-link" aria-label="Direct link to Troubleshooting the AKS Automatic-specific bits" title="Direct link to Troubleshooting the AKS Automatic-specific bits" translate="no">​</a></h2>
<p>Here are the issues I hit while validating this setup and what they mean.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="missing-resource-requests">Missing resource requests<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#missing-resource-requests" class="hash-link" aria-label="Direct link to Missing resource requests" title="Direct link to Missing resource requests" translate="no">​</a></h3>
<p>If the listener or controller is rejected with an error like this:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">container &lt;listener&gt; has no resource requests</span><br></div></code></pre></div></div>
<p>or:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">container &lt;manager&gt; has no resource requests</span><br></div></code></pre></div></div>
<p>AKS Automatic Deployment Safeguards blocks these pods because resource requests are a Kubernetes scheduling best practice. Requests give the scheduler enough information to place the controller, listener, and runner pods reliably as ARC scales jobs up and down.</p>
<p>Make sure the controller chart has <code>resources</code> set and the runner scale set chart has <code>listenerTemplate.spec.containers[].resources</code> set.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-latest-runner-image-is-blocked">The <code>latest</code> runner image is blocked<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#the-latest-runner-image-is-blocked" class="hash-link" aria-label="Direct link to the-latest-runner-image-is-blocked" title="Direct link to the-latest-runner-image-is-blocked" translate="no">​</a></h3>
<p>AKS Automatic Deployment Safeguards can reject the default runner image because it uses a floating <code>latest</code> tag:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Avoiding the latest tag for container: runner</span><br></div></code></pre></div></div>
<p>Pin the runner image to an explicit release:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">template</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">containers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> runner</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ghcr.io/actions/actions</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">runner</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">2.336.0</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-pinned-runner-version-is-too-old">The pinned runner version is too old<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#the-pinned-runner-version-is-too-old" class="hash-link" aria-label="Direct link to The pinned runner version is too old" title="Direct link to The pinned runner version is too old" translate="no">​</a></h3>
<p>Pinning is required, but the pinned version still needs to be current. If the runner logs show:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Runner version vX.Y.Z is deprecated and cannot receive messages.</span><br></div></code></pre></div></div>
<p>update the image tag to a current runner release and upgrade the Helm release:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">helm upgrade "${RUNNER_SET_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  oci://ghcr.io/actions/actions-runner-controller-charts/gha-runner-scale-set \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace "${ARC_RUNNERS_NAMESPACE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --wait \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --timeout 10m \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -f arc-runner-set-values.yaml</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-job-stays-queued">The job stays queued<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#the-job-stays-queued" class="hash-link" aria-label="Direct link to The job stays queued" title="Direct link to The job stays queued" translate="no">​</a></h3>
<p>Start by checking the controller, listener, and runner logs:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl logs \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace "${ARC_SYSTEMS_NAMESPACE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  deployment/arc-gha-rs-controller \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --tail 200</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl logs \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace "${ARC_SYSTEMS_NAMESPACE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -l app.kubernetes.io/component=runner-scale-set-listener \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --tail 200</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl logs \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace "${ARC_RUNNERS_NAMESPACE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -l app.kubernetes.io/component=runner \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --tail 200</span><br></div></code></pre></div></div>
<p>Also confirm that the workflow <code>runs-on</code> value exactly matches the runner scale set name.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cleanup">Cleanup<a href="https://blog.aks.azure.com/2026/09/17/github-actions-runner-controller-aks-automatic#cleanup" class="hash-link" aria-label="Direct link to Cleanup" title="Direct link to Cleanup" translate="no">​</a></h2>
<p>Remove the Helm releases:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">helm uninstall "${RUNNER_SET_NAME}" --namespace "${ARC_RUNNERS_NAMESPACE}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">helm uninstall arc --namespace "${ARC_SYSTEMS_NAMESPACE}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl delete namespace "${ARC_RUNNERS_NAMESPACE}" "${ARC_SYSTEMS_NAMESPACE}"</span><br></div></code></pre></div></div>
<p>Delete the resource group:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az group delete \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name "${RG}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --yes \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --no-wait</span><br></div></code></pre></div></div>
<p>If any offline repository runners remain in GitHub after failed experiments, remove them from the repository's Actions runner settings.</p>
<p>That's it. Once the Automatic-friendly values are in place, ARC can scale an ephemeral runner, pick up a queued GitHub Actions job, and run it successfully on AKS Automatic.</p>]]></content:encoded>
            <category>AKS Automatic</category>
            <category>GitHub Actions</category>
            <category>GitHub ARC</category>
            <category>DevOps</category>
        </item>
        <item>
            <title><![CDATA[AKS Configurable Scheduler Profiles]]></title>
            <link>https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles</link>
            <guid>https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles</guid>
            <pubDate>Tue, 08 Sep 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Improve GPU and CPU utilization, align pod placement to critical workloads, and reduce node costs at scale with Configurable Scheduler Profiles on AKS.]]></description>
            <content:encoded><![CDATA[<p>Your Kubernetes clusters are likely running well below capacity and underutilized resources materially contribute to increased infrastructure cost. In 2025, Datadog found most Kubernetes containers use less than <a href="https://www.datadoghq.com/state-of-containers-and-serverless/" target="_blank" rel="noopener noreferrer" class="">25% of their requested CPU</a>, and in 2023, Weights and Biases found that nearly a third of GPU users <a href="https://wandb.ai/wandb_fc/articles/reports/Monitor-Improve-GPU-Usage-for-Model-Training--Vmlldzo1NDQzNjM3" target="_blank" rel="noopener noreferrer" class="">averaged less than 15% utilization</a>. Although node utilization is influenced by multiple factors, the kube-scheduler is a key Kubernetes control plane component that plays a critical role in how efficiently workloads are placed across nodes.</p>
<p><a href="https://learn.microsoft.com/azure/aks/concepts-scheduler-configuration" target="_blank" rel="noopener noreferrer" class="">Configurable Scheduler Profiles</a> on Azure Kubernetes Service (AKS) let you configure your own scheduling logic: enable specific plugins, adjust plugin priorities, and tune parameter weights. <strong>The result: higher node density, better CPU and GPU utilization, and lower infrastructure costs.</strong></p>
<p>You'll learn how the default Kubernetes scheduler places pods, where the defaults fall short, and how to increase node utilization with Configurable Scheduler Profiles on AKS.</p>
<ol>
<li class=""><a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#how-does-the-default-kubernetes-scheduler-place-pods" class="">How does kube-scheduler work?</a></li>
<li class=""><a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#increase-node-utilization-and-operator-control" class="">Use Configurable Scheduler Profiles to increase node utilization and operator control</a></li>
<li class=""><a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#increase-aks-cpu-utilization" class="">Increase AKS cluster CPU utilization up to 85% with Configurable Scheduler</a></li>
<li class=""><a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#increase-aks-gpu-utilization" class="">Increase AKS cluster GPU or CPU utilization while balancing memory with Configurable Scheduler</a></li>
<li class=""><a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#faq" class="">FAQ: How do Configurable Scheduler Profiles interact with autoscalers?</a></li>
</ol>
<p><img decoding="async" loading="lazy" alt="Diagram showing increased node utilization with the node bin packing scheduler profile" src="https://blog.aks.azure.com/assets/images/hero-image-b908a9e86bdcf93e82adfc9f5b502440.png" width="853" height="425" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-does-the-default-kubernetes-scheduler-place-pods">How does the default Kubernetes scheduler place pods?<a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#how-does-the-default-kubernetes-scheduler-place-pods" class="hash-link" aria-label="Direct link to How does the default Kubernetes scheduler place pods?" title="Direct link to How does the default Kubernetes scheduler place pods?" translate="no">​</a></h2>
<p>The Kubernetes scheduler operates in two cycles: a synchronous scheduling cycle and an asynchronous binding cycle. The scheduling cycle has two sub-phases, filtering and scoring, and only manages one pod at a time.</p>
<ol>
<li class=""><strong>Filtering</strong> phase removes nodes that do not satisfy the pod's scheduling requirements, based on hard and soft constraints.</li>
<li class=""><strong>Scoring</strong> phase assigns a score for the remaining nodes, based on scheduler plugins and their configured weights, with the highest-scoring node selected as the scheduling target.</li>
</ol>
<p>After a node is selected, the scheduler enters the binding cycle, which can process multiple pods concurrently. During this phase, the scheduler attempts to bind the pod to the chosen node. If the bind operation fails, the pod is typically re-queued and retried in a subsequent scheduling cycle rather than immediately falling back to the next-highest-scoring node. When filtering and scoring nodes, the default scheduler considers several hard and soft constraints with predefined weights, including (but not limited to):</p>
<ol>
<li class="">Resource requirements (CPU, memory)</li>
<li class="">Node affinity/anti-affinity</li>
<li class="">Pod affinity/anti-affinity</li>
<li class="">Taints and tolerations</li>
<li class="">TopologySpreadConstraints</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Diagram of the kube-scheduler workflow showing pods entering the scheduling cycle and binding cycle to assign a pod on a node" src="https://blog.aks.azure.com/assets/images/kube-scheduler-scheduling-phases-diagram-2941a7675bb6d96e75ba145ffb7fef7f.png" width="1637" height="760" class="img_ev3q"></p>
<p>For more on how the Kubernetes scheduler works, see the technical blog post from SIG Scheduling contributor and AKS upstream engineer Heba Elayoty, <a href="https://helayoty.org/blog/inside-kube-scheduler-the-plugin" target="_blank" rel="noopener noreferrer" class="">Deep Dive into the Kubernetes Scheduler Framework</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="limitations-of-the-default-kubernetes-scheduler">Limitations of the default Kubernetes scheduler<a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#limitations-of-the-default-kubernetes-scheduler" class="hash-link" aria-label="Direct link to Limitations of the default Kubernetes scheduler" title="Direct link to Limitations of the default Kubernetes scheduler" translate="no">​</a></h3>
<p>The default scheduler is primarily designed for general-purpose workloads. It prioritizes nodes with the most available resources using the <em>LeastAllocated</em> scoring strategy. This strategy spreads pods across nodes, even when they could safely be packed more densely. While this works well for many services, the default scheduling criteria, and their fixed priority order, are not suitable for workloads that demand optimizing GPU and CPU utilization. In these scenarios, spreading pods across nodes can lead to fragmented resources, underutilized GPUs, and increased infrastructure cost.</p>
<p>Today, the default scheduler on AKS lacks the flexibility for users to change which criteria should be prioritized, or ignored, in the scheduling cycle because the default scheduler is not accessible to users. This rigidity often forces users to either accept suboptimal placement or manage a separate custom scheduler, both of which increase operational complexity. Starting with Kubernetes v1.33, AKS introduces <a href="https://learn.microsoft.com/azure/aks/concepts-scheduler-configuration" target="_blank" rel="noopener noreferrer" class="">Configurable Scheduler Profiles</a> - an AKS-managed CRD - that exposes the upstream scheduling framework without maintaining a separate scheduler. Now, users can adjust the <code>NodeResourcesFit</code> plugin from the default configuration to favor nodes with higher utilization to achieve more efficient bin‑packing and reduce infrastructure cost.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="configurable-scheduler-profiles-on-aks">Configurable Scheduler Profiles on AKS<a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#configurable-scheduler-profiles-on-aks" class="hash-link" aria-label="Direct link to Configurable Scheduler Profiles on AKS" title="Direct link to Configurable Scheduler Profiles on AKS" translate="no">​</a></h2>
<p><a href="https://learn.microsoft.com/azure/aks/concepts-scheduler-configuration" target="_blank" rel="noopener noreferrer" class="">Configurable Scheduler Profiles on AKS</a> allow users to change the default scheduler behavior using the extensibility of the <a href="https://kubernetes.io/docs/concepts/scheduling-eviction/scheduling-framework/#interfaces" target="_blank" rel="noopener noreferrer" class="">scheduling framework</a>, without the operational overhead of adopting a second scheduler or defining a custom scheduler. You express the intent declaratively, while AKS safely applies and manages the resulting scheduler behavior using a Custom Resource Definition (CRD).</p>
<ol>
<li class="">AKS begins with the default scheduling configuration.</li>
<li class="">You declare your desired scheduling behavior using a Configurable Scheduler Profile applied as a CRD.</li>
<li class="">The controller overlays the user-defined configuration on top of the base scheduler configuration.</li>
<li class="">The controller produces and maintains a reconciled scheduler ConfigMap representing the combined configuration.</li>
<li class="">The kube-scheduler deployment consumes the controller-managed ConfigMap and applies the updated scheduling behavior.</li>
<li class="">If the scheduler becomes unhealthy, the controller automatically rolls back to the last known good configuration.</li>
</ol>
<p><img decoding="async" loading="lazy" alt="Architecture diagram showing how Configurable Scheduler Profiles use a CRD and controller to reconcile user-defined profiles with the kube-scheduler deployment" src="https://blog.aks.azure.com/assets/images/config-scheduler-profiles-767dfd6e72a3398483e3dbdd1b9e4ade.png" width="1162" height="472" class="img_ev3q"></p>
<p>A profile is a set of one or more in-tree scheduling plugins and configurations that dictate how to schedule a pod. AKS supports 18 in-tree Kubernetes <a href="https://learn.microsoft.com/azure/aks/concepts-scheduler-configuration#supported-in-tree-scheduling-plugins" target="_blank" rel="noopener noreferrer" class="">scheduling plugins</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="increase-node-utilization-and-operator-control">Increase node utilization and operator control<a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#increase-node-utilization-and-operator-control" class="hash-link" aria-label="Direct link to Increase node utilization and operator control" title="Direct link to Increase node utilization and operator control" translate="no">​</a></h2>
<p>In this simple scale-out scenario, manually increasing identical CPU-bound replicas from 8 to 30, the default scheduler distributes pods evenly across nodes.</p>
<p>Configurable Scheduler Profiles configured for bin-packing show a visible consolidation pattern that differs from the default scheduler, and improves capacity for new pods. This shift occurs without changing the workload, node size, or autoscaling behavior - only the scheduler’s scoring logic.</p>
<p><img decoding="async" loading="lazy" alt="Table showing increased node utilization with the node bin packing scheduler profiles versus the pod distribution using the default scheduler" src="https://blog.aks.azure.com/assets/images/default-config-scheduler-comparison-5f7a1fc67f56dd6c4d4c86e3ccbe4bcf.png" width="1460" height="372" class="img_ev3q"></p>
<p>While this experiment uses intentionally simple, CPU-bound containers to isolate scheduling behavior, the placement patterns observed here can be applied to GPU-bound workloads where capacity constraints elevate the need for utilization improvements. In this constrained GPU scenario the bin‑packing scheduler plays a critical role in maximizing usable capacity of 4 GPUs spread across 2 nodes. By consolidating single‑GPU workloads onto the same node, the scheduler avoids idle GPUs and enables all four GPUs to be actively used before new nodes are required.</p>
<p><img decoding="async" loading="lazy" alt="Table showing increased node utilization with the node bin packing scheduler profiles versus the pod distribution using the default scheduler for GPUs across multiple scheduling iterations" src="https://blog.aks.azure.com/assets/images/default-config-scheduler-comparison-gpu-f3c034d478118a1d94cbec39d7672623.png" width="1530" height="350" class="img_ev3q"></p>
<p>This change in distribution shape enables downstream efficiencies: improved control for platform engineers, efficient resource usage, and cost optimization that are difficult to achieve when pods are evenly spread. <strong>Configurable Scheduler has many use cases and each profile expresses a distinct scheduling intent. The next two sections detail how the scoring strategies, MostAllocated and RequestedToCapacityRatio, are configured to achieve increased utilization outcomes.</strong></p>
<table><thead><tr><th>Scheduler</th><th>Scoring strategy</th><th>Scheduling intent</th><th>Operator benefits</th></tr></thead><tbody><tr><td>Default scheduler</td><td>NodeResourcesFit: LeastAllocated</td><td>Balance and hotspot reduction</td><td>No tuning</td></tr><tr><td>Configurable Scheduler Profile</td><td>NodeResourcesFit: MostAllocated</td><td>Maximize consolidation / bin‑packing</td><td>Maximum node utilization, highest cost reduction potential</td></tr><tr><td>Configurable Scheduler Profile</td><td>NodeResourcesFit: RequestedToCapacityRatio</td><td>Targeted utilization with headroom</td><td>✅ <strong>(Recommended strategy)</strong> Increased utilization with stronger control over consolidation and burst headroom than <code>MostAllocated</code></td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="increase-aks-cpu-utilization">Increase AKS CPU utilization<a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#increase-aks-cpu-utilization" class="hash-link" aria-label="Direct link to Increase AKS CPU utilization" title="Direct link to Increase AKS CPU utilization" translate="no">​</a></h3>
<p>Using <code>RequestedToCapacityRatio</code>, this bin packing profile is configured to favor nodes within a utilization band of 50—85%, avoiding empty nodes, and severely deprioritizing nearly full nodes at 90% utilization or more, to limit oversaturated nodes. <a href="https://learn.microsoft.com/azure/aks/configure-node-binpack-scheduler?tabs=new-cluster#configure-node-bin-packing-with-requestedtocapacity-plugin" target="_blank" rel="noopener noreferrer" class="">Configure node bin-packing</a> using the <code>RequestedToCapacityRatio</code> strategy to improve utilization and reduce infrastructure costs.</p>
<ol>
<li class=""><code>RequestedToCapacityRatio</code> scores nodes based on the ratio of requested resources to total node capacity after the pod is <em>hypothetically</em> placed. This strategy enables more fine-grained bin‑packing by allowing operators to define an ideal utilization curve for their nodes rather than simply preferring the most or least utilized nodes.</li>
<li class=""><code>PodTopologySpread</code> is disabled in this profile because bin-packing and zone-spreading are opposing goals and the scheduling logic may prioritize pod spreading. If you need both high utilization <em>and</em> zone resilience, define a new profile to achieve both goals.</li>
</ol>
<p>By shaping the scoring curve to target a range of 50—85% CPU utilization, users can increase pod density on provisioned nodes while preserving headroom for bursts, background processes, and system components in CPU-bound workloads.</p>
<p><strong>Given this level of configuration detail, <code>RequestedToCapacityRatio</code> is the recommended scoring strategy for node bin‑packing on AKS for production clusters.</strong></p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>Scoring strategy can also be used for GPU by changing the target resource. Adjust resources, resource weights, utilization thresholds, and plugin parameters to match your VM SKUs, workload patterns, and cluster topology.</p></div></div>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> aks.azure.com/v1alpha1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> SchedulerConfiguration</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> upstream</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">rawConfig</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">    apiVersion: kubescheduler.config.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">    kind: KubeSchedulerConfiguration</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">    profiles:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">      - schedulerName: cpu-binpacking-scheduler</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">        plugins:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          multiPoint:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">            enabled:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              - name: NodeResourcesFit</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">            disabled:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              - name: PodTopologySpread</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">        pluginConfig:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          - name: NodeResourcesFit</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">            args:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              apiVersion: kubescheduler.config.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              kind: NodeResourcesFitArgs</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              scoringStrategy:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                type: RequestedToCapacityRatio</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                resources:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                  - name: cpu</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                    weight: 8</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                  - name: memory</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                    weight: 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                requestedToCapacityRatio:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                  shape:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                    - utilization: 0</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                      score: 0</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                    - utilization: 30</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                      score: 8</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                    - utilization: 50</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                      score: 10</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                    - utilization: 85</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                      score: 10</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                    - utilization: 90</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                      score: 3</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                    - utilization: 100</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                      score: 0</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="increase-aks-gpu-utilization">Increase AKS GPU utilization<a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#increase-aks-gpu-utilization" class="hash-link" aria-label="Direct link to Increase AKS GPU utilization" title="Direct link to Increase AKS GPU utilization" translate="no">​</a></h3>
<p>When <code>MostAllocated</code> and <code>NodeResourcesBalancedAllocation</code> are combined, the scheduler favors GPU‑bound nodes with balanced CPU and memory usage over nodes with large amounts of unused memory or fragmented resources. This approach reduces fragmented GPU capacity and results in fewer underutilized secondary resources. <a href="https://learn.microsoft.com/azure/aks/configure-node-binpack-scheduler?tabs=new-cluster#configure-node-bin-packing-with-mostallocated-plugin" target="_blank" rel="noopener noreferrer" class="">Configure node bin-packing</a> using the MostAllocated strategy to improve utilization and reduce infrastructure costs.</p>
<ol>
<li class=""><code>MostAllocated</code> scores nodes based on its current resource utilization, favoring nodes that are already heavily used for the specified resources.</li>
<li class=""><code>RequestedToCapacityRatio</code> lets you define a scoring curve so you can explicitly control preferred utilization ranges and scores nodes based on resource requests relative to the remaining node capacity. This makes <code>MostAllocated</code> more aggressive for consolidation but gives you less explicit control over headroom.</li>
<li class=""><code>PodTopologySpread</code> is disabled in this profile because bin-packing and zone-spreading are opposing goals. Enabling both can result in spreading over consolidation, weakening the intended packing behavior.</li>
</ol>
<p><code>NodeResourcesBalancedAllocation</code> complements <code>MostAllocated</code> because it prefers nodes whose CPU and memory utilization stay proportionally balanced, helping reduce bottlenecks caused by asymmetric resource pressure.</p>
<p><strong>This scheduler configuration maximizes GPU utilization by consolidating smaller jobs onto fewer nodes, reducing idle accelerator capacity while maintaining reasonable CPU and memory balance.</strong></p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>Adjust resources, resource weights, utilization thresholds, and plugin parameters to match your VM SKUs, workload patterns, and cluster topology.</p></div></div>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> aks.azure.com/v1alpha1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> SchedulerConfiguration</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> upstream</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">rawConfig</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">    apiVersion: kubescheduler.config.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">    kind: KubeSchedulerConfiguration</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">    profiles:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">      - schedulerName: gpu-node-binpacking-scheduler</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">        plugins:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          multiPoint:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">            enabled:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              - name: NodeResourcesFit</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              - name: NodeResourcesBalancedAllocation</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">            disabled:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              - name: PodTopologySpread</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">        pluginConfig:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          - name: NodeResourcesFit</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">            args:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              apiVersion: kubescheduler.config.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              kind: NodeResourcesFitArgs</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              scoringStrategy:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                type: MostAllocated</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                resources:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                  - name: nvidia.com/gpu</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                    weight: 8</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                  - name: cpu</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                    weight: 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          - name: NodeResourcesBalancedAllocation</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">            args:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              apiVersion: kubescheduler.config.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              kind: NodeResourcesBalancedAllocationArgs</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              resources:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                - name: cpu</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                  weight: 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                - name: memory</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">                  weight: 1</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="faq">FAQ<a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#faq" class="hash-link" aria-label="Direct link to FAQ" title="Direct link to FAQ" translate="no">​</a></h3>
<ol>
<li class="">
<p>Which bin-packing strategy does AKS recommend to increase node utilization?</p>
<p>AKS recommends using the scoring strategy <code>RequestedToCapacityRatio</code> because it provides a more granular scoring approach allowing users to define an ideal utilization curve for their respective nodes. For example, this bin packing strategy allows users to configure a target utilization of 85%.</p>
</li>
<li class="">
<p>How do Configurable Scheduler Profiles interact with autoscalers such as Node Auto Provisioning (NAP), Cluster Autoscaler (CA), and Vertical Pod Autoscaler (VPA)?</p>
<p>These components are complementary to each other. Configurable Scheduler Profiles influence how pods are placed on nodes, while autoscalers make scaling decisions based on resource utilization and pending pods. Look out for an upcoming blog that details how scheduling constraints affect Node Auto Provisioning.</p>
<ul>
<li class=""><strong>Node Auto Provisioning (NAP)</strong> is triggered when pods are unschedulable. If a suitable node already exists, that pod will be scheduled with the defined Configurable Scheduler Profile. If no suitable node exists, NAP provisions new capacity and schedules the pod.</li>
<li class=""><strong>Cluster Autoscaler (CA)</strong> manages node scale-up and scale-down. On scale-up, CA is triggered when there aren't any suitable nodes available for the pending pod. Using Configurable Scheduler Profiles ensures nodes are only scaled when provisioned resources are no longer suitable. On scale-down, CA is triggered when nodes fall below utilization thresholds, the default is 50%. As active nodes are packed more efficiently, underutilized nodes become easier candidates for removal.</li>
<li class=""><strong>Vertical Pod Autoscaler (VPA)</strong> optimizes resource utilization patterns in pods. As pods are recreated with updated CPU and memory requests, they are scheduled using the configured scheduler profile, allowing placement decisions to reflect the new resource requirements.</li>
</ul>
</li>
<li class="">
<p>What if a resource, like <code>memory</code>, is omitted in the <code>scoringStrategy</code>?</p>
<p>If a resource is omitted in the <code>scoringStrategy</code>, then that resource will not be considered in the filter or scoring cycles of the defined Configurable Scheduler Profile. If that resource should be considered, but with a reduced influence on the final score, it can be included with reduced weight.</p>
</li>
<li class="">
<p>Can multiple Configurable Scheduler Profiles be used for different workloads on the same cluster?</p>
<p>Yes, multiple scheduling profiles can coexist in a single cluster. This allows different placement strategies (for example, cost‑optimized vs. latency‑sensitive workloads) to run side‑by‑side. Visit the documentation for a <a href="https://learn.microsoft.com/azure/aks/configure-aks-scheduler?tabs=new-cluster#configure-multiple-scheduler-profiles" target="_blank" rel="noopener noreferrer" class="">multiple scheduler profiles example</a>.</p>
<ul>
<li class="">Multiple profiles can be defined centrally in a single scheduler configuration.</li>
<li class="">Individual workloads select a profile via <code>schedulerName</code> in the pod spec.</li>
</ul>
</li>
<li class="">
<p>How do I monitor whether my scheduler profile is improving utilization?</p>
<p>Monitor these signals to confirm that the scheduler is behaving correctly. Over time, you should see higher average node utilization, reduced variance between nodes, and fewer lightly utilized nodes.</p>
<ul>
<li class="">Track node‑level utilization metrics, including CPU and memory utilization per node and distribution of pods across nodes, using Azure Monitor for containers, the AKS <a href="https://github.com/Azure/aks-node-viewer" target="_blank" rel="noopener noreferrer" class="">node viewer tool</a>, or <code>kubectl top nodes</code> for quick validation.</li>
<li class="">Review autoscaler outcomes, looking for fewer scale‑ups during normal load and more decisive scale‑downs after demand drops.</li>
<li class="">Measure cost metrics, such as reduced idle costs when you use the <a href="https://learn.microsoft.com/azure/aks/cost-analysis" target="_blank" rel="noopener noreferrer" class="">Cost Analysis add-on</a>.</li>
</ul>
</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="next-steps-optimize-resources-with-configurable-scheduler-profiles-on-aks">Next steps: Optimize resources with Configurable Scheduler Profiles on AKS<a href="https://blog.aks.azure.com/2026/09/08/aks-config-scheduler-profiles#next-steps-optimize-resources-with-configurable-scheduler-profiles-on-aks" class="hash-link" aria-label="Direct link to Next steps: Optimize resources with Configurable Scheduler Profiles on AKS" title="Direct link to Next steps: Optimize resources with Configurable Scheduler Profiles on AKS" translate="no">​</a></h2>
<p>Configurable Scheduler Profiles give you direct control over pod placement. With these scheduling plugins, your workloads make full use of available GPU capacity, reduce idle costs, and avoid costly overprovisioning.</p>
<ul>
<li class="">For additional guidance and best practices, see <a href="https://learn.microsoft.com/azure/aks/operator-best-practices-advanced-scheduler" target="_blank" rel="noopener noreferrer" class="">kube-scheduler best practices</a></li>
<li class="">Increase node utilization using <a href="https://learn.microsoft.com/azure/aks/configure-node-binpack-scheduler?tabs=new-cluster" target="_blank" rel="noopener noreferrer" class="">Configurable Scheduler Profiles</a></li>
<li class="">To schedule and queue batch workloads, <a href="https://learn.microsoft.com/azure/aks/kueue-overview" target="_blank" rel="noopener noreferrer" class="">install and configure Kueue on AKS</a>.</li>
</ul>]]></content:encoded>
            <category>AI</category>
            <category>Performance</category>
            <category>Scheduler</category>
            <category>Best Practices</category>
            <category>Cost</category>
        </item>
        <item>
            <title><![CDATA[Open-sourcing TauGrid: cloud-native AI infrastructure for GPU workloads on Kubernetes]]></title>
            <link>https://blog.aks.azure.com/2026/08/28/taugrid-open-source</link>
            <guid>https://blog.aks.azure.com/2026/08/28/taugrid-open-source</guid>
            <pubDate>Fri, 28 Aug 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[TauGrid combines the tau CLI, Kueue, KubeRay, GPU health monitoring, and observability into one Kubernetes-native stack for AI workloads.]]></description>
            <content:encoded><![CDATA[<p>Running AI workloads on Kubernetes often requires platform teams to assemble and maintain multiple open-source projects, custom scripts, and operational tools. This includes the glue between those components: submission scripts, queue wrappers, health checks, and result retrieval. TauGrid brings these pieces together into a single platform, so platform teams operate one stack instead of many, and researchers submit workloads without learning Kubernetes.</p>
<p>We are delighted to announce the open-source release of <a href="https://github.com/Azure/taugrid" target="_blank" rel="noopener noreferrer" class="">TauGrid</a>. TauGrid combines the <code>tau</code> CLI, <a href="https://kueue.sigs.k8s.io/" target="_blank" rel="noopener noreferrer" class="">Kueue</a> queueing, <a href="https://ray-project.github.io/kuberay/" target="_blank" rel="noopener noreferrer" class="">KubeRay</a> orchestration, GPU-node health monitoring, and observability in a single Kubernetes-native stack. It supports workflows spanning data preparation, distributed training, fine-tuning, and inference.</p>
<p>Instead of building and maintaining these components and the integration between them separately, TauGrid provides a single Helm install with clear ownership boundaries. Platform teams get a unified way to operate the stack, while researchers get one CLI to submit workloads, observe their progress, recover from failures, and retrieve results without interacting with Kubernetes directly.</p>
<p>The project is MIT-licensed and available at <a href="https://github.com/Azure/taugrid" target="_blank" rel="noopener noreferrer" class="">github.com/Azure/taugrid</a>, with container images and Helm charts published to <a href="https://mcr.microsoft.com/" target="_blank" rel="noopener noreferrer" class="">Microsoft Container Registry</a>. Full documentation is available at <a href="https://azure.github.io/taugrid" target="_blank" rel="noopener noreferrer" class="">azure.github.io/taugrid</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-is-taugrid">What is TauGrid?<a href="https://blog.aks.azure.com/2026/08/28/taugrid-open-source#what-is-taugrid" class="hash-link" aria-label="Direct link to What is TauGrid?" title="Direct link to What is TauGrid?" translate="no">​</a></h2>
<p>TauGrid is an open-source, self-hosted platform for running AI workloads on Kubernetes. It gives teams the key benefits of a managed AI platform (job submission, scalable distributed computing, shared GPU resources, experiment tracking, and operational controls) while running on infrastructure they manage in their own cloud or data center.</p>
<p>Researchers work from a repository and the Tau CLI. Platform teams provide governed workspaces, queues, compute profiles, storage, identity, and observability. TauGrid connects those two experiences across training, fine-tuning, batch inference, and serving. The result is reduced operational complexity for platform teams, faster onboarding for researchers, better GPU utilization through shared queueing, and consistent experiment reproducibility through evidence records.</p>
<p><img decoding="async" loading="lazy" alt="TauGrid architecture: from researcher intent through the TauGrid workflow layer to Kubernetes execution and evidence" src="https://blog.aks.azure.com/assets/images/architecture-cec75f0fe2ece0eb828f4f520993a4e4.png" width="1800" height="484" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-works">How it works<a href="https://blog.aks.azure.com/2026/08/28/taugrid-open-source#how-it-works" class="hash-link" aria-label="Direct link to How it works" title="Direct link to How it works" translate="no">​</a></h2>
<p>TauGrid uses a <code>tau.yaml</code> configuration file to describe the workload. Here is a GPU training example that runs a PyTorch workload on a single A100:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">schema_version</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> aks</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">gpu</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">quickstart</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">run</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">entrypoint</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> train.py</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">workload_kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> rayjob</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">compute</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">gpus</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">workers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">cpus</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">16</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">memory</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 64Gi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">runtime</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> mcr.microsoft.com/aks/ai</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">runtime/ray</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">py3.12</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">ray2.56.0</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">cuda13.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">pip</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> torch</span><span class="token punctuation" style="color:#393A34">&gt;</span><span class="token plain">=2.4.0</span><br></div></code></pre></div></div>
<p>Once submitted with <code>tau run</code>, TauGrid resolves platform policy, renders a KubeRay RayJob, and submits it through Kueue. Status, logs, checkpoints, and experiment evidence are tracked throughout the run. Evidence records capture workload metadata, configuration, logs, metrics, checkpoints, and execution history, helping teams reproduce results, troubleshoot failures, and maintain an audit trail.</p>
<p>The same stack covers the full GPU workload lifecycle. This animation shows a workload moving from data preparation through distributed training, fine-tuning, and into a ready inference endpoint, all with one continuous evidence record:</p>
<p><img decoding="async" loading="lazy" alt="A single workload moves through data preparation, distributed training, fine-tuning, and a ready inference endpoint, with one continuous evidence record" src="https://blog.aks.azure.com/assets/images/taugrid-workload-lifecycle-bad269224e92be0e38915b5776d42b9c.gif" width="960" height="540" class="img_ev3q"></p>
<p>Here is what TauGrid handles at each stage:</p>
<table><thead><tr><th>Stage</th><th>What TauGrid does</th></tr></thead><tbody><tr><td>Submission</td><td>Validates configuration and renders a Kubernetes Job or KubeRay RayJob</td></tr><tr><td>Queueing</td><td>Uses Kueue to govern quota admission and priority</td></tr><tr><td>Execution</td><td>Launches and manages distributed Ray workloads on Kubernetes</td></tr><tr><td>Monitoring</td><td>Tracks status, logs, and GPU health metrics throughout the run</td></tr><tr><td>Recovery</td><td>Supports retry, resume from checkpoints, and failure diagnosis</td></tr><tr><td>Evidence</td><td>Preserves workload history, metrics, and artifacts for reproducibility</td></tr></tbody></table>
<p>When multiple teams share the same cluster, their workloads enter a shared Kueue ClusterQueue. Kueue admits each job based on quota and priority, and Kubernetes places it on healthy GPUs.</p>
<p><img decoding="async" loading="lazy" alt="Jobs from three workspaces share a Kueue ClusterQueue, the front job receives GPU quota and Kubernetes places it on two healthy GPUs" src="https://blog.aks.azure.com/assets/images/taugrid-control-room-83f31b044f344c8653642c467ec9a07c.gif" width="960" height="540" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started">Getting started<a href="https://blog.aks.azure.com/2026/08/28/taugrid-open-source#getting-started" class="hash-link" aria-label="Direct link to Getting started" title="Direct link to Getting started" translate="no">​</a></h2>
<p>You need a Kubernetes 1.30+ cluster, <code>kubectl</code>, Helm 3+, and Git. Install the Tau CLI:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -fsSL https://github.com/Azure/taugrid/releases/latest/download/install.sh | sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export PATH="$HOME/.local/bin:$PATH"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">tau version --short</span><br></div></code></pre></div></div>
<p>Install TauGrid onto your cluster:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">tau cluster install</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">tau cluster validate installation</span><br></div></code></pre></div></div>
<p>Create a workspace and wait for it to be ready:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">tau workspace create "taugrid-default" --apply</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl wait \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --for=jsonpath='{.status.phase}'=Ready \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  workspaces.tau.azure.com/taugrid-default \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --namespace tau-system \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --timeout=5m</span><br></div></code></pre></div></div>
<p>Submit your first workload:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">git clone https://github.com/Azure/taugrid.git</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cd taugrid</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">tau run --config examples/cpu-multi-interest-ray/tau.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">tau run status cpu-multi-interest-ray --watch</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">tau run logs cpu-multi-interest-ray -f</span><br></div></code></pre></div></div>
<p>This example runs a CPU workload and does not require GPU nodes. For the full step-by-step guide including cluster preparation, workspace configuration, GPU workloads, and handoff to researchers, see the <a href="https://azure.github.io/taugrid/docs/platform-admin-guide/installation-guides/kubernetes/" target="_blank" rel="noopener noreferrer" class="">Getting Started on Kubernetes</a> documentation.</p>
<p>The repository also includes these runnable examples:</p>
<table><thead><tr><th>Example</th><th>What it demonstrates</th></tr></thead><tbody><tr><td><a href="https://azure.github.io/taugrid/docs/examples/cpu-queueing/" target="_blank" rel="noopener noreferrer" class="">CPU queueing</a></td><td>Kueue admission, pending states, and borrowing between teams (no GPU required)</td></tr><tr><td><a href="https://azure.github.io/taugrid/docs/examples/gpu-ray-tune/" target="_blank" rel="noopener noreferrer" class="">GPU Ray Tune</a></td><td>Hyperparameter search on a single GPU with deterministic validation</td></tr><tr><td><a href="https://azure.github.io/taugrid/docs/examples/experiment-evidence/" target="_blank" rel="noopener noreferrer" class="">Experiment evidence</a></td><td>Persistent metrics, Stellar visualization, and evidence verification</td></tr><tr><td><a href="https://azure.github.io/taugrid/docs/examples/full-cluster/" target="_blank" rel="noopener noreferrer" class="">Full cluster</a></td><td>Terraform-provisioned AKS cluster with GPU nodes and end-to-end validation</td></tr></tbody></table>
<p>For more examples and detailed walkthroughs, see the <a href="https://azure.github.io/taugrid/docs/examples/" target="_blank" rel="noopener noreferrer" class="">examples documentation</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="looking-ahead">Looking ahead<a href="https://blog.aks.azure.com/2026/08/28/taugrid-open-source#looking-ahead" class="hash-link" aria-label="Direct link to Looking ahead" title="Direct link to Looking ahead" translate="no">​</a></h2>
<p>TauGrid is under active development. The full <a href="https://github.com/Azure/taugrid/blob/main/ROADMAP.md" target="_blank" rel="noopener noreferrer" class="">roadmap</a> is maintained in the repository.</p>
<p><strong>Multi-tenant platform capabilities:</strong> Support for multiple workspaces with scoped identity, RBAC, and quotas, along with an interactive portal for submitting and managing workloads.</p>
<p><strong>Distributed training workflows:</strong> End-to-end examples for PyTorch DDP, FSDP, DeepSpeed, and Hugging Face LoRA/QLoRA, plus a complete dataset lifecycle covering fetch, staging, validation, tokenization, and registration.</p>
<p><strong>Inference workflows:</strong> Production-oriented serving examples for vLLM, SGLang, and TensorRT-LLM.</p>
<p><strong>Multi-cluster and multi-cloud execution:</strong> Cross-cloud workload execution with portable data, checkpoints, and artifacts, supported by provider-agnostic observability and cost attribution.</p>
<p>TauGrid will continue to focus on the workflow and workload lifecycle layer. Cluster provisioning, pod scheduling, quota enforcement handled by Kubernetes and Kueue, framework internals, and model code remain outside its scope.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-next">What's next?<a href="https://blog.aks.azure.com/2026/08/28/taugrid-open-source#whats-next" class="hash-link" aria-label="Direct link to What's next?" title="Direct link to What's next?" translate="no">​</a></h2>
<p>Install the CLI, point it at any Kubernetes 1.30+ cluster, and submit your first workload. We are building TauGrid in the open and want your input: file an issue when something breaks, open a pull request to fix a bug or add a feature, share feedback on the lifecycle contract, or tell us how TauGrid fits (or does not fit) into your existing platform.</p>
<p>Get started with the <a href="https://azure.github.io/taugrid" target="_blank" rel="noopener noreferrer" class="">documentation</a>, explore the code on <a href="https://github.com/Azure/taugrid" target="_blank" rel="noopener noreferrer" class="">GitHub</a>, and check out the <a href="https://github.com/Azure/taugrid/blob/main/CONTRIBUTING.md" target="_blank" rel="noopener noreferrer" class="">contributing guide</a> to learn how to get involved.</p>]]></content:encoded>
            <category>AI</category>
            <category>GPU</category>
            <category>Kueue</category>
            <category>Ray</category>
            <category>Open Source</category>
        </item>
        <item>
            <title><![CDATA[Managing disruption with AKS Node Auto-Provisioning]]></title>
            <link>https://blog.aks.azure.com/2026/07/28/nap-disruption</link>
            <guid>https://blog.aks.azure.com/2026/07/28/nap-disruption</guid>
            <pubDate>Tue, 28 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn AKS best practices for managing node disruption when using node auto provisioning (NAP) with pod disruption budgets, node disruption budgets, consolidation policies, and maintenance windows in production.]]></description>
            <content:encoded><![CDATA[<p>Azure Kubernetes Service (AKS) Node Auto-Provisioning (NAP) keeps your clusters efficient: it provisions nodes for pending pods, and it continuously <em>removes</em> nodes when it's safe to do so, for example, when nodes are empty or underutilized. That node-removal disruption is where many production surprises happen.</p>
<p>When you manage Kubernetes, a few disruption questions come up fast:</p>
<ul>
<li class="">How do I control when scale down happens, or when it should not happen?</li>
<li class="">How do I make workload disruption predictable?</li>
<li class="">Why won’t NAP scale down my nodes, even with lots of underused capacity?</li>
<li class="">Why do upgrades get stuck on certain nodes?</li>
</ul>
<p>This post focuses on <strong>NAP disruption best practices</strong>, not workload scheduling tools such as topology spread constraints, node affinity, and taints. For scheduling best practices, see the <a href="https://blog.aks.azure.com/2026/03/20/node-provisioning-best-practice" target="_blank" rel="noopener noreferrer" class="">NAP scheduling fundamentals blog post</a>.</p>
<p>If you’re new to these features, start here. If you already use NAP disruption settings, use this post as a checklist for the behaviors AKS users most commonly ask about.</p>
<hr>
<p><img decoding="async" loading="lazy" alt="Diagram showing two concentric defensive layers protecting workloads during NAP node consolidation" src="https://blog.aks.azure.com/assets/images/hero-image-60142d25bb0937dda597910c57e8faec.png" width="1536" height="1024" class="img_ev3q"></p>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>info</div><div class="admonitionContent_BuS1"><p>Learn more about how to <a href="https://learn.microsoft.com/azure/aks/node-auto-provisioning-disruption" target="_blank" rel="noopener noreferrer" class="">configure disruption policies for NAP</a></p></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="two-layers-of-disruption-control">Two layers of disruption control<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#two-layers-of-disruption-control" class="hash-link" aria-label="Direct link to Two layers of disruption control" title="Direct link to Two layers of disruption control" translate="no">​</a></h2>
<p>When NAP decides a node (virtual machine) <em>could</em> be removed, two layers of controls determine whether it actually happens:</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="workload-layer-pod-disruption-budgets-pdbs">Workload layer: Pod Disruption Budgets (PDBs)<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#workload-layer-pod-disruption-budgets-pdbs" class="hash-link" aria-label="Direct link to Workload layer: Pod Disruption Budgets (PDBs)" title="Direct link to Workload layer: Pod Disruption Budgets (PDBs)" translate="no">​</a></h3>
<p>PDBs are Kubernetes-native guardrails that limit <strong>voluntary evictions</strong> of pods. PDBs tell Kubernetes:</p>
<p>"During voluntary disruptions, keep at least N replicas available, or limit max unavailable."</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>Pod disruption budgets protect against <strong>voluntary evictions</strong>, not involuntary failures, forced migrations, or spot node eviction.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="infrastructure-layer-node-level-disruption-settings">Infrastructure layer: node-level disruption settings<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#infrastructure-layer-node-level-disruption-settings" class="hash-link" aria-label="Direct link to Infrastructure layer: node-level disruption settings" title="Direct link to Infrastructure layer: node-level disruption settings" translate="no">​</a></h3>
<p>NAP exposes disruption controls at the node level.</p>
<p>NAP is built on Karpenter concepts and exposes disruption controls on the <strong>NodePool</strong>:</p>
<ul>
<li class=""><strong>Consolidation policy</strong>: when NAP is allowed to consolidate</li>
<li class=""><strong>Disruption budgets</strong>: how many nodes can be disrupted at once, and when</li>
<li class=""><strong>Expire-after</strong>: node lifetime</li>
<li class=""><strong>Drift</strong>: replace nodes that are out of date with the desired NodePool configuration</li>
</ul>
<p>A good operational posture is simple: <strong>use PDBs to protect applications</strong> and <strong>use NAP disruption tools to control the cluster's disruption rate</strong>.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="nap-overview">NAP overview<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#nap-overview" class="hash-link" aria-label="Direct link to NAP overview" title="Direct link to NAP overview" translate="no">​</a></h2>
<p>Node auto-provisioning (NAP) provisions, scales, and manages nodes. NAP bases its scheduling and disruption logic on settings from three sources:</p>
<ul>
<li class="">Workload deployment file: for disruption, NAP honors the PodDisruptionBudgets you define here.</li>
<li class=""><a href="https://learn.microsoft.com/azure/aks/node-auto-provisioning-node-pools" target="_blank" rel="noopener noreferrer" class="">NodePool CRD</a>: lists the allowed virtual machine options, such as size, zones, and architecture, and also defines disruption settings.</li>
<li class=""><a href="https://learn.microsoft.com/azure/aks/node-auto-provisioning-aksnodeclass" target="_blank" rel="noopener noreferrer" class="">AKSNodeClass CRD</a>: defines Azure-specific settings.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-nap-handles-disruption">How NAP handles disruption<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#how-nap-handles-disruption" class="hash-link" aria-label="Direct link to How NAP handles disruption" title="Direct link to How NAP handles disruption" translate="no">​</a></h3>
<p>NAP honors Kubernetes-native concepts such as <code>PodDisruptionBudgets</code> when making disruption decisions. NAP also includes Karpenter-based concepts such as <a href="https://learn.microsoft.com/azure/aks/node-auto-provisioning-disruption#consolidation" target="_blank" rel="noopener noreferrer" class="">consolidation</a>, <a href="https://learn.microsoft.com/azure/aks/node-auto-provisioning-disruption#drift" target="_blank" rel="noopener noreferrer" class="">drift</a>, and <a href="https://learn.microsoft.com/azure/aks/node-auto-provisioning-disruption#disruption-budgets" target="_blank" rel="noopener noreferrer" class="">node disruption budgets</a>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-disruption-means-in-nap">What disruption means in NAP<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#what-disruption-means-in-nap" class="hash-link" aria-label="Direct link to What disruption means in NAP" title="Direct link to What disruption means in NAP" translate="no">​</a></h3>
<p>In NAP, disruption typically refers to <strong>voluntary</strong> actions that delete nodes after draining them, such as:</p>
<ul>
<li class=""><strong>Consolidation</strong>: delete or replace nodes with better virtual machine sizes to improve compute efficiency and reduce cost.</li>
<li class=""><strong>Drift</strong>: replace existing nodes that no longer match the desired configuration, for example, updated settings in your NodePool and AKSNodeClass CRDs.</li>
<li class=""><strong>Expiration</strong>: replace nodes after a configured lifetime.</li>
</ul>
<p>These actions differ from <strong>involuntary</strong> disruptions such as:</p>
<ul>
<li class="">Spot eviction events</li>
<li class="">Hardware failures</li>
<li class="">Host reboots outside your control</li>
</ul>
<p>PDBs and Karpenter disruption budgets mainly help with <strong>voluntary</strong> disruptions. These features do not regulate involuntary disruption, for example, spot VM evictions, node termination events, or node stopping events.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-nap-handles-spot-nodes">How NAP handles spot nodes<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#how-nap-handles-spot-nodes" class="hash-link" aria-label="Direct link to How NAP handles spot nodes" title="Direct link to How NAP handles spot nodes" translate="no">​</a></h3>
<p>NAP can provision <a href="https://learn.microsoft.com/azure/virtual-machines/spot-vms" target="_blank" rel="noopener noreferrer" class="">Azure Spot VMs</a>, which are spare capacity in Azure available at a discount, and can be evicted by Azure once this capacity is needed back. Spot VMs are prone to eviction, by design. We recommend you limit Spot VM use to workloads that can handle disruption, and use on-demand compute for stable capacity needs. Disruption controls detailed in this blog will not stop/control spot eviction, or involuntary disruption. You can use some of these tools like consolidation policies with spot to limit when these spot VMs are disrupted due to node utilization.</p>
<p>When NAP receives a signal that a NAP-managed spot VM is scheduled to be evicted, <a href="https://learn.microsoft.com/azure/aks/node-problem-detector" target="_blank" rel="noopener noreferrer" class="">Node Problem Detector</a> flags the VM(s) as unhealthy, and NAP will begin provisioning a replacement VM.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="pod-disruption-budgets-pdbs-controlling-voluntary-disruption">Pod Disruption Budgets (PDBs): controlling voluntary disruption<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#pod-disruption-budgets-pdbs-controlling-voluntary-disruption" class="hash-link" aria-label="Direct link to Pod Disruption Budgets (PDBs): controlling voluntary disruption" title="Direct link to Pod Disruption Budgets (PDBs): controlling voluntary disruption" translate="no">​</a></h2>
<p>Most NAP disruption problems come from PDBs that are either:</p>
<ul>
<li class=""><strong>Too strict</strong>: a strong guardrail blocks node drains indefinitely.</li>
<li class=""><strong>Missing</strong>: no guardrail limits disruption.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-good-default-pdb">A good default PDB<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#a-good-default-pdb" class="hash-link" aria-label="Direct link to A good default PDB" title="Direct link to A good default PDB" translate="no">​</a></h3>
<p>Kubernetes documentation describes <code>minAvailable</code> and <code>maxUnavailable</code> as the two key availability settings for PDBs. You can specify only one per PDB.</p>
<p>Here's an example of a PDB that regulates disruption without blocking scale down, upgrades, and consolidation:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> policy/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> PodDisruptionBudget</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> web</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">pdb</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">maxUnavailable</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">selector</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">matchLabels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> web</span><br></div></code></pre></div></div>
<p>Why it works well in practice:</p>
<ul>
<li class="">Consolidation, drift, and expiration can still proceed.</li>
<li class="">It helps you avoid large brownouts caused by draining too many replicas at once.</li>
<li class="">It reduces the chance of NAP thrashing a service by repeatedly moving too many pods.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-common-pdb-pitfall-zero-voluntary-evictions">The common PDB pitfall: zero voluntary evictions<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#the-common-pdb-pitfall-zero-voluntary-evictions" class="hash-link" aria-label="Direct link to The common PDB pitfall: zero voluntary evictions" title="Direct link to The common PDB pitfall: zero voluntary evictions" translate="no">​</a></h3>
<p>If you effectively set zero voluntary evictions, for example, <code>maxUnavailable: 0</code> or <code>minAvailable: 100%</code>, Kubernetes warns that this can block node drains indefinitely for a node running one of those pods.</p>
<p>This common misconfiguration can cause scenarios such as:</p>
<ul>
<li class="">Node and cluster upgrades fail because nodes do not voluntarily scale down.</li>
<li class="">Migration fails.</li>
<li class="">NAP consolidation never happens.</li>
</ul>
<p>This can be intentional for extremely sensitive workloads, but it has a cost. If a node has one of these pods, draining that node can become impossible without changing the PDB or taking an outage. Set some tolerance in these PDB settings and use disruption budgets or maintenance windows to control disruption.</p>
<p><strong>Practical guidance:</strong></p>
<ul>
<li class="">Choose <code>maxUnavailable</code> or <code>maxUnavailable</code> based on your replica count and required service capacity.</li>
<li class="">For critical workloads that you do not want to disrupt, zero eviction may be intentional. Be deliberate. When you're ready to allow disruption to these workloads, you may need to change the PDBs in the workload deployment file.</li>
<li class="">For multi-replica workloads that can tolerate minor disruption, prefer a small <code>maxUnavailable</code> value, such as <code>1</code>, instead of zero voluntary disruption.</li>
<li class="">Be clear on the tradeoff. Zero tolerance can block upgrades, NAP consolidation, and scale down.</li>
</ul>
<p>The following example allows one replica to be unavailable during voluntary disruption. It is appropriate only when the workload has enough replicas to preserve its availability target. Choose <code>maxUnavailable</code> or <code>minAvailable</code> based on your replica count and required service capacity.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="controlling-consolidation-when-vs-how-fast">Controlling consolidation: when vs. how fast<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#controlling-consolidation-when-vs-how-fast" class="hash-link" aria-label="Direct link to Controlling consolidation: when vs. how fast" title="Direct link to Controlling consolidation: when vs. how fast" translate="no">​</a></h2>
<p>Two operator intents often get conflated:</p>
<ul>
<li class=""><strong>When</strong> consolidation is allowed and happens.</li>
<li class=""><strong>How much</strong> disruption can happen concurrently.</li>
</ul>
<p>Use these settings to control consolidation behavior:</p>
<ul>
<li class=""><code>consolidationPolicy: WhenEmptyOrUnderutilized</code>: triggers when NAP identifies existing nodes as underutilized or empty. NAP runs cost simulations of virtual machine size combinations that best match the current configuration. When it finds a better combination, consolidation starts.</li>
<li class=""><code>consolidateAfter: 1d</code>: controls the delay before NAP consolidates underutilized nodes, and works with the <code>consolidationPolicy</code> setting.</li>
<li class=""><code>expireAfter: 24h</code>: determines how long nodes in this NodePool CRD can exist. Older nodes are deleted regardless of consolidation policies.</li>
</ul>
<p><strong>Note:</strong> Underutilized rate is not a value you can set. NAP determines it through internal cost simulations.</p>
<p>The following example shows these disruption tools in action:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> karpenter.sh/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> NodePool</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> default</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">disruption</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">consolidationPolicy</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> WhenEmptyOrUnderutilized</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">template</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">nodeClassRef</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> default</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">expireAfter</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Never</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="node-disruption-budgets-how-fast">Node disruption budgets: how fast<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#node-disruption-budgets-how-fast" class="hash-link" aria-label="Direct link to Node disruption budgets: how fast" title="Direct link to Node disruption budgets: how fast" translate="no">​</a></h3>
<p>NAP exposes Karpenter-style disruption budgets on the NodePool. If you don’t set them, a default budget of <code>nodes: 10%</code> applies. Use budgets to regulate how many nodes are consolidated at a time.</p>
<p>The following example sets the node disruption budget to one node at a time.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> karpenter.sh/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> NodePool</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> default</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">disruption</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">budgets</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">nodes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"1"</span><br></div></code></pre></div></div>
<p>The following example sets the node disruption budget to not disrupt more than 10% of nodes at a time.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> karpenter.sh/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> NodePool</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> default</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">disruption</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">budgets</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">nodes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 10%</span><br></div></code></pre></div></div>
<p>This is often the simplest way to prevent NAP from moving too many nodes at once.</p>
<hr>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="maintenance-windows">Maintenance windows<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#maintenance-windows" class="hash-link" aria-label="Direct link to Maintenance windows" title="Direct link to Maintenance windows" translate="no">​</a></h3>
<p>A good practice is to allow some consolidation, but only during a specific time window.</p>
<p>NAP node disruption budgets support <code>schedule</code> and <code>duration</code>, so you can create time-based rules with cron syntax. Define these budgets in the <code>spec.disruption.budgets</code> field of the <a href="https://learn.microsoft.com/azure/aks/node-auto-provisioning-node-pools" target="_blank" rel="noopener noreferrer" class="">NodePool CRD</a>.</p>
<p>For example, block disruptions during business hours:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">budgets</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">nodes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"0"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">schedule</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"0 9 * * 1-5"</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># 9 AM Monday-Friday</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">duration</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 8h</span><br></div></code></pre></div></div>
<p>Or allow higher disruption on weekends, and block disruption otherwise:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">budgets</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">nodes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"50%"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">schedule</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"0 0 * * 6"</span><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic"># Saturday midnight</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">duration</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 48h</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">nodes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"0"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">schedule</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"0 9 * * 1-5"</span><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># 9 AM Monday-Friday</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">duration</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 8h</span><br></div></code></pre></div></div>
<p><strong>Why this matters:</strong> It aligns cost optimization, including consolidation, drift, and expiration, with the timeline that works for your workload needs.</p>
<p>To learn more about node disruption budgets, see the <a href="https://learn.microsoft.com/azure/aks/node-auto-provisioning-disruption#disruption-budgets" target="_blank" rel="noopener noreferrer" class="">NAP disruption documentation</a>.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>NAP disruption budget-based maintenance windows offer node pool-level control of consolidation. For cluster-level control of upgrades, see our documentation for <a href="https://learn.microsoft.com/azure/aks/planned-maintenance?pivots=azure-cli" target="_blank" rel="noopener noreferrer" class="">Auto-upgrade planned maintenance</a>.</p></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="observability-verify-disruption-decisions-with-events-and-logs">Observability: verify disruption decisions with events and logs<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#observability-verify-disruption-decisions-with-events-and-logs" class="hash-link" aria-label="Direct link to Observability: verify disruption decisions with events and logs" title="Direct link to Observability: verify disruption decisions with events and logs" translate="no">​</a></h2>
<p>Before you change policies, confirm what NAP <em>thinks</em> it's doing:</p>
<ul>
<li class="">View events:<!-- -->
<ul>
<li class="">run command <code>kubectl get events --field-selector source=karpenter-events</code></li>
</ul>
</li>
<li class="">Or use AKS control plane logs in Log Analytics, filtered for <code>karpenter-events</code></li>
</ul>
<p>This helps you distinguish between these cases:</p>
<ul>
<li class="">NAP wants to disrupt nodes, but PDBs or disruption budgets block it.</li>
<li class="">NAP is not trying to disrupt nodes because the consolidation policy does not allow it.</li>
<li class="">NAP can't replace nodes because provisioning is failing.</li>
</ul>
<p>For more on monitoring NAP logs and metrics, see the <a href="https://learn.microsoft.com/azure/aks/use-node-auto-provisioning?pivots=azure-cli#monitoring-node-auto-provisioning" target="_blank" rel="noopener noreferrer" class="">NAP observability documentation</a>.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="common-disruption-pitfalls">Common disruption pitfalls<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#common-disruption-pitfalls" class="hash-link" aria-label="Direct link to Common disruption pitfalls" title="Direct link to Common disruption pitfalls" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="symptom-nap-wont-consolidate-or-drains-hang-forever">Symptom: NAP won’t consolidate or drains hang forever<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#symptom-nap-wont-consolidate-or-drains-hang-forever" class="hash-link" aria-label="Direct link to Symptom: NAP won’t consolidate or drains hang forever" title="Direct link to Symptom: NAP won’t consolidate or drains hang forever" translate="no">​</a></h3>
<p>Behavior: Nodes do not scale down for consolidation or updates.</p>
<p>Cause:</p>
<ul>
<li class="">PDBs effectively allow zero voluntary evictions, for example, <code>maxUnavailable: 0</code> or <code>minAvailable: 100%</code>.</li>
<li class="">Too few replicas exist to satisfy the PDB during drain.</li>
</ul>
<p>Fix:</p>
<ul>
<li class="">Relax PDBs, for example, <code>maxUnavailable: 1</code>, or increase replicas.</li>
<li class="">If a workload truly must not be disrupted, accept that nodes running it will not consolidate. This also increases the risk of update failure. A strict <code>100%</code> available PDB can cause scale down and update failures.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="symptom-nap-disrupts-nodes-too-often-or-too-much-at-once">Symptom: NAP disrupts nodes too often or too much at once<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#symptom-nap-disrupts-nodes-too-often-or-too-much-at-once" class="hash-link" aria-label="Direct link to Symptom: NAP disrupts nodes too often or too much at once" title="Direct link to Symptom: NAP disrupts nodes too often or too much at once" translate="no">​</a></h3>
<p>Behavior: NAP consolidates too often or voluntarily disrupts too many nodes at once.</p>
<p>Cause:</p>
<ul>
<li class="">No guardrails exist for node disruption behavior, such as PDBs or node disruption budgets.</li>
<li class="">No maintenance window exists for scheduled disruption times.</li>
</ul>
<p>Fix:</p>
<ul>
<li class="">Add PDBs that regulate disruption pace.</li>
<li class="">Add NodePool disruption budgets. Start with <code>nodes: "1"</code> or a small percentage.</li>
<li class="">Add time-based budgets so disruption happens when you want it.</li>
</ul>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p><a href="https://learn.microsoft.com/azure/virtual-machines/spot-vms" target="_blank" rel="noopener noreferrer" class="">Spot VMs</a> by design are prone to evictions. For stable workloads that are not intended for frequent disruption, we recommend using on-demand virtual machines.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="symptom-node-disruption-happens-at-the-wrong-time">Symptom: node disruption happens at the wrong time<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#symptom-node-disruption-happens-at-the-wrong-time" class="hash-link" aria-label="Direct link to Symptom: node disruption happens at the wrong time" title="Direct link to Symptom: node disruption happens at the wrong time" translate="no">​</a></h3>
<p>Behavior: Disruption happens during inconvenient times, such as work hours or peak usage windows.</p>
<p>Cause:</p>
<ul>
<li class="">No time-based budgets or maintenance window exists.</li>
</ul>
<p>Fix:</p>
<ul>
<li class="">Add Karpenter disruption budgets to block disruption during business hours.</li>
<li class="">Combine a maintenance window with a small allowed-disruption budget outside the window.</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="next-steps">Next steps<a href="https://blog.aks.azure.com/2026/07/28/nap-disruption#next-steps" class="hash-link" aria-label="Direct link to Next steps" title="Direct link to Next steps" translate="no">​</a></h2>
<ol>
<li class=""><strong>Try NAP today:</strong> Check out the <a href="https://learn.microsoft.com/azure/aks/use-node-auto-provisioning" target="_blank" rel="noopener noreferrer" class="">Enable Node Auto Provisioning steps</a>.</li>
<li class=""><strong>Learn more:</strong> Visit the AKS <a href="https://learn.microsoft.com/azure/aks/operator-best-practices-advanced-scheduler" target="_blank" rel="noopener noreferrer" class="">operator best-practices guidance</a>.</li>
<li class="">Troubleshoot NAP: For more common issues visit our <a href="https://learn.microsoft.com/troubleshoot/azure/azure-kubernetes/extensions/troubleshoot-node-auto-provision" target="_blank" rel="noopener noreferrer" class="">NAP troubleshooting guide</a></li>
<li class=""><strong>Share feedback:</strong> Open issues or ideas in <a href="https://github.com/Azure/AKS/issues" target="_blank" rel="noopener noreferrer" class="">AKS GitHub Issues</a>.</li>
<li class=""><strong>Join the community:</strong> Subscribe to the <a href="https://www.youtube.com/@theakscommunity" target="_blank" rel="noopener noreferrer" class="">AKS Community YouTube</a> and follow <a href="https://x.com/theakscommunity" target="_blank" rel="noopener noreferrer" class="">@theakscommunity</a> on X.</li>
</ol>]]></content:encoded>
            <category>Node Auto Provisioning</category>
        </item>
        <item>
            <title><![CDATA[Stream Model Weights to NVIDIA GPU (vLLM) from Azure Blob Storage using the RunAI Model Streamer]]></title>
            <link>https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm</link>
            <guid>https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm</guid>
            <pubDate>Mon, 13 Jul 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Stream LLM Model Weights to NVIDIA GPU running vLLM directly from Azure Blob Storage on AKS using the RunAI Model Streamer to cut cold starts and speed up autoscaling.]]></description>
            <content:encoded><![CDATA[<p>If you serve large language models on AKS, you eventually run into the same wall: every time an inference replica scales up, after a node failure, during a rollout, or when your autoscaler races to meet a traffic spike, the new pod has to load tens of gigabytes of model weights before it can serve a single request. That load time is your cold-start tax, and it lands at the worst possible moment: exactly when you need capacity <em>now</em>. This post shows how to cut it by streaming weights straight from Azure Blob Storage into GPU memory with the RunAI Model Streamer, and walks the full setup end to end on AKS.</p>
<p>When you autoscale LLM inference on Kubernetes, cold-start time is dominated by one thing: getting those tens of gigabytes of model weights off storage and into GPU memory. The naive path involves downloading all the model weights to local disk, then loading them into the GPU. However, this serializes two slow copies back-to-back and leaves the network idle while the GPU waits.</p>
<p>The <a href="https://github.com/run-ai/runai-model-streamer" target="_blank" rel="noopener noreferrer" class="">RunAI Model Streamer</a> collapses that into a single streaming step: it reads tensors concurrently from object storage and feeds them into GPU memory as they arrive, saturating the available bandwidth instead of staging everything on disk first. vLLM wires it in behind <code>--load-format runai_streamer</code>. For production inference on AKS, that translates to pods that reach a ready, serving state faster, which means quicker recovery from node failures, snappier rollouts, and autoscaling that can add replicas in time to absorb a traffic spike instead of lagging behind it.</p>
<p><img decoding="async" loading="lazy" alt="Isometric illustration of model weights streaming from Azure Blob Storage directly into a GPU, with a continuous flow of data blocks bypassing local disk staging." src="https://blog.aks.azure.com/assets/images/hero-80f7e0fee0fa3d91d95aacbde494a4da.png" width="1536" height="1024" class="img_ev3q"></p>
<p>Support for Azure Blob Storage was recently added to the RunAI Model Streamer. <strong>As of vLLM v0.18.0 (<a href="https://github.com/vllm-project/vllm/pull/34614" target="_blank" rel="noopener noreferrer" class="">vllm-project/vllm#34614</a>) and runai-model-streamer v0.15.6 (<a href="https://github.com/run-ai/runai-model-streamer/pull/116" target="_blank" rel="noopener noreferrer" class="">run-ai/runai-model-streamer#116</a>), the <code>az://</code> scheme is now supported out of the box.</strong> A stock <code>vllm/vllm-openai</code> image can stream from Blob with nothing more than an environment variable and a workload-identity binding.</p>
<p>This post walks the whole thing end to end on AKS:</p>
<ol>
<li class="">An AKS cluster with OIDC + workload identity, and a <strong>fully managed</strong> A100 GPU node pool (AKS installs and maintains the NVIDIA driver, device plugin, and DCGM exporter, no need to install the NVIDIA GPU operator).</li>
<li class="">A premium block-blob storage account to hold the weights.</li>
<li class="">Workload identity that lets pods read and write Blob with a Microsoft Entra ID token instead of a storage key.</li>
<li class="">An <strong>in-cluster upload Job</strong> that pulls <code>microsoft/phi-4</code> from HuggingFace and pushes it to Blob, running inside Azure, so it uses Azure's backbone bandwidth rather than your home uplink. Such jobs need to be run once per model.</li>
<li class="">A vLLM Deployment that streams those weights straight from <code>az://</code> at startup.</li>
</ol>
<p>:::note Why upload from a Job instead of your laptop?
Multi-gigabyte model weights uploaded from a workstation are gated by your upstream bandwidth and can take hours. A Job in the same region as the storage account moves the same bytes over Azure's network in minutes, and it authenticates with workload identity so there are no keys to copy around.
:::</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-stream-instead-of-download-then-load">Why stream instead of download-then-load?<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#why-stream-instead-of-download-then-load" class="hash-link" aria-label="Direct link to Why stream instead of download-then-load?" title="Direct link to Why stream instead of download-then-load?" translate="no">​</a></h2>
<p>The default way vLLM loads a model is a two-step relay: download all the model weights from the source (<a href="https://huggingface.co/models" target="_blank" rel="noopener noreferrer" class="">HuggingFace Hub</a> or object storage) onto the node's local disk, then read them back off disk to load them into GPU memory. The RunAI streamer replaces both halves with a single concurrent stream from object storage straight into GPU memory.</p>
<p><img decoding="async" loading="lazy" alt="Two diagrams compared: the default download-then-load path copies the entire model from object storage to local disk while the GPU sits idle, then reads it back off disk into GPU memory while the network sits idle; the RunAI streamer path reads concurrently from Azure Blob straight into GPU memory with no disk staging." src="https://blog.aks.azure.com/assets/images/1-why-stream-vs-download-8f566c770c049d5f18897f25624686e9.png" width="2816" height="1536" class="img_ev3q"></p>
<p>To put real numbers on it, we timed both paths for <code>microsoft/phi-4</code> (about 27.4 GiB, ~29 GB of weights) on the same single, A100 node (<code>Standard_NC24ads_A100_v4</code>) with vLLM v0.23.0, the exact setup this post walks you through:</p>
<table><thead><tr><th>Path</th><th style="text-align:right">Download to disk</th><th style="text-align:right">Load into GPU</th><th style="text-align:right">Time to weights loaded</th></tr></thead><tbody><tr><td>Default: HuggingFace → local disk → GPU</td><td style="text-align:right">~113 s</td><td style="text-align:right">~74 s</td><td style="text-align:right"><strong>~187 s</strong></td></tr><tr><td>Streamer: Azure Blob → GPU (<code>runai_streamer</code>)</td><td style="text-align:right">— (no staging)</td><td style="text-align:right"><strong>~10 s</strong></td><td style="text-align:right"><strong>~10 s</strong></td></tr></tbody></table>
<p>The single most comparable number is the load step itself, reading the same ~29 GB (about 27.4 GiB) into GPU memory: <strong>~74 s</strong> off local disk versus <strong>~10 s</strong> streamed from Blob, roughly <strong>7× faster</strong>, because the streamer overlaps the read with the GPU copy and reads with high concurrency instead of shard-by-shard. Add the download the default path can't avoid and the end-to-end gap widens to <strong>~187 s vs ~10 s</strong>. (The download half in particular is network-dependent and will vary run to run; the load-step comparison is the stable, apples-to-apples one.)</p>
<p>Three things make the default path slow, and the streamer fixes each one:</p>
<ul>
<li class=""><strong>Two serial passes instead of one.</strong> With download-then-load, <em>nothing</em> reaches the GPU until the last byte has landed on disk. The GPU sits idle through the entire download, and the network sits idle through the entire disk-to-GPU load, the two slow copies never overlap. The streamer pipelines them: a tensor is loading onto the GPU while the next ones are still in flight from storage.</li>
<li class=""><strong>A wasted round-trip through disk.</strong> Staging to disk writes tens of gigabytes and immediately reads them back, a second full pass over the data that exists only to bridge the two steps. Even on fast local NVMe that's pure overhead; on network-attached or smaller disks it's worse, and large model weights can fill the disk outright. Streaming never touches disk.</li>
<li class=""><strong>No concurrency.</strong> A plain download and a plain disk read each tend to move data in a single stream, leaving bandwidth unused. The streamer issues many parallel reads against object storage and feeds the GPU concurrently, keeping the link saturated, which is exactly why the premium block-blob account in step 2 is worth it.</li>
</ul>
<p>The net effect, as the numbers above show: cold-start time drops from <em>download time + disk-load time</em> (added together, ~187 s here) to roughly <em>the streaming time alone</em> (~10 s, overlapped), with the disk write-and-read-back eliminated entirely. That gap only widens as your models grow and your autoscaler gets busier: the larger the model weights, the more disk staging you skip, and the more often you scale out at peak traffic, the more those saved seconds compound into lower tail latency and fewer cold-start stalls for your AKS inference apps. Let's build the infrastructure to prove it.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerequisites">Prerequisites<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites" translate="no">​</a></h2>
<ul>
<li class="">The <a href="https://learn.microsoft.com/cli/azure/install-azure-cli" target="_blank" rel="noopener noreferrer" class="">Azure CLI</a> (<code>az</code>), version 2.85.0 or later.</li>
<li class=""><a href="https://kubernetes.io/docs/tasks/tools/" target="_blank" rel="noopener noreferrer" class=""><code>kubectl</code></a> and <a href="https://jqlang.github.io/jq/" target="_blank" rel="noopener noreferrer" class=""><code>jq</code></a>.</li>
<li class=""><code>envsubst</code> (ships with GNU gettext) used to fill the <code>${...}</code> placeholders in the Kubernetes manifests before applying them. On macOS, <code>brew install gettext</code>; on Debian/Ubuntu, <code>apt-get install gettext-base</code>.</li>
<li class="">An Azure subscription with quota for an NVIDIA GPU VM (For example: <code>Standard_NC24ads_A100_v4</code>) in your target region.</li>
<li class="">Logged in: <code>az login</code> and <code>az account set --subscription &lt;id&gt;</code>.</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="configuration">Configuration<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#configuration" class="hash-link" aria-label="Direct link to Configuration" title="Direct link to Configuration" translate="no">​</a></h2>
<p>Everything below is driven by these variables. Modify them to match your environment and export them once in your shell and the rest of the commands are copy-paste.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># --- Azure / cluster ---</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export AZURE_RESOURCE_GROUP="runai-demo"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export AZURE_REGION="&lt;location with GPU VM SKU support&gt;"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export CLUSTER_NAME="llm"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export ADMIN_USERNAME="azuser"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># --- GPU node pool ---</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export NODE_POOL_NAME="gpunodes"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export NODE_POOL_VM_SIZE="&lt;Standard_NC24ads_A100_v4 or other GPU VM size&gt;"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export NODE_POOL_NODE_COUNT=1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># --- Storage ---</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Must be globally unique, 3-24 chars, lowercase letters and digits only.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export STORAGE_ACCOUNT_NAME="&lt;provide unique name&gt;"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export STORAGE_CONTAINER_NAME="models"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># --- Workload identity ---</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export IDENTITY_NAME="vllm-model-reader"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export SERVICE_ACCOUNT_NAME="runai-streamer"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export NAMESPACE="default"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># --- Model / vLLM ---</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export MODEL_NAME="microsoft/phi-4"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export VLLM_IMAGE="vllm/vllm-openai:v0.23.0"</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-deploy-the-aks-cluster">1. Deploy the AKS cluster<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#1-deploy-the-aks-cluster" class="hash-link" aria-label="Direct link to 1. Deploy the AKS cluster" title="Direct link to 1. Deploy the AKS cluster" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1a-enable-the-managed-gpu-preview">1a. Enable the managed GPU preview<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#1a-enable-the-managed-gpu-preview" class="hash-link" aria-label="Direct link to 1a. Enable the managed GPU preview" title="Direct link to 1a. Enable the managed GPU preview" translate="no">​</a></h3>
<p>The fully managed GPU node-pool experience is a preview feature. Register it once per subscription. This is what lets us skip the NVIDIA GPU operator entirely, AKS takes over installing and maintaining the driver, the NVIDIA GPU device plugin, and the NVIDIA DCGM metrics exporter on every GPU node.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># The managed-GPU flag lives in the aks-preview extension (&gt;= 19.0.0b29).</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az extension add --name aks-preview --upgrade</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Register the feature flag.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az feature register \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --namespace Microsoft.ContainerService \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name ManagedGPUExperiencePreview</span><br></div></code></pre></div></div>
<p>Registration is asynchronous and can take a few minutes. Re-run the command below until it reports <code>Registered</code> before moving on, starting the node pool in step 1c while the feature is still <code>Registering</code> makes that command fail.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az feature show \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --namespace Microsoft.ContainerService \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name ManagedGPUExperiencePreview \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --query properties.state -o tsv</span><br></div></code></pre></div></div>
<p>Once it prints <code>Registered</code>, propagate the feature to the resource provider:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az provider register --namespace Microsoft.ContainerService</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1b-create-the-resource-group-and-cluster">1b. Create the resource group and cluster<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#1b-create-the-resource-group-and-cluster" class="hash-link" aria-label="Direct link to 1b. Create the resource group and cluster" title="Direct link to 1b. Create the resource group and cluster" translate="no">​</a></h3>
<p>First, create the resource group:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az group create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --location "${AZURE_REGION}"</span><br></div></code></pre></div></div>
<p>Then create the cluster. It needs three things switched on for workload identity to work later: <code>--enable-oidc-issuer</code> (so Kubernetes service-account tokens can be validated by Microsoft Entra ID), <code>--enable-workload-identity</code> (installs the mutating webhook that injects federated-token env vars into labeled pods), and <code>--enable-managed-identity</code>. This takes a few minutes:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aks create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${CLUSTER_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --location "${AZURE_REGION}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --node-count 1 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --enable-oidc-issuer \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --enable-workload-identity \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --enable-managed-identity \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --generate-ssh-keys \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --admin-username "${ADMIN_USERNAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --os-sku Ubuntu</span><br></div></code></pre></div></div>
<p>Finally, fetch the cluster credentials so <code>kubectl</code> targets it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aks get-credentials \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${CLUSTER_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --overwrite-existing</span><br></div></code></pre></div></div>
<p>The default node pool created here is a small CPU pool that runs system
components. GPU capacity comes from a dedicated pool in the next step.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1c-add-the-managed-gpu-node-pool">1c. Add the managed GPU node pool<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#1c-add-the-managed-gpu-node-pool" class="hash-link" aria-label="Direct link to 1c. Add the managed GPU node pool" title="Direct link to 1c. Add the managed GPU node pool" translate="no">​</a></h3>
<p>This single flag, <code>--enable-managed-gpu=true</code>, is the whole reason we don't need a GPU operator. AKS provisions the node with the NVIDIA driver already installed, deploys the NVIDIA GPU device plugin so <code>nvidia.com/gpu</code> is advertised, and wires up the NVIDIA DCGM exporter for metrics.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aks nodepool add \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --cluster-name "${CLUSTER_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${NODE_POOL_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --node-count "${NODE_POOL_NODE_COUNT}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --node-vm-size "${NODE_POOL_VM_SIZE}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --enable-managed-gpu=true</span><br></div></code></pre></div></div>
<p>Confirm the GPU is schedulable before moving on. It can take a couple of minutes after the pool is ready for the device plugin to advertise the resource:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get nodes -o json \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    | jq -r '.items[] | {name: .metadata.name, gpu: .status.allocatable["nvidia.com/gpu"]}'</span><br></div></code></pre></div></div>
<p>You're looking for a node reporting <code>"gpu": "1"</code>.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-create-a-premium-block-blob-storage-account">2. Create a premium block-blob storage account<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#2-create-a-premium-block-blob-storage-account" class="hash-link" aria-label="Direct link to 2. Create a premium block-blob storage account" title="Direct link to 2. Create a premium block-blob storage account" translate="no">​</a></h2>
<p>Premium block blob (<code>Premium_LRS</code> + <code>BlockBlobStorage</code>) gives the streamer the high, consistent throughput that makes this whole approach worthwhile. A standard account works too, but you'll leave bandwidth on the table.</p>
<p>Create the storage account:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az storage account create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${STORAGE_ACCOUNT_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --location "${AZURE_REGION}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --sku Premium_LRS \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --kind BlockBlobStorage</span><br></div></code></pre></div></div>
<p>Then create the container that will hold the model weights:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az storage container create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${STORAGE_CONTAINER_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --account-name "${STORAGE_ACCOUNT_NAME}"</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-set-up-workload-identity-for-blob-access">3. Set up workload identity for Blob access<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#3-set-up-workload-identity-for-blob-access" class="hash-link" aria-label="Direct link to 3. Set up workload identity for Blob access" title="Direct link to 3. Set up workload identity for Blob access" translate="no">​</a></h2>
<p>This is the part that lets pods talk to Blob with <strong>zero secrets</strong>. The chain looks like this:</p>
<p><img decoding="async" loading="lazy" alt="Workload-identity trust chain: a labeled pod running as a dedicated service account presents a projected token to a federated credential, which trusts the cluster&amp;#39;s OIDC issuer and maps to a user-assigned managed identity; that identity holds the Storage Blob Data Contributor role, which grants access to Azure Blob Storage." src="https://blog.aks.azure.com/assets/images/2-identity-fd09977fe0e04392a7b56ed7c6aba57f.png" width="2816" height="1536" class="img_ev3q"></p>
<p>We use <strong>Storage Blob Data Contributor</strong> (not just Reader) on purpose: the same identity is reused by both the upload Job (which needs to <em>write</em>) and the vLLM pod (which only <em>reads</em>).</p>
<p>Read the cluster's OIDC issuer URL, the trust anchor for the federated credential, and create the user-assigned managed identity that pods will run as:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># The cluster's OIDC issuer URL, the trust anchor for federated credentials.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export AKS_OIDC_ISSUER=$(az aks show \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${CLUSTER_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --query "oidcIssuerProfile.issuerUrl" -o tsv)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Create a user-assigned managed identity.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az identity create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${IDENTITY_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --location "${AZURE_REGION}"</span><br></div></code></pre></div></div>
<p>Capture the identity's client ID and object ID, plus the storage account's resource ID, the next two commands reference them:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">export IDENTITY_CLIENT_ID=$(az identity show \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${IDENTITY_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --query clientId -o tsv)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export IDENTITY_OBJECT_ID=$(az identity show \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${IDENTITY_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --query principalId -o tsv)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export STORAGE_ACCOUNT_ID=$(az storage account show \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "${STORAGE_ACCOUNT_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --query id -o tsv)</span><br></div></code></pre></div></div>
<p>Grant the identity read/write access on the storage account:</p>
<p>:::caution You need permission to create role assignments
The <code>az role assignment create</code> step here writes to <code>Microsoft.Authorization/roleAssignments</code>, which requires an elevated role on the scope: <strong>Owner</strong>, <strong>User Access Administrator</strong>, or <strong>Role Based Access Control Administrator</strong>. Plain Contributor isn't enough. Without it you'll see:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">(AuthorizationFailed) The client 'you@example.com' with object id '...' does not</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">have authorization to perform action 'Microsoft.Authorization/roleAssignments/write'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">over scope '/subscriptions/.../storageAccounts/&lt;account&gt;/providers/Microsoft.Authorization/roleAssignments/...'</span><br></div></code></pre></div></div>
<p>If you hit this, ask a subscription/resource-group administrator to either grant you one of those roles or run the role-assignment command for you. The rest of the setup is unaffected.
:::</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az role assignment create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --role "Storage Blob Data Contributor" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --assignee-object-id "${IDENTITY_OBJECT_ID}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --assignee-principal-type ServicePrincipal \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --scope "${STORAGE_ACCOUNT_ID}"</span><br></div></code></pre></div></div>
<p>Finally, federate the Kubernetes service account to the managed identity, so a projected service-account token can be exchanged for a Microsoft Entra ID token:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az identity federated-credential create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name "vllm-federated-cred" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --identity-name "${IDENTITY_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group "${AZURE_RESOURCE_GROUP}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --issuer "${AKS_OIDC_ISSUER}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --subject "system:serviceaccount:${NAMESPACE}:${SERVICE_ACCOUNT_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --audience api://AzureADTokenExchange</span><br></div></code></pre></div></div>
<p>Create a <strong>dedicated</strong> service account for these workloads, rather than reusing the namespace's shared <code>default</code>, so the workload-identity wiring stays off every other pod in the namespace. <a href="https://github.com/Azure-Samples/aks-samples/blob/master/runai-model-streamer/manifests/service-account.yaml" target="_blank" rel="noopener noreferrer" class=""><code>manifests/service-account.yaml</code></a> declares it with two pieces of workload-identity metadata: an <strong>annotation</strong> (<code>azure.workload.identity/client-id</code>) that tells the webhook <em>which</em> managed identity to mint tokens for, and a <strong>label</strong> (<code>azure.workload.identity/use: "true"</code>).</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -sL https://raw.githubusercontent.com/Azure-Samples/aks-samples/refs/heads/master/runai-model-streamer/manifests/service-account.yaml \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    | envsubst \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    | kubectl apply -f -</span><br></div></code></pre></div></div>
<p>A subtlety worth understanding: the <strong>label</strong> is what actually triggers token injection, but the webhook keys off the label on the <strong>pod</strong>, not the service account. The label on the service account here is optional; the authoritative switch is the <code>azure.workload.identity/use: "true"</code> label in each pod template (you'll see it on both the Job and the Deployment in later sections). A pod that runs as this service account but is missing that label gets <strong>no</strong> token injected, and fails to authenticate to Blob with no obvious error. So from here on, every workload that needs Blob access carries <code>azure.workload.identity/use: "true"</code> on its <strong>pod template</strong>, which earns it <code>AZURE_CLIENT_ID</code>, <code>AZURE_TENANT_ID</code>, and a projected federated token automatically.</p>
<p>:::note Give the role assignment a couple of minutes to propagate
Azure RBAC role assignments are eventually consistent. If the upload Job in the next step starts too soon, it fails with <code>AuthorizationPermissionMismatch</code>. If you hit that, wait a couple of minutes (<code>sleep 120</code>) and let the Job's <code>backoffLimit</code> retry. To re-run the Job from scratch, delete it first, a <code>Job</code> is immutable, so a second <code>kubectl apply</code> fails with <code>AlreadyExists</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl delete job upload-model --ignore-not-found</span><br></div></code></pre></div></div>
<p>:::</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-upload-the-model-weights-from-a-kubernetes-job">4. Upload the model weights from a Kubernetes Job<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#4-upload-the-model-weights-from-a-kubernetes-job" class="hash-link" aria-label="Direct link to 4. Upload the model weights from a Kubernetes Job" title="Direct link to 4. Upload the model weights from a Kubernetes Job" translate="no">​</a></h2>
<p>The Job does three things: pull <code>microsoft/phi-4</code> from HuggingFace, install <code>azcopy</code>, and copy the files into Blob using workload identity (<code>AZCOPY_AUTO_LOGIN_TYPE=WORKLOAD</code>). Because it runs in-cluster, the HuggingFace → Blob transfer rides Azure's network, not yours.</p>
<p>A few details worth calling out:</p>
<ul>
<li class="">The pod template carries the <code>azure.workload.identity/use: "true"</code> label, so <code>azcopy</code> finds the federated token automatically.</li>
<li class=""><code>--overwrite=ifSourceNewer</code> makes retries cheap: a re-run skips blobs that already uploaded successfully instead of re-sending all the model weights.</li>
<li class=""><code>--exclude-path '.cache'</code> skips HuggingFace's local cache directory.</li>
</ul>
<p>The full manifest lives in <a href="https://github.com/Azure-Samples/aks-samples/blob/master/runai-model-streamer/manifests/upload-model-job.yaml" target="_blank" rel="noopener noreferrer" class=""><code>Azure-Samples/aks-samples</code></a>. Pull it, fill in the placeholders with <code>envsubst</code>, and apply, all in one copy-pastable command:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -sL https://raw.githubusercontent.com/Azure-Samples/aks-samples/refs/heads/master/runai-model-streamer/manifests/upload-model-job.yaml \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    | envsubst '${NAMESPACE} ${SERVICE_ACCOUNT_NAME} ${STORAGE_ACCOUNT_NAME} ${STORAGE_CONTAINER_NAME} ${MODEL_NAME}' \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    | kubectl apply -f -</span><br></div></code></pre></div></div>
<p>The <code>envsubst</code> allow-list substitutes only those five variables, so the runtime shell variables inside the container script (<code>$AZURE_STORAGE_ACCOUNT_NAME</code>, the retry counters) are left untouched.</p>
<p>Wait for it to finish and check the logs:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl wait --for=condition=complete job/upload-model --timeout=1800s</span><br></div></code></pre></div></div>
<p>:::note A note on scaling to larger models
The 1800s (30 minute) timeout is plenty for a model like <code>microsoft/phi-4</code> (a 14.7B-parameter model, ~29 GB on disk in bf16). If you reuse this manifest for a 70B+ parameter model, two limits need raising together: bump this <code>--timeout</code> so the command doesn't give up while the Job is still uploading, <strong>and</strong> raise the <code>ephemeral-storage</code> request/limit on the Job, larger model weights that overflow the reserved scratch space get evicted with <code>DiskPressure</code> long before any timeout fires.
:::</p>
<p>In a separate terminal you can follow the logs:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl logs -f job/upload-model</span><br></div></code></pre></div></div>
<p>Verify the blobs landed:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az storage blob list \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --account-name "${STORAGE_ACCOUNT_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --container-name "${STORAGE_CONTAINER_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --prefix "${MODEL_NAME}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --output table</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-deploy-vllm-streaming-from-azure-blob">5. Deploy vLLM streaming from Azure Blob<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#5-deploy-vllm-streaming-from-azure-blob" class="hash-link" aria-label="Direct link to 5. Deploy vLLM streaming from Azure Blob" title="Direct link to 5. Deploy vLLM streaming from Azure Blob" translate="no">​</a></h2>
<p>Now the payoff. Two things turn on Blob streaming:</p>
<ul>
<li class=""><code>--load-format runai_streamer</code> selects the RunAI Model Streamer loader.</li>
<li class=""><code>--model az://${STORAGE_CONTAINER_NAME}/${MODEL_NAME}</code> points at the blobs using the native <code>az://</code> scheme. The <code>AZURE_STORAGE_ACCOUNT_NAME</code> env var tells the streamer which account to read from, and the pod's workload-identity label handles auth, no keys, no connection strings.</li>
</ul>
<p>:::tip Tuning the streamer
The RunAI Model Streamer exposes <a href="https://docs.vllm.ai/en/latest/models/extensions/runai_model_streamer/#tunable-parameters" target="_blank" rel="noopener noreferrer" class="">tunable parameters</a> that you pass to vLLM as a JSON object via <code>--model-loader-extra-config</code>. A few worth knowing:</p>
<ul>
<li class=""><code>'{"distributed":true}'</code> enables distributed streaming across tensor-parallel ranks, useful once you scale <code>--tensor-parallel-size</code> past 1.</li>
<li class=""><code>'{"concurrency":16}'</code> sets how many OS threads read tensors from storage into the CPU buffer; raise it to push more bandwidth against a premium account.</li>
<li class=""><code>'{"memory_limit":5368709120}'</code> caps the CPU staging buffer (in bytes).</li>
</ul>
<p>To set one, add the flag to the container's <code>args</code> list in the <a href="https://github.com/Azure-Samples/aks-samples/blob/master/runai-model-streamer/manifests/vllm-deployment.yaml" target="_blank" rel="noopener noreferrer" class="">Deployment manifest</a>, for example:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">loader</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">extra</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">'{"concurrency":16}'</span><br></div></code></pre></div></div>
<p>:::</p>
<p>The pod mounts an in-memory <code>emptyDir</code> at <code>/dev/shm</code> because vLLM uses shared memory for inter-process communication. A <code>startupProbe</code> and <code>readinessProbe</code> on vLLM's <code>/health</code> endpoint keep the pod out of the Service until the model has finished streaming and the server is actually answering.</p>
<p>The full manifest, a <code>Deployment</code> plus a <code>Service</code>, is in <a href="https://github.com/Azure-Samples/aks-samples/blob/master/runai-model-streamer/manifests/vllm-deployment.yaml" target="_blank" rel="noopener noreferrer" class=""><code>Azure-Samples/aks-samples</code></a>. It has no runtime-only variables, so <code>envsubst</code> can substitute everything:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -sL https://raw.githubusercontent.com/Azure-Samples/aks-samples/refs/heads/master/runai-model-streamer/manifests/vllm-deployment.yaml \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    | envsubst \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    | kubectl apply -f -</span><br></div></code></pre></div></div>
<p>Wait for the rollout to complete:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl rollout status deployment/phi-4 --timeout=600s</span><br></div></code></pre></div></div>
<p>In a separate terminal you can follow the logs:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl logs -f deployment/phi-4</span><br></div></code></pre></div></div>
<p>How do you know the weights actually came from Blob through the RunAI streamer and not vLLM's default loader? Look for the streamer's own progress line, <code>Loading safetensors using Runai Model Streamer</code>. Its presence is the confirmation, the <code>runai_streamer</code> loader engaged and pulled the tensors from <code>az://</code> directly into GPU memory:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">(EngineCore pid=157) INFO 06-29 20:07:33 [gpu_model_runner.py:5092] Starting to load model /root/.cache/vllm/assets/model_streamer/4d80bd96...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Loading safetensors using Runai Model Streamer:   0% Completed | 0/243 [00:00&lt;?, ?it/s]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Loading safetensors using Runai Model Streamer:  47% Completed | 114/243 [00:06&lt;00:07, 17.13it/s]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Loading safetensors using Runai Model Streamer: 100% Completed | 243/243 [00:08&lt;00:00, 27.80it/s]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">(EngineCore pid=157) INFO 06-29 20:07:43 [gpu_model_runner.py:5187] Model loading took 27.39 GiB memory and 9.956121 seconds</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">...</span><br></div></code></pre></div></div>
<p>The closing <code>Model loading took ... seconds</code> line reports how long the stream-and-load took end to end, a handy number to compare against the default download-then-load path.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="6-verify-and-test">6. Verify and test<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#6-verify-and-test" class="hash-link" aria-label="Direct link to 6. Verify and test" title="Direct link to 6. Verify and test" translate="no">​</a></h2>
<p>Once it's serving, port-forward and send a chat completion. The port-forward needs a moment to establish, so wait for <code>/health</code> to answer before firing the request:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl port-forward svc/phi-4 8000:8000 &amp;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">curl -s http://localhost:8000/v1/chat/completions \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    -H "Content-Type: application/json" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    -d '{</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        "model": "microsoft/phi-4",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        "messages": [{"role": "user", "content": "Hello!"}],</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        "max_tokens": 50</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }' | jq .</span><br></div></code></pre></div></div>
<p>A JSON response with a <code>choices[0].message.content</code> field means the model loaded from Blob and is serving.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-it-fits-together">How it fits together<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#how-it-fits-together" class="hash-link" aria-label="Direct link to How it fits together" title="Direct link to How it fits together" translate="no">​</a></h2>
<p><img decoding="async" loading="lazy" alt="End-to-end flow: HuggingFace weights are downloaded by an in-cluster upload Job using workload identity, which uploads them with azcopy to Azure Blob (az://models/microsoft/phi-4); the vLLM pod then streams them natively over az:// with the RunAI Model Streamer (--load-format runai_streamer) and loads the tensors into A100 GPU memory." src="https://blog.aks.azure.com/assets/images/3-end-to-end-5bca5e77d57605fdb90119519d6f32c2.png" width="2814" height="1536" class="img_ev3q"></p>
<p>The pieces that make this clean:</p>
<ul>
<li class=""><strong>Native <code>az://</code> support</strong> in vLLM + runai-model-streamer means a stock <code>vllm/vllm-openai</code> image just works. No custom builds, no monkey-patching.</li>
<li class=""><strong>Workload identity end to end.</strong> Neither the uploader nor the server ever sees a storage key, both authenticate with short-lived federated tokens, and the same managed identity does double duty (write for the Job, read for the server).</li>
<li class=""><strong>Managed GPU node pool.</strong> <code>--enable-managed-gpu=true</code> hands the driver, device plugin, and DCGM exporter lifecycle to AKS, so there's no GPU operator to install, reconcile, or debug.</li>
<li class=""><strong>Upload from inside Azure.</strong> Moving the HuggingFace → Blob copy into a Job trades your home uplink for Azure's backbone, turning an hours-long upload into a minutes-long one.</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="trade-offs-and-downsides">Trade-offs and downsides<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#trade-offs-and-downsides" class="hash-link" aria-label="Direct link to Trade-offs and downsides" title="Direct link to Trade-offs and downsides" translate="no">​</a></h2>
<p>Streaming from Blob isn't the right fit for every model. Weigh these before adopting it:</p>
<ul>
<li class=""><strong>Every cold start re-streams the full weights.</strong> The streamer's advantage is overlapping the network and GPU copies, which pays off most for large models on the autoscaling critical path. For a small model that already loads quickly from local disk, that win is marginal, the simpler default download path (optionally with the weights baked into your image) may be the better trade. Measure cold-start time both ways for <em>your</em> model before committing.</li>
<li class=""><strong>One upload per model, up front.</strong> Every new model, and every new <em>version</em> of a model, has to be pushed to Blob with the upload Job before it can be served. There's no way around this with this pattern: the streamer reads from Blob, so the weights have to land in Blob first.</li>
<li class=""><strong>Safetensors only.</strong> The RunAI Model Streamer loads weights in <a href="https://github.com/huggingface/safetensors" target="_blank" rel="noopener noreferrer" class="">Safetensors</a> format. A model distributed only as PyTorch pickle (<code>.bin</code> / <code>.pt</code>) or GGUF has to be converted first, or it won't load through <code>runai_streamer</code>. <code>microsoft/phi-4</code> ships as Safetensors, so the walkthrough above works as-is.</li>
<li class=""><strong>You now own a second copy of the weights.</strong> The model lives in your premium Blob account in addition to its upstream source on HuggingFace. That's an ongoing storage cost, premium block blob isn't cheap at tens of gigabytes per model, and it's on you to re-run the upload when the upstream model changes, or your served copy quietly goes stale.</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>Cold-start time is the tax you pay every time an inference replica scales up, and the default download-then-load path pays it twice, once to disk, once to the GPU. Streaming weights from Azure Blob with the RunAI Model Streamer collapses that into a single overlapped copy, and now that vLLM and runai-model-streamer speak <code>az://</code> natively, it takes nothing more than <code>--load-format runai_streamer</code>, one environment variable, and a workload-identity binding on a stock <code>vllm/vllm-openai</code> image.</p>
<p>The pieces reinforce each other: a managed GPU node pool removes the operator toil, workload identity removes the secrets, and an in-cluster upload Job moves the weights over Azure's backbone instead of your uplink. The result is faster, cheaper cold starts when you're autoscaling, exactly when it matters most.</p>
<p>If you're serving large models on AKS and every cold start sits on the critical path, this is worth adopting. If your model is small and already loads quickly from disk, measure both paths first, the simpler default may still win. Either way, the building blocks here are reusable: swap in your own model, point the Job at it once, and let the streamer do the rest.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cleanup">Cleanup<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#cleanup" class="hash-link" aria-label="Direct link to Cleanup" title="Direct link to Cleanup" translate="no">​</a></h2>
<p>The GPU VM is the expensive part, delete the resource group when you're done to stop the meter on everything in one shot:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az group delete --name "${AZURE_RESOURCE_GROUP}" --yes --no-wait</span><br></div></code></pre></div></div>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="references">References<a href="https://blog.aks.azure.com/2026/07/13/runai-streamer-vllm#references" class="hash-link" aria-label="Direct link to References" title="Direct link to References" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://github.com/Azure-Samples/aks-samples/tree/master/runai-model-streamer" target="_blank" rel="noopener noreferrer" class="">Azure-Samples/aks-samples: runai-model-streamer scripts and manifests</a></li>
<li class=""><a href="https://github.com/vllm-project/vllm/pull/34614" target="_blank" rel="noopener noreferrer" class="">vLLM #34614: Azure Blob support in the RunAI streamer loader</a></li>
<li class=""><a href="https://github.com/run-ai/runai-model-streamer/pull/116" target="_blank" rel="noopener noreferrer" class="">runai-model-streamer #116: <code>az://</code> scheme support</a></li>
<li class=""><a href="https://learn.microsoft.com/azure/aks/aks-managed-gpu-nodes" target="_blank" rel="noopener noreferrer" class="">AKS-managed GPU node pools (preview)</a></li>
<li class=""><a href="https://learn.microsoft.com/azure/aks/workload-identity-overview" target="_blank" rel="noopener noreferrer" class="">Azure Workload Identity on AKS</a></li>
<li class=""><a href="https://docs.vllm.ai/en/latest/models/extensions/runai_model_streamer.html" target="_blank" rel="noopener noreferrer" class="">vLLM RunAI Model Streamer docs</a></li>
</ul>]]></content:encoded>
            <category>AI Inference</category>
            <category>GPU</category>
            <category>Storage</category>
            <category>Workload Identity</category>
        </item>
        <item>
            <title><![CDATA[Routing agent traffic is really three decisions]]></title>
            <link>https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks</link>
            <guid>https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks</guid>
            <pubDate>Mon, 29 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Agents make many LLM calls in loops, so routing each to the right model — cheap self-hosted or frontier — keeps them affordable. Three decisions on AKS.]]></description>
            <content:encoded><![CDATA[<p>A chat turn is one LLM call. An agent is hundreds — a reasoning loop that plans, calls a tool, reads the result, re-plans, and only sometimes stops. The cost and latency you signed up for in a demo get multiplied by that loop, and most of those calls are <em>easy</em> — a tool-argument fill, a yes/no gate, a short summary — that never needed a frontier model. So the question isn't "which model is best." It's "which model should answer <em>this specific call</em>, and how do I govern a flood of them?" That's not one decision; it's three, on different signals.</p>
<p>This post wires up those three decisions on AKS as a single OpenAI-compatible endpoint: a trivial agent step lands on a cheap self-hosted model (KAITO + vLLM, placed by GPU state) while a hard step escalates to a frontier model on Azure OpenAI — every call authenticated, metered, and traced. We lean on managed Azure throughout (<strong>KAITO</strong> for serving, <strong>Azure OpenAI</strong> for the frontier path, <strong>Azure Managed Prometheus and Grafana</strong> for observability); the two layers without a drop-in managed service — the semantic router and the AI gateway — run as open source on the cluster.</p>
<p><img decoding="async" loading="lazy" alt="Title card: &amp;quot;Routing agent traffic is really three decisions.&amp;quot; Three chips read content, then policy, then GPU state — the through-line of the post — over the stack RouteLLM, agentgateway, Gateway API Inference Extension, and KAITO + vLLM on AKS." src="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTIwMCA0ODAiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgcm9sZT0iaW1nIiBhcmlhLWxhYmVsPSJUaGUgdGhyZWUgcm91dGluZyBkZWNpc2lvbnMgZm9yIGFnZW50IHRyYWZmaWM6IGNvbnRlbnQsIHRoZW4gcG9saWN5LCB0aGVuIEdQVSBzdGF0ZSwgb3ZlciB0aGUgc3RhY2sgUm91dGVMTE0sIGFnZW50Z2F0ZXdheSwgR2F0ZXdheSBBUEkgSW5mZXJlbmNlIEV4dGVuc2lvbiwgYW5kIEtBSVRPIHdpdGggdkxMTSBvbiBBS1MuIj4KICA8ZGVmcz4KICAgIDxtYXJrZXIgaWQ9Imh4IiBtYXJrZXJXaWR0aD0iMTAiIG1hcmtlckhlaWdodD0iMTAiIHJlZlg9IjYuNSIgcmVmWT0iMyIgb3JpZW50PSJhdXRvIiBtYXJrZXJVbml0cz0ic3Ryb2tlV2lkdGgiPgogICAgICA8cGF0aCBkPSJNMCwwIEw2LDMgTDAsNiB6IiBmaWxsPSIjOUZCNENDIi8+CiAgICA8L21hcmtlcj4KICAgIDxsaW5lYXJHcmFkaWVudCBpZD0iYmciIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIxIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjMEIxRjNBIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMC41NSIgc3RvcC1jb2xvcj0iIzBFMkM1MiIvPgogICAgICA8c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiMxMDM5NkIiLz4KICAgIDwvbGluZWFyR3JhZGllbnQ+CiAgICA8bGluZWFyR3JhZGllbnQgaWQ9ImNoaXAiIHgxPSIwIiB5MT0iMCIgeDI9IjAiIHkyPSIxIj4KICAgICAgPHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjRkZGRkZGIi8+CiAgICAgIDxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iI0VBRjJGQiIvPgogICAgPC9saW5lYXJHcmFkaWVudD4KICAgIDxzdHlsZT4KICAgICAgdGV4dCB7IGZvbnQtZmFtaWx5OiAiU2Vnb2UgVUkiLCAtYXBwbGUtc3lzdGVtLCBzeXN0ZW0tdWksICJIZWx2ZXRpY2EgTmV1ZSIsIHNhbnMtc2VyaWY7IH0KICAgICAgLmV5ZWJyb3cgeyBmaWxsOiM3RkIwRTY7IGZvbnQtd2VpZ2h0OjYwMDsgbGV0dGVyLXNwYWNpbmc6M3B4OyB9CiAgICAgIC5jdCAgeyBmaWxsOiMyNDNBNUU7IGZvbnQtd2VpZ2h0OjcwMDsgfQogICAgICAuY3MgIHsgZmlsbDojNUI2QjdCOyBmb250LXdlaWdodDo1MDA7IH0KICAgICAgLm51bSB7IGZpbGw6IzAwNzhENDsgZm9udC13ZWlnaHQ6NzAwOyB9CiAgICAgIC5zaWcgeyBmaWxsOiM0RkMzRjc7IGZvbnQtd2VpZ2h0OjYwMDsgfQogICAgPC9zdHlsZT4KICA8L2RlZnM+CgogIDwhLS0gQmFja2Ryb3AgLS0+CiAgPHJlY3Qgd2lkdGg9IjEyMDAiIGhlaWdodD0iNDgwIiBmaWxsPSJ1cmwoI2JnKSIvPgogIDwhLS0gc3VidGxlIEdQVS1ncmlkIHRleHR1cmUsIHJpZ2h0IC0tPgogIDxnIG9wYWNpdHk9IjAuMTAiIHN0cm9rZT0iIzlGRDBGRiIgc3Ryb2tlLXdpZHRoPSIxLjQiPgogICAgPGxpbmUgeDE9Ijk4MCIgeTE9Ii00MCIgeDI9IjEzMDAiIHkyPSIyODAiLz4KICAgIDxsaW5lIHgxPSIxMDUwIiB5MT0iLTQwIiB4Mj0iMTM3MCIgeTI9IjI4MCIvPgogICAgPGxpbmUgeDE9IjExMjAiIHkxPSItNDAiIHgyPSIxNDQwIiB5Mj0iMjgwIi8+CiAgICA8bGluZSB4MT0iOTAwIiB5MT0iNDAiICB4Mj0iMTMwMCIgeTI9IjQwIi8+CiAgICA8bGluZSB4MT0iOTAwIiB5MT0iMTAwIiB4Mj0iMTMyMCIgeTI9IjEwMCIvPgogIDwvZz4KCiAgPCEtLSBFeWVicm93ICsgdGhyb3VnaC1saW5lIGNhcHRpb24gLS0+CiAgPHRleHQgeD0iNjAwIiB5PSI3OCIgZm9udC1zaXplPSIyMCIgY2xhc3M9ImV5ZWJyb3ciIHRleHQtYW5jaG9yPSJtaWRkbGUiPkFHRU5UIFRSQUZGSUMgT04gQUtTPC90ZXh0PgogIDx0ZXh0IHg9IjYwMCIgeT0iMTI2IiBmb250LXNpemU9IjMwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBmaWxsPSIjRkZGRkZGIiBmb250LXdlaWdodD0iNzAwIj5Db250ZW50IDx0c3BhbiBmaWxsPSIjNUU3QkEwIj4mIzg1OTQ7PC90c3Bhbj4gUG9saWN5IDx0c3BhbiBmaWxsPSIjNUU3QkEwIj4mIzg1OTQ7PC90c3Bhbj4gR1BVIHN0YXRlPC90ZXh0PgoKICA8IS0tIFRocmVlIGNoaXBzIC0tPgogIDwhLS0gY2hpcCAxIC0tPgogIDxnPgogICAgPHJlY3QgeD0iODAiIHk9IjE4NiIgd2lkdGg9IjMyMCIgaGVpZ2h0PSIxNTAiIHJ4PSIxOCIgZmlsbD0idXJsKCNjaGlwKSIgc3Ryb2tlPSIjQ0ZFMEYyIiBzdHJva2Utd2lkdGg9IjEuNSIvPgogICAgPGNpcmNsZSBjeD0iMTIwIiBjeT0iMjI0IiByPSIxNyIgZmlsbD0iI0U2RjFGQyIvPgogICAgPHRleHQgeD0iMTIwIiB5PSIyMzAiIGZvbnQtc2l6ZT0iMTgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGNsYXNzPSJudW0iPjE8L3RleHQ+CiAgICA8dGV4dCB4PSIxNTIiIHk9IjIzMCIgZm9udC1zaXplPSIyMiIgY2xhc3M9ImN0Ij5Db250ZW50PC90ZXh0PgogICAgPHRleHQgeD0iMTA2IiB5PSIyNzAiIGZvbnQtc2l6ZT0iMTUiIGNsYXNzPSJjcyI+V2hpY2ggbW9kZWwgc2hvdWxkPC90ZXh0PgogICAgPHRleHQgeD0iMTA2IiB5PSIyOTIiIGZvbnQtc2l6ZT0iMTUiIGNsYXNzPSJjcyI+YW5zd2VyIHRoaXMgY2FsbD88L3RleHQ+CiAgICA8dGV4dCB4PSIxMDYiIHk9IjMxOCIgZm9udC1zaXplPSIxMyIgZmlsbD0iIzAwNzhENCIgZm9udC13ZWlnaHQ9IjYwMCI+c2VtYW50aWMgcm91dGVyPC90ZXh0PgogIDwvZz4KICA8bGluZSB4MT0iNDEyIiB5MT0iMjYxIiB4Mj0iNDU2IiB5Mj0iMjYxIiBzdHJva2U9IiM5RkI0Q0MiIHN0cm9rZS13aWR0aD0iMi40IiBtYXJrZXItZW5kPSJ1cmwoI2h4KSIvPgogIDwhLS0gY2hpcCAyIC0tPgogIDxnPgogICAgPHJlY3QgeD0iNDcwIiB5PSIxODYiIHdpZHRoPSIzMjAiIGhlaWdodD0iMTUwIiByeD0iMTgiIGZpbGw9InVybCgjY2hpcCkiIHN0cm9rZT0iI0NGRTBGMiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICAgIDxjaXJjbGUgY3g9IjUxMCIgY3k9IjIyNCIgcj0iMTciIGZpbGw9IiNFNkYxRkMiLz4KICAgIDx0ZXh0IHg9IjUxMCIgeT0iMjMwIiBmb250LXNpemU9IjE4IiB0ZXh0LWFuY2hvcj0ibWlkZGxlIiBjbGFzcz0ibnVtIj4yPC90ZXh0PgogICAgPHRleHQgeD0iNTQyIiB5PSIyMzAiIGZvbnQtc2l6ZT0iMjIiIGNsYXNzPSJjdCI+UG9saWN5PC90ZXh0PgogICAgPHRleHQgeD0iNDk2IiB5PSIyNzAiIGZvbnQtc2l6ZT0iMTUiIGNsYXNzPSJjcyI+QXV0aCwgYnVkZ2V0cywgbGltaXRzLDwvdGV4dD4KICAgIDx0ZXh0IHg9IjQ5NiIgeT0iMjkyIiBmb250LXNpemU9IjE1IiBjbGFzcz0iY3MiPmd1YXJkcmFpbHMsIHRyYWNpbmcuPC90ZXh0PgogICAgPHRleHQgeD0iNDk2IiB5PSIzMTgiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiMwMDc4RDQiIGZvbnQtd2VpZ2h0PSI2MDAiPkFJIGdhdGV3YXk8L3RleHQ+CiAgPC9nPgogIDxsaW5lIHgxPSI4MDIiIHkxPSIyNjEiIHgyPSI4NDYiIHkyPSIyNjEiIHN0cm9rZT0iIzlGQjRDQyIgc3Ryb2tlLXdpZHRoPSIyLjQiIG1hcmtlci1lbmQ9InVybCgjaHgpIi8+CiAgPCEtLSBjaGlwIDMgLS0+CiAgPGc+CiAgICA8cmVjdCB4PSI4NjAiIHk9IjE4NiIgd2lkdGg9IjMyMCIgaGVpZ2h0PSIxNTAiIHJ4PSIxOCIgZmlsbD0idXJsKCNjaGlwKSIgc3Ryb2tlPSIjQ0ZFMEYyIiBzdHJva2Utd2lkdGg9IjEuNSIvPgogICAgPGNpcmNsZSBjeD0iOTAwIiBjeT0iMjI0IiByPSIxNyIgZmlsbD0iI0U2RjFGQyIvPgogICAgPHRleHQgeD0iOTAwIiB5PSIyMzAiIGZvbnQtc2l6ZT0iMTgiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGNsYXNzPSJudW0iPjM8L3RleHQ+CiAgICA8dGV4dCB4PSI5MzIiIHk9IjIzMCIgZm9udC1zaXplPSIyMiIgY2xhc3M9ImN0Ij5HUFUgc3RhdGU8L3RleHQ+CiAgICA8dGV4dCB4PSI4ODYiIHk9IjI3MCIgZm9udC1zaXplPSIxNSIgY2xhc3M9ImNzIj5XaGljaCByZXBsaWNhIHRha2VzIGl0LDwvdGV4dD4KICAgIDx0ZXh0IHg9Ijg4NiIgeT0iMjkyIiBmb250LXNpemU9IjE1IiBjbGFzcz0iY3MiPmJ5IGxpdmUgbG9hZD88L3RleHQ+CiAgICA8dGV4dCB4PSI4ODYiIHk9IjMxOCIgZm9udC1zaXplPSIxMyIgZmlsbD0iIzAwNzhENCIgZm9udC13ZWlnaHQ9IjYwMCI+aW5mZXJlbmNlIExCPC90ZXh0PgogIDwvZz4KCiAgPCEtLSBmb290ZXIgd29yZG1hcmsgLS0+CiAgPHRleHQgeD0iNjAwIiB5PSI0MDQiIGZvbnQtc2l6ZT0iMTUiIGZpbGw9IiM3RTk5QkIiIGZvbnQtd2VpZ2h0PSI1MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPlJvdXRlTExNICYjMTgzOyBhZ2VudGdhdGV3YXkgJiMxODM7IEdhdGV3YXkgQVBJIEluZmVyZW5jZSBFeHRlbnNpb24gJiMxODM7IEtBSVRPICsgdkxMTTwvdGV4dD4KPC9zdmc+Cg==" width="1200" height="480" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-three-decisions">The three decisions<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#the-three-decisions" class="hash-link" aria-label="Direct link to The three decisions" title="Direct link to The three decisions" translate="no">​</a></h2>
<p>"Route this request" hides three separate questions, each reading a different signal, each with a different owner. For an agent firing calls in a loop, the first two fire on every call and the third on every weak-path call:</p>
<ol>
<li class=""><strong>Which model should answer this call?</strong> A multi-step reasoning hop deserves a frontier model; "extract the date from this string" does not. Answering means reading the request <em>content</em> and predicting answer quality. This is <strong>semantic routing</strong>, and we'll use <strong><a href="https://github.com/lm-sys/RouteLLM" target="_blank" rel="noopener noreferrer" class="">RouteLLM</a></strong>.</li>
<li class=""><strong>How do I expose, secure, govern, and forward this traffic?</strong> Provider abstraction, auth, per-agent budgets, rate limits, guardrails, observability. None of that cares about prompt meaning — it's a <em>policy</em> job for a data plane. We'll use <strong><a href="https://agentgateway.dev/docs/" target="_blank" rel="noopener noreferrer" class="">agentgateway</a></strong>, an OpenAI-compatible, agent-native proxy, in the cluster.</li>
<li class=""><strong>Which replica of the chosen model gets this call?</strong> Across a fleet of GPU pods the right target depends on KV-cache occupancy, queue depth, and which LoRA adapters are already resident — live <em>GPU state</em>. That's the <strong><a href="https://gateway-api-inference-extension.sigs.k8s.io/" target="_blank" rel="noopener noreferrer" class="">Gateway API Inference Extension</a></strong> and its Endpoint Picker.</li>
</ol>
<p><strong>Content → policy → GPU state.</strong> That's the through-line, and the discipline is refusing to let any layer answer a question that belongs to another: the router never thinks about auth, the gateway never thinks about prompt difficulty, the Endpoint Picker never thinks about <em>which</em> model — only <em>which replica</em>.</p>
<p><img decoding="async" loading="lazy" alt="Structure and ownership of the three-layer agent-traffic routing design on AKS: RouteLLM (Layer 1) and agentgateway (Layer 2) run as open source inside the AKS cluster; agentgateway calls the Inference Extension Endpoint Picker directly and forwards to KAITO-served vLLM pods (Layer 3). The gateway abstracts a managed Azure OpenAI frontier backend and the self-hosted backend, and Azure Monitor scrapes vLLM and cost metrics. Solid lines show composition and ownership, not request order." src="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgOTQwIDg1MCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcz4KICAgIDxtYXJrZXIgaWQ9ImFyIiBtYXJrZXJXaWR0aD0iOSIgbWFya2VySGVpZ2h0PSI5IiByZWZYPSI2IiByZWZZPSIzIiBvcmllbnQ9ImF1dG8iIG1hcmtlclVuaXRzPSJzdHJva2VXaWR0aCI+CiAgICAgIDxwYXRoIGQ9Ik0wLDAgTDYsMyBMMCw2IHoiIGZpbGw9IiM1QjZCN0IiLz4KICAgIDwvbWFya2VyPgogICAgPHN0eWxlPgogICAgICB0ZXh0IHsgZm9udC1mYW1pbHk6ICJTZWdvZSBVSSIsIC1hcHBsZS1zeXN0ZW0sIHN5c3RlbS11aSwgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjsgfQogICAgICAudCB7IGZpbGw6IzI0M0E1RTsgZm9udC13ZWlnaHQ6NjAwOyB9CiAgICAgIC5zIHsgZmlsbDojNUI2QjdCOyB9CiAgICAgIC5yZWwgeyBmaWxsOiM4QTk4QTY7IGZvbnQtc3R5bGU6aXRhbGljOyB9CiAgICA8L3N0eWxlPgogIDwvZGVmcz4KCiAgPHJlY3QgeD0iMiIgeT0iMiIgd2lkdGg9IjkzNiIgaGVpZ2h0PSI4NDYiIHJ4PSIxNCIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSIjRTJFOEYwIiBzdHJva2Utd2lkdGg9IjEuNSIvPgoKICA8IS0tIFRpdGxlIC0tPgogIDx0ZXh0IHg9IjM2IiB5PSI0MCIgZm9udC1zaXplPSIyMSIgY2xhc3M9InQiPlJvdXRpbmcgYWdlbnQgdHJhZmZpYyBvbiBBS1Mgwrcgc3RydWN0dXJlICZhbXA7IG93bmVyc2hpcDwvdGV4dD4KICA8dGV4dCB4PSIzNiIgeT0iNjIiIGZvbnQtc2l6ZT0iMTIiIGNsYXNzPSJzIj5ob3cgdGhlIGxheWVycyBjb21wb3NlIGFuZCB3aG8gb3ducyBlYWNoIOKAlCBtYW5hZ2VkIEF6dXJlIHZzLiBvcGVuIHNvdXJjZSBvbiBBS1M8L3RleHQ+CgogIDwhLS0gTGVnZW5kIC0tPgogIDxyZWN0IHg9IjY0OCIgeT0iMjgiIHdpZHRoPSIxNiIgaGVpZ2h0PSIxNiIgcng9IjMiIGZpbGw9IiMwMDc4RDQiLz4KICA8dGV4dCB4PSI2NzAiIHk9IjQxIiBmb250LXNpemU9IjExIiBjbGFzcz0idCIgZm9udC13ZWlnaHQ9IjQwMCI+TWFuYWdlZCBBenVyZTwvdGV4dD4KICA8cmVjdCB4PSI2NDgiIHk9IjUwIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHJ4PSIzIiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9IiM2NDc0OEIiIHN0cm9rZS13aWR0aD0iMS41Ii8+CiAgPHRleHQgeD0iNjcwIiB5PSI2MyIgZm9udC1zaXplPSIxMSIgY2xhc3M9InQiIGZvbnQtd2VpZ2h0PSI0MDAiPk9wZW4gc291cmNlIG9uIEFLUzwvdGV4dD4KCiAgPCEtLSBDbGllbnQgLS0+CiAgPHJlY3QgeD0iMzQwIiB5PSI4NCIgd2lkdGg9IjIwMCIgaGVpZ2h0PSI0NiIgcng9IjEwIiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9IiNDOUQ2RTIiIHN0cm9rZS13aWR0aD0iMS4zIi8+CiAgPHJlY3QgeD0iMzUyIiB5PSI5NyIgd2lkdGg9IjE4IiBoZWlnaHQ9IjE4IiByeD0iNCIgZmlsbD0iIzY0NzQ4QiIvPgogIDx0ZXh0IHg9IjM3OCIgeT0iMTA1IiBmb250LXNpemU9IjEzIiBjbGFzcz0idCI+QWdlbnQgLyBhcHA8L3RleHQ+CiAgPHRleHQgeD0iMzc4IiB5PSIxMjEiIGZvbnQtc2l6ZT0iMTAiIGNsYXNzPSJzIj5tYW55IGNhbGxzIHBlciB0YXNrIMK3IGxvb3AgJmFtcDsgdG9vbHM8L3RleHQ+CgogIDwhLS0gY29tcG9zaXRpb24gbGluZTogY2xpZW50IHNpdHMgYWJvdmUgTGF5ZXIgMSAobm8gYXJyb3doZWFkIOKAlCB0aGlzIG1hcCBpcyBub3QgYSBmbG93KSAtLT4KICA8bGluZSB4MT0iNDQwIiB5MT0iMTMwIiB4Mj0iNDQwIiB5Mj0iMjAwIiBzdHJva2U9IiNDMkNDRDYiIHN0cm9rZS13aWR0aD0iMS41Ii8+CgogIDwhLS0gQUtTIGNsdXN0ZXIgYm91bmRhcnkgLS0+CiAgPHJlY3QgeD0iNDAiIHk9IjE1MCIgd2lkdGg9IjYyMCIgaGVpZ2h0PSI2NTYiIHJ4PSIxNCIgZmlsbD0iI0Y1RkFGRSIgc3Ryb2tlPSIjMDA3OEQ0IiBzdHJva2Utd2lkdGg9IjEuNCIgc3Ryb2tlLWRhc2hhcnJheT0iNyA1Ii8+CiAgPHJlY3QgeD0iNTIiIHk9IjE2MCIgd2lkdGg9IjE2IiBoZWlnaHQ9IjE2IiByeD0iNCIgZmlsbD0iIzAwNzhENCIvPgogIDx0ZXh0IHg9Ijc2IiB5PSIxNzMiIGZvbnQtc2l6ZT0iMTIuNSIgY2xhc3M9InQiPkF6dXJlIEt1YmVybmV0ZXMgU2VydmljZSAoQUtTKSBjbHVzdGVyPC90ZXh0PgoKICA8IS0tID09PT09PT09PT09PSBMYXllciAxID09PT09PT09PT09PSAtLT4KICA8cmVjdCB4PSI1OCIgeT0iMTkwIiB3aWR0aD0iNTkyIiBoZWlnaHQ9Ijk0IiByeD0iMTAiIGZpbGw9IiNFQUYzRkIiLz4KICA8Y2lyY2xlIGN4PSI5NiIgY3k9IjIzOCIgcj0iMTUiIGZpbGw9IiMwMDc4RDQiLz4KICA8dGV4dCB4PSI5NiIgeT0iMjQzIiBmb250LXNpemU9IjE1IiBmaWxsPSIjZmZmZmZmIiBmb250LXdlaWdodD0iNzAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj4xPC90ZXh0PgogIDx0ZXh0IHg9IjExOCIgeT0iMjMyIiBmb250LXNpemU9IjEyIiBjbGFzcz0idCI+TGF5ZXIgMSDigJQgU2VtYW50aWMgZGVjaXNpb248L3RleHQ+CiAgPHRleHQgeD0iMTE4IiB5PSIyNTAiIGZvbnQtc2l6ZT0iMTAuNSIgY2xhc3M9InMiPm93bnM6IHdoaWNoIG1vZGVsIMK3IHNpZ25hbDogY29udGVudDwvdGV4dD4KCiAgPHJlY3QgeD0iMzMwIiB5PSIyMDAiIHdpZHRoPSIyODAiIGhlaWdodD0iNzYiIHJ4PSIxMCIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSIjQzlENkUyIiBzdHJva2Utd2lkdGg9IjEuMyIvPgogIDxyZWN0IHg9IjM0MiIgeT0iMjE0IiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHJ4PSI0IiBmaWxsPSIjNjQ3NDhCIi8+CiAgPHRleHQgeD0iMzcwIiB5PSIyMjYiIGZvbnQtc2l6ZT0iMTQiIGNsYXNzPSJ0Ij5Sb3V0ZUxMTTwvdGV4dD4KICA8dGV4dCB4PSIzNzAiIHk9IjI0NCIgZm9udC1zaXplPSIxMC41IiBjbGFzcz0icyI+bWF0cml4LWZhY3Rvcml6YXRpb24gcm91dGVyPC90ZXh0PgogIDx0ZXh0IHg9IjM3MCIgeT0iMjYxIiBmb250LXNpemU9IjEwIiBmaWxsPSIjMDA3OEQ0Ij5waWNrcyB0aGUgbW9kZWwgY2xhc3M6IHN0cm9uZyB8IHdlYWs8L3RleHQ+CiAgPHJlY3QgeD0iNTU4IiB5PSIyMDgiIHdpZHRoPSI0NCIgaGVpZ2h0PSIxNyIgcng9IjgiIGZpbGw9IiNGMUY0RjciIHN0cm9rZT0iI0NCRDVERiIvPgogIDx0ZXh0IHg9IjU4MCIgeT0iMjIwIiBmb250LXNpemU9IjkuNSIgY2xhc3M9InMiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk9TUzwvdGV4dD4KCiAgPCEtLSBjb21wb3NpdGlvbiBsaW5lIEwx4oaUTDIgLS0+CiAgPGxpbmUgeDE9IjQ3MCIgeTE9IjI3NiIgeDI9IjQ3MCIgeTI9IjMwOCIgc3Ryb2tlPSIjQzJDQ0Q2IiBzdHJva2Utd2lkdGg9IjEuNSIvPgoKICA8IS0tID09PT09PT09PT09PSBMYXllciAyID09PT09PT09PT09PSAtLT4KICA8cmVjdCB4PSI1OCIgeT0iMjk4IiB3aWR0aD0iNTkyIiBoZWlnaHQ9Ijk0IiByeD0iMTAiIGZpbGw9IiNFQUYzRkIiLz4KICA8Y2lyY2xlIGN4PSI5NiIgY3k9IjM0NiIgcj0iMTUiIGZpbGw9IiMwMDc4RDQiLz4KICA8dGV4dCB4PSI5NiIgeT0iMzUxIiBmb250LXNpemU9IjE1IiBmaWxsPSIjZmZmZmZmIiBmb250LXdlaWdodD0iNzAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj4yPC90ZXh0PgogIDx0ZXh0IHg9IjExOCIgeT0iMzQwIiBmb250LXNpemU9IjEyIiBjbGFzcz0idCI+TGF5ZXIgMiDigJQgQUkgZ2F0ZXdheTwvdGV4dD4KICA8dGV4dCB4PSIxMTgiIHk9IjM1OCIgZm9udC1zaXplPSIxMC41IiBjbGFzcz0icyI+b3duczogYXV0aCDCtyBjb3N0IMK3IGd1YXJkcmFpbHMgwrcgc2lnbmFsOiBwb2xpY3k8L3RleHQ+CgogIDxyZWN0IHg9IjMzMCIgeT0iMzA4IiB3aWR0aD0iMjgwIiBoZWlnaHQ9Ijc2IiByeD0iMTAiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iI0M5RDZFMiIgc3Ryb2tlLXdpZHRoPSIxLjMiLz4KICA8cmVjdCB4PSIzNDIiIHk9IjMyMiIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIiByeD0iNCIgZmlsbD0iIzY0NzQ4QiIvPgogIDx0ZXh0IHg9IjM3MCIgeT0iMzM0IiBmb250LXNpemU9IjE0IiBjbGFzcz0idCI+YWdlbnRnYXRld2F5PC90ZXh0PgogIDx0ZXh0IHg9IjM3MCIgeT0iMzUyIiBmb250LXNpemU9IjEwLjUiIGNsYXNzPSJzIj5PcGVuQUktY29tcGF0aWJsZSBkYXRhIHBsYW5lPC90ZXh0PgogIDx0ZXh0IHg9IjM3MCIgeT0iMzY5IiBmb250LXNpemU9IjEwIiBmaWxsPSIjMDA3OEQ0Ij5hYnN0cmFjdHMgYm90aCBiYWNrZW5kcyBiZWhpbmQgb25lIEFQSTwvdGV4dD4KICA8cmVjdCB4PSI1NTgiIHk9IjMxNiIgd2lkdGg9IjQ0IiBoZWlnaHQ9IjE3IiByeD0iOCIgZmlsbD0iI0YxRjRGNyIgc3Ryb2tlPSIjQ0JENURGIi8+CiAgPHRleHQgeD0iNTgwIiB5PSIzMjgiIGZvbnQtc2l6ZT0iOS41IiBjbGFzcz0icyIgdGV4dC1hbmNob3I9Im1pZGRsZSI+T1NTPC90ZXh0PgoKICA8IS0tIHN0cnVjdHVyYWwgbGluazogZ2F0ZXdheSBhYnN0cmFjdHMgdGhlIGZyb250aWVyIGJhY2tlbmQgKG1hbmFnZWQsIGV4dGVybmFsKSAtLT4KICA8bGluZSB4MT0iNjEwIiB5MT0iMzQ2IiB4Mj0iNjg2IiB5Mj0iMzUyIiBzdHJva2U9IiMwMDc4RDQiIHN0cm9rZS13aWR0aD0iMS40Ii8+CiAgPHRleHQgeD0iNjE2IiB5PSIzMzgiIGZvbnQtc2l6ZT0iOS41IiBjbGFzcz0icmVsIj5mcm9udGllciBiYWNrZW5kPC90ZXh0PgoKICA8IS0tIHN0cnVjdHVyYWwgbGluazogZ2F0ZXdheSBhYnN0cmFjdHMgdGhlIHNlbGYtaG9zdGVkIGJhY2tlbmQgKExheWVyIDMpIC0tPgogIDxsaW5lIHgxPSI0NzAiIHkxPSIzODQiIHgyPSI0NzAiIHkyPSI0MTIiIHN0cm9rZT0iI0MyQ0NENiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICA8dGV4dCB4PSI0ODIiIHk9IjQwMyIgZm9udC1zaXplPSI5LjUiIGNsYXNzPSJyZWwiPmNhbGxzIChleHQtcHJvYyk8L3RleHQ+CgogIDwhLS0gPT09PT09PT09PT09IExheWVyIDMgPT09PT09PT09PT09IC0tPgogIDxyZWN0IHg9IjU4IiB5PSI0MDQiIHdpZHRoPSI1OTIiIGhlaWdodD0iMzkyIiByeD0iMTAiIGZpbGw9IiNFQUYzRkIiLz4KICA8Y2lyY2xlIGN4PSI5NiIgY3k9IjQ0NiIgcj0iMTUiIGZpbGw9IiMwMDc4RDQiLz4KICA8dGV4dCB4PSI5NiIgeT0iNDUxIiBmb250LXNpemU9IjE1IiBmaWxsPSIjZmZmZmZmIiBmb250LXdlaWdodD0iNzAwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj4zPC90ZXh0PgogIDx0ZXh0IHg9IjExOCIgeT0iNDQwIiBmb250LXNpemU9IjEyIiBjbGFzcz0idCI+TGF5ZXIgMyDigJQgSW5mZXJlbmNlLWF3YXJlIHNlcnZpbmc8L3RleHQ+CiAgPHRleHQgeD0iMTE4IiB5PSI0NTgiIGZvbnQtc2l6ZT0iMTAuNSIgY2xhc3M9InMiPm93bnM6IHdoaWNoIHJlcGxpY2Egwrcgc2lnbmFsOiBHUFUgc3RhdGU8L3RleHQ+CgogIDwhLS0gQ2FyZCBBOiBJbmZlcmVuY2UgRXh0ZW5zaW9uIC8gRVBQIChhZ2VudGdhdGV3YXkgY2FsbHMgaXQgZGlyZWN0bHkpIC0tPgogIDxyZWN0IHg9IjMzMCIgeT0iNDE2IiB3aWR0aD0iMjgwIiBoZWlnaHQ9Ijc0IiByeD0iMTAiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iI0M5RDZFMiIgc3Ryb2tlLXdpZHRoPSIxLjMiLz4KICA8cmVjdCB4PSIzNDIiIHk9IjQzMCIgd2lkdGg9IjIwIiBoZWlnaHQ9IjIwIiByeD0iNCIgZmlsbD0iIzY0NzQ4QiIvPgogIDx0ZXh0IHg9IjM3MCIgeT0iNDQyIiBmb250LXNpemU9IjEzLjUiIGNsYXNzPSJ0Ij5JbmZlcmVuY2UgRXh0ZW5zaW9uPC90ZXh0PgogIDx0ZXh0IHg9IjM3MCIgeT0iNDYwIiBmb250LXNpemU9IjEwLjUiIGNsYXNzPSJzIj5FbmRwb2ludCBQaWNrZXIg4oCUIHBpY2tzIHBvZCBvbjo8L3RleHQ+CiAgPHRleHQgeD0iMzcwIiB5PSI0NzYiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiMwMDY3QjgiPktWLWNhY2hlIMK3IHF1ZXVlIGRlcHRoIMK3IExvUkE8L3RleHQ+CiAgPHJlY3QgeD0iNTU4IiB5PSI0MjQiIHdpZHRoPSI0NCIgaGVpZ2h0PSIxNyIgcng9IjgiIGZpbGw9IiNGMUY0RjciIHN0cm9rZT0iI0NCRDVERiIvPgogIDx0ZXh0IHg9IjU4MCIgeT0iNDM2IiBmb250LXNpemU9IjkuNSIgY2xhc3M9InMiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk9TUzwvdGV4dD4KCiAgPCEtLSBjb21wb3NpdGlvbjogdGhlIHBpY2tlciBzZWxlY3RzIGEgcmVwbGljYSBmcm9tIHRoZSBLQUlUTyBwb29sIC0tPgogIDxsaW5lIHgxPSI0NzAiIHkxPSI0OTAiIHgyPSI0NzAiIHkyPSI1ODAiIHN0cm9rZT0iI0MyQ0NENiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICA8dGV4dCB4PSI0ODIiIHk9IjUzOCIgZm9udC1zaXplPSI5LjUiIGNsYXNzPSJyZWwiPnNlbGVjdHMgcmVwbGljYSBmcm9tPC90ZXh0PgoKICA8IS0tIENhcmQgQzogS0FJVE8gLS0+CiAgPHJlY3QgeD0iMzMwIiB5PSI1ODAiIHdpZHRoPSIyODAiIGhlaWdodD0iNjYiIHJ4PSIxMCIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSIjQzlENkUyIiBzdHJva2Utd2lkdGg9IjEuMyIvPgogIDxyZWN0IHg9IjM0MiIgeT0iNTk0IiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHJ4PSI0IiBmaWxsPSIjMDA3OEQ0Ii8+CiAgPHRleHQgeD0iMzcwIiB5PSI2MDYiIGZvbnQtc2l6ZT0iMTMuNSIgY2xhc3M9InQiPktBSVRPIFdvcmtzcGFjZTwvdGV4dD4KICA8dGV4dCB4PSIzNzAiIHk9IjYyNCIgZm9udC1zaXplPSIxMC41IiBjbGFzcz0icyI+QUkgdG9vbGNoYWluIG9wZXJhdG9yIGFkZC1vbiDCtyB2TExNPC90ZXh0PgogIDxyZWN0IHg9IjU0MCIgeT0iNTg4IiB3aWR0aD0iNjIiIGhlaWdodD0iMTciIHJ4PSI4IiBmaWxsPSIjRTZGMkZCIiBzdHJva2U9IiNCOERDRjUiLz4KICA8dGV4dCB4PSI1NzEiIHk9IjYwMCIgZm9udC1zaXplPSI5LjUiIGZpbGw9IiMwMDY3QjgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk1hbmFnZWQ8L3RleHQ+CgogIDwhLS0gY29tcG9zaXRpb246IEtBSVRPIHNlcnZlcyB0aGUgcG9kIGZsZWV0IC0tPgogIDxsaW5lIHgxPSI0NzAiIHkxPSI2NDYiIHgyPSI0NzAiIHkyPSI2NjQiIHN0cm9rZT0iI0MyQ0NENiIgc3Ryb2tlLXdpZHRoPSIxLjUiLz4KICA8dGV4dCB4PSI0ODIiIHk9IjY1OCIgZm9udC1zaXplPSI5LjUiIGNsYXNzPSJyZWwiPnNlcnZlczwvdGV4dD4KCiAgPCEtLSB2TExNIHBvZHMgLS0+CiAgPGcgZm9udC1zaXplPSIxMS41Ij4KICAgIDxyZWN0IHg9IjMzNSIgeT0iNjY2IiB3aWR0aD0iODAiIGhlaWdodD0iNDQiIHJ4PSI4IiBmaWxsPSIjRTZGMkZCIiBzdHJva2U9IiMwMDc4RDQiIHN0cm9rZS13aWR0aD0iMS4yIi8+CiAgICA8dGV4dCB4PSIzNzUiIHk9IjY5MiIgZmlsbD0iIzAwNjdCOCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+dkxMTTwvdGV4dD4KICAgIDxyZWN0IHg9IjQzMCIgeT0iNjY2IiB3aWR0aD0iODAiIGhlaWdodD0iNDQiIHJ4PSI4IiBmaWxsPSIjRTZGMkZCIiBzdHJva2U9IiMwMDc4RDQiIHN0cm9rZS13aWR0aD0iMS4yIi8+CiAgICA8dGV4dCB4PSI0NzAiIHk9IjY5MiIgZmlsbD0iIzAwNjdCOCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+dkxMTTwvdGV4dD4KICAgIDxyZWN0IHg9IjUyNSIgeT0iNjY2IiB3aWR0aD0iODAiIGhlaWdodD0iNDQiIHJ4PSI4IiBmaWxsPSIjRTZGMkZCIiBzdHJva2U9IiMwMDc4RDQiIHN0cm9rZS13aWR0aD0iMS4yIi8+CiAgICA8dGV4dCB4PSI1NjUiIHk9IjY5MiIgZmlsbD0iIzAwNjdCOCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+dkxMTTwvdGV4dD4KICA8L2c+CiAgPHRleHQgeD0iMzM1IiB5PSI3MzAiIGZvbnQtc2l6ZT0iMTAuNSIgY2xhc3M9InMiPnRoZSAid2VhayIgLyBzZWxmLWhvc3RlZCBtb2RlbCDCtyBHUFUgbm9kZSBwb29sIHByb3Zpc2lvbmVkICZhbXA7IG1hbmFnZWQgYnkgS0FJVE88L3RleHQ+CgogIDwhLS0gPT09PT09PT09PT09IEV4dGVybmFsIG1hbmFnZWQgc2VydmljZXMgKHJpZ2h0KSA9PT09PT09PT09PT0gLS0+CiAgPCEtLSBBenVyZSBPcGVuQUkgLS0+CiAgPHJlY3QgeD0iNjg4IiB5PSIzMTgiIHdpZHRoPSIyMTIiIGhlaWdodD0iNzQiIHJ4PSIxMCIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSIjQzlENkUyIiBzdHJva2Utd2lkdGg9IjEuMyIvPgogIDxyZWN0IHg9IjcwMCIgeT0iMzMyIiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHJ4PSI0IiBmaWxsPSIjMDA3OEQ0Ii8+CiAgPHRleHQgeD0iNzI4IiB5PSIzNDQiIGZvbnQtc2l6ZT0iMTQiIGNsYXNzPSJ0Ij5BenVyZSBPcGVuQUk8L3RleHQ+CiAgPHRleHQgeD0iNzI4IiB5PSIzNjIiIGZvbnQtc2l6ZT0iMTAuNSIgY2xhc3M9InMiPmdwdC01LjEgZGVwbG95bWVudDwvdGV4dD4KICA8dGV4dCB4PSI3MjgiIHk9IjM3OSIgZm9udC1zaXplPSIxMCIgZmlsbD0iIzAwNjdCOCI+dGhlICJzdHJvbmciIC8gZnJvbnRpZXIgbW9kZWw8L3RleHQ+CiAgPHJlY3QgeD0iODM2IiB5PSIzMjYiIHdpZHRoPSI1NiIgaGVpZ2h0PSIxNyIgcng9IjgiIGZpbGw9IiNFNkYyRkIiIHN0cm9rZT0iI0I4RENGNSIvPgogIDx0ZXh0IHg9Ijg2NCIgeT0iMzM4IiBmb250LXNpemU9IjkuNSIgZmlsbD0iIzAwNjdCOCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+TWFuYWdlZDwvdGV4dD4KCiAgPCEtLSBNYW5hZ2VkIG1vbml0b3JpbmcgLS0+CiAgPHJlY3QgeD0iNjg4IiB5PSI0NzAiIHdpZHRoPSIyMTIiIGhlaWdodD0iOTIiIHJ4PSIxMCIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSIjQzlENkUyIiBzdHJva2Utd2lkdGg9IjEuMyIvPgogIDxyZWN0IHg9IjcwMCIgeT0iNDg0IiB3aWR0aD0iMjAiIGhlaWdodD0iMjAiIHJ4PSI0IiBmaWxsPSIjMDA3OEQ0Ii8+CiAgPHRleHQgeD0iNzI4IiB5PSI0OTYiIGZvbnQtc2l6ZT0iMTMuNSIgY2xhc3M9InQiPkF6dXJlIE1vbml0b3I8L3RleHQ+CiAgPHRleHQgeD0iNzI4IiB5PSI1MTQiIGZvbnQtc2l6ZT0iMTAuNSIgY2xhc3M9InMiPk1hbmFnZWQgUHJvbWV0aGV1cyArIEdyYWZhbmE8L3RleHQ+CiAgPHRleHQgeD0iNzI4IiB5PSI1MzEiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiMwMDY3QjgiPnZMTE0gJmFtcDsgY29zdCBtZXRyaWNzPC90ZXh0PgogIDxyZWN0IHg9IjgzNiIgeT0iNDc4IiB3aWR0aD0iNTYiIGhlaWdodD0iMTciIHJ4PSI4IiBmaWxsPSIjRTZGMkZCIiBzdHJva2U9IiNCOERDRjUiLz4KICA8dGV4dCB4PSI4NjQiIHk9IjQ5MCIgZm9udC1zaXplPSI5LjUiIGZpbGw9IiMwMDY3QjgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPk1hbmFnZWQ8L3RleHQ+CgogIDwhLS0gbWV0cmljcyBzaWRlLWNoYW5uZWw6IGRhc2hlZCArIGFycm93LCBkZWxpYmVyYXRlbHkgZGlzdGluY3QgZnJvbSBzb2xpZCBjb21wb3NpdGlvbiBsaW5lcyAtLT4KICA8cGF0aCBkPSJNNjEwLDYxMyBDNjYwLDYxMyA2NzIsNTI4IDY4Niw1MTIiIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzlBQThCNiIgc3Ryb2tlLXdpZHRoPSIxLjMiIHN0cm9rZS1kYXNoYXJyYXk9IjUgNCIgbWFya2VyLWVuZD0idXJsKCNhcikiLz4KICA8dGV4dCB4PSI2MjgiIHk9IjU2MCIgZm9udC1zaXplPSIxMCIgY2xhc3M9InMiPnNjcmFwZXM8L3RleHQ+CgogIDwhLS0gZm9vdGVyIC0tPgogIDx0ZXh0IHg9IjQwIiB5PSI4MjgiIGZvbnQtc2l6ZT0iMTAuNSIgY2xhc3M9InMiPlNvbGlkIGxpbmVzIHNob3cgY29tcG9zaXRpb24gJmFtcDsgb3duZXJzaGlwLCBub3QgcmVxdWVzdCBvcmRlciDigJQgc2VlIHRoZSByZXF1ZXN0LXBhdGggZGlhZ3JhbSBmb3IgdGhlIHBlci1yZXF1ZXN0IHRyYWNlLiBEYXNoZWQgPSB0ZWxlbWV0cnkuPC90ZXh0Pgo8L3N2Zz4K" width="940" height="850" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-architecture-on-aks">The architecture on AKS<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#the-architecture-on-aks" class="hash-link" aria-label="Direct link to The architecture on AKS" title="Direct link to The architecture on AKS" translate="no">​</a></h2>
<p>Most slots are managed Azure; where an open-source piece has a managed alternative, it's called out in the layer that earns it:</p>
<table><thead><tr><th>Layer</th><th>Decision</th><th>Featured component</th><th>Managed on AKS?</th></tr></thead><tbody><tr><td>1 — Semantic</td><td>which <em>model</em> answers</td><td>RouteLLM</td><td>Open source on AKS</td></tr><tr><td>2 — AI gateway</td><td>auth, cost, guardrails, provider abstraction, inference-aware front door</td><td>agentgateway</td><td>Open source on AKS</td></tr><tr><td>3 — Inference LB</td><td>which <em>replica</em></td><td>Gateway API Inference Extension (Endpoint Picker)</td><td>Open source, called by agentgateway</td></tr><tr><td>3 — Serving</td><td>run the model on GPUs</td><td><strong>KAITO</strong> add-on → vLLM</td><td>Managed add-on</td></tr><tr><td>Frontier path</td><td>the "strong" model</td><td><strong>Azure OpenAI</strong></td><td>Managed</td></tr><tr><td>Observability</td><td>metrics &amp; dashboards</td><td><strong>Azure Managed Prometheus + Grafana</strong></td><td>Managed</td></tr></tbody></table>
<p><img decoding="async" loading="lazy" alt="Per-request trace through the three layers, numbered in order: an agent call (step 1) enters RouteLLM, which reads the prompt and picks strong or weak (step 2); agentgateway dispatches the strong path straight to Azure OpenAI (step 3a, ends there) or, on the weak path (step 3b), calls the Inference Extension Endpoint Picker over ext-proc (step 4), which selects one KAITO-served vLLM pod by GPU state that agentgateway then forwards to (step 5). The strong path ends at step 3a; the weak path ends at step 5." src="data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgODIwIDYyMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KICA8ZGVmcz4KICAgIDxtYXJrZXIgaWQ9ImFyIiBtYXJrZXJXaWR0aD0iOSIgbWFya2VySGVpZ2h0PSI5IiByZWZYPSI2IiByZWZZPSIzIiBvcmllbnQ9ImF1dG8iIG1hcmtlclVuaXRzPSJzdHJva2VXaWR0aCI+CiAgICAgIDxwYXRoIGQ9Ik0wLDAgTDYsMyBMMCw2IHoiIGZpbGw9IiM1QjZCN0IiLz4KICAgIDwvbWFya2VyPgogICAgPG1hcmtlciBpZD0iYXJCIiBtYXJrZXJXaWR0aD0iOSIgbWFya2VySGVpZ2h0PSI5IiByZWZYPSI2IiByZWZZPSIzIiBvcmllbnQ9ImF1dG8iIG1hcmtlclVuaXRzPSJzdHJva2VXaWR0aCI+CiAgICAgIDxwYXRoIGQ9Ik0wLDAgTDYsMyBMMCw2IHoiIGZpbGw9IiMwMDc4RDQiLz4KICAgIDwvbWFya2VyPgogICAgPHN0eWxlPgogICAgICB0ZXh0IHsgZm9udC1mYW1pbHk6ICJTZWdvZSBVSSIsIC1hcHBsZS1zeXN0ZW0sIHN5c3RlbS11aSwgIkhlbHZldGljYSBOZXVlIiwgc2Fucy1zZXJpZjsgfQogICAgICAudCB7IGZpbGw6IzI0M0E1RTsgZm9udC13ZWlnaHQ6NjAwOyB9CiAgICAgIC5zIHsgZmlsbDojNUI2QjdCOyB9CiAgICAgIC5zdGVwIHsgZmlsbDojNUI2QjdCOyBmb250LXdlaWdodDo3MDA7IH0KICAgIDwvc3R5bGU+CiAgPC9kZWZzPgoKICA8cmVjdCB4PSIyIiB5PSIyIiB3aWR0aD0iODE2IiBoZWlnaHQ9IjYxNiIgcng9IjE0IiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9IiNFMkU4RjAiIHN0cm9rZS13aWR0aD0iMS41Ii8+CgogIDx0ZXh0IHg9IjMyIiB5PSI0MCIgZm9udC1zaXplPSIyMCIgY2xhc3M9InQiPkFnZW50IGNhbGwgwrcgb25lIHJlcXVlc3QsIGluIG9yZGVyPC90ZXh0PgogIDx0ZXh0IHg9IjMyIiB5PSI1OSIgZm9udC1zaXplPSIxMiIgY2xhc3M9InMiPnRoZSBwZXItY2FsbCB0cmFjZSB0aHJvdWdoIHRoZSB0aHJlZSBsYXllcnMg4oCUIGZvciB3aG8tb3ducy13aGF0IGFuZCBtYW5hZ2VkIHZzLiBPU1MsIHNlZSB0aGUgc3RydWN0dXJlIGRpYWdyYW08L3RleHQ+CgogIDwhLS0gc3RlcC1iYWRnZSBoZWxwZXIgbG9vazogc21hbGwgY2lyY2xlcyBvbiB0aGUgZmxvdyBhcnJvd3MgLS0+CgogIDwhLS0gQ2xpZW50IC0tPgogIDxyZWN0IHg9IjEzMCIgeT0iODAiIHdpZHRoPSIyODAiIGhlaWdodD0iNDgiIHJ4PSIxMCIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSIjQzlENkUyIiBzdHJva2Utd2lkdGg9IjEuMyIvPgogIDxyZWN0IHg9IjE0MiIgeT0iOTQiIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgcng9IjQiIGZpbGw9IiM2NDc0OEIiLz4KICA8dGV4dCB4PSIxNzAiIHk9IjEwOSIgZm9udC1zaXplPSIxMy41IiBjbGFzcz0idCI+QWdlbnQgLyBhcHA8L3RleHQ+CgogIDwhLS0gZmxvdyAxIC0tPgogIDxsaW5lIHgxPSIyNzAiIHkxPSIxMjgiIHgyPSIyNzAiIHkyPSIxNjAiIHN0cm9rZT0iIzVCNkI3QiIgc3Ryb2tlLXdpZHRoPSIxLjUiIG1hcmtlci1lbmQ9InVybCgjYXIpIi8+CiAgPGNpcmNsZSBjeD0iMjQ4IiBjeT0iMTQ0IiByPSI5IiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9IiM1QjZCN0IiIHN0cm9rZS13aWR0aD0iMS4zIi8+CiAgPHRleHQgeD0iMjQ4IiB5PSIxNDgiIGZvbnQtc2l6ZT0iMTAiIGNsYXNzPSJzdGVwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj4xPC90ZXh0PgogIDx0ZXh0IHg9IjI4OCIgeT0iMTQwIiBmb250LXNpemU9IjEwIiBjbGFzcz0icyI+UE9TVCAvdjEvY2hhdC9jb21wbGV0aW9uczwvdGV4dD4KICA8dGV4dCB4PSIyODgiIHk9IjE1MyIgZm9udC1zaXplPSIxMCIgY2xhc3M9InMiPm1vZGVsOiAicm91dGVyLW1mLTAuMTE1OTMiPC90ZXh0PgoKICA8IS0tIExheWVyIDE6IFJvdXRlTExNIC0tPgogIDxjaXJjbGUgY3g9IjEwMCIgY3k9IjE5NiIgcj0iMTMiIGZpbGw9IiMwMDc4RDQiLz4KICA8dGV4dCB4PSIxMDAiIHk9IjIwMSIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2ZmZmZmZiIgZm9udC13ZWlnaHQ9IjcwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+MTwvdGV4dD4KICA8cmVjdCB4PSIxMzAiIHk9IjE2MCIgd2lkdGg9IjI4MCIgaGVpZ2h0PSI3MiIgcng9IjEwIiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9IiNDOUQ2RTIiIHN0cm9rZS13aWR0aD0iMS4zIi8+CiAgPHJlY3QgeD0iMTQyIiB5PSIxNzYiIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgcng9IjQiIGZpbGw9IiM2NDc0OEIiLz4KICA8dGV4dCB4PSIxNzAiIHk9IjE4OCIgZm9udC1zaXplPSIxMy41IiBjbGFzcz0idCI+Um91dGVMTE08L3RleHQ+CiAgPHRleHQgeD0iMTcwIiB5PSIyMDYiIGZvbnQtc2l6ZT0iMTAuNSIgY2xhc3M9InMiPnNlbWFudGljIGRlY2lzaW9uIChtYXRyaXgtZmFjdG9yaXphdGlvbik8L3RleHQ+CiAgPHRleHQgeD0iMTcwIiB5PSIyMjMiIGZvbnQtc2l6ZT0iMTAuNSIgZmlsbD0iIzAwNzhENCI+cmVhZHMgcHJvbXB0IOKGkiBwaWNrcyBzdHJvbmcgfCB3ZWFrPC90ZXh0PgoKICA8IS0tIGZsb3cgMiAtLT4KICA8bGluZSB4MT0iMjcwIiB5MT0iMjMyIiB4Mj0iMjcwIiB5Mj0iMjYyIiBzdHJva2U9IiM1QjZCN0IiIHN0cm9rZS13aWR0aD0iMS41IiBtYXJrZXItZW5kPSJ1cmwoI2FyKSIvPgogIDxjaXJjbGUgY3g9IjI0OCIgY3k9IjI0NyIgcj0iOSIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSIjNUI2QjdCIiBzdHJva2Utd2lkdGg9IjEuMyIvPgogIDx0ZXh0IHg9IjI0OCIgeT0iMjUxIiBmb250LXNpemU9IjEwIiBjbGFzcz0ic3RlcCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+MjwvdGV4dD4KICA8dGV4dCB4PSIyODgiIHk9IjI1MSIgZm9udC1zaXplPSIxMCIgY2xhc3M9InMiPmZvcndhcmRzIG1vZGVsOiBzdHJvbmcgfCB3ZWFrPC90ZXh0PgoKICA8IS0tIExheWVyIDI6IGFnZW50Z2F0ZXdheSAtLT4KICA8Y2lyY2xlIGN4PSIxMDAiIGN5PSIyOTgiIHI9IjEzIiBmaWxsPSIjMDA3OEQ0Ii8+CiAgPHRleHQgeD0iMTAwIiB5PSIzMDMiIGZvbnQtc2l6ZT0iMTMiIGZpbGw9IiNmZmZmZmYiIGZvbnQtd2VpZ2h0PSI3MDAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPjI8L3RleHQ+CiAgPHJlY3QgeD0iMTMwIiB5PSIyNjIiIHdpZHRoPSIyODAiIGhlaWdodD0iNzIiIHJ4PSIxMCIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSIjQzlENkUyIiBzdHJva2Utd2lkdGg9IjEuMyIvPgogIDxyZWN0IHg9IjE0MiIgeT0iMjc4IiB3aWR0aD0iMTgiIGhlaWdodD0iMTgiIHJ4PSI0IiBmaWxsPSIjNjQ3NDhCIi8+CiAgPHRleHQgeD0iMTcwIiB5PSIyOTAiIGZvbnQtc2l6ZT0iMTMuNSIgY2xhc3M9InQiPmFnZW50Z2F0ZXdheTwvdGV4dD4KICA8dGV4dCB4PSIxNzAiIHk9IjMwOCIgZm9udC1zaXplPSIxMC41IiBjbGFzcz0icyI+YXV0aCDCtyBidWRnZXQgwrcgZ3VhcmRyYWlscyDCtyBPVGVsPC90ZXh0PgogIDx0ZXh0IHg9IjE3MCIgeT0iMzI1IiBmb250LXNpemU9IjEwLjUiIGZpbGw9IiMwMDc4RDQiPmRpc3BhdGNoZXMgYnkgbW9kZWwgbmFtZTwvdGV4dD4KCiAgPCEtLSBzdHJvbmcgYnJhbmNoOiBzdGVwIDNhIC0tPgogIDxsaW5lIHgxPSI0MTAiIHkxPSIyOTgiIHgyPSI0OTgiIHkyPSIyOTgiIHN0cm9rZT0iIzAwNzhENCIgc3Ryb2tlLXdpZHRoPSIxLjYiIG1hcmtlci1lbmQ9InVybCgjYXJCKSIvPgogIDxjaXJjbGUgY3g9IjQ3MCIgY3k9IjMxMyIgcj0iOSIgZmlsbD0iI2ZmZmZmZiIgc3Ryb2tlPSIjMDA3OEQ0IiBzdHJva2Utd2lkdGg9IjEuMyIvPgogIDx0ZXh0IHg9IjQ3MCIgeT0iMzE3IiBmb250LXNpemU9IjkuNSIgZmlsbD0iIzAwNjdCOCIgZm9udC13ZWlnaHQ9IjcwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+M2E8L3RleHQ+CiAgPHRleHQgeD0iNDI4IiB5PSIyODgiIGZvbnQtc2l6ZT0iMTAuNSIgZmlsbD0iIzAwNjdCOCIgZm9udC13ZWlnaHQ9IjYwMCI+c3Ryb25nPC90ZXh0PgogIDxyZWN0IHg9IjUwMCIgeT0iMjYyIiB3aWR0aD0iMjUwIiBoZWlnaHQ9IjcyIiByeD0iMTAiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iI0M5RDZFMiIgc3Ryb2tlLXdpZHRoPSIxLjMiLz4KICA8cmVjdCB4PSI1MTIiIHk9IjI3OCIgd2lkdGg9IjE4IiBoZWlnaHQ9IjE4IiByeD0iNCIgZmlsbD0iIzAwNzhENCIvPgogIDx0ZXh0IHg9IjU0MCIgeT0iMjkwIiBmb250LXNpemU9IjEzLjUiIGNsYXNzPSJ0Ij5BenVyZSBPcGVuQUk8L3RleHQ+CiAgPHRleHQgeD0iNTQwIiB5PSIzMDgiIGZvbnQtc2l6ZT0iMTAuNSIgY2xhc3M9InMiPmdwdC01LjEgZGVwbG95bWVudDwvdGV4dD4KICA8dGV4dCB4PSI1NDAiIHk9IjMyNSIgZm9udC1zaXplPSIxMC41IiBmaWxsPSIjMDA2N0I4Ij5mcm9udGllciBtb2RlbCDCtyBlbmRzIGhlcmU8L3RleHQ+CgogIDwhLS0gd2VhayBicmFuY2g6IHN0ZXAgM2IgLS0+CiAgPGxpbmUgeDE9IjI3MCIgeTE9IjMzNCIgeDI9IjI3MCIgeTI9IjM2NiIgc3Ryb2tlPSIjNUI2QjdCIiBzdHJva2Utd2lkdGg9IjEuNSIgbWFya2VyLWVuZD0idXJsKCNhcikiLz4KICA8Y2lyY2xlIGN4PSIyNDgiIGN5PSIzNTAiIHI9IjkiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iIzVCNkI3QiIgc3Ryb2tlLXdpZHRoPSIxLjMiLz4KICA8dGV4dCB4PSIyNDgiIHk9IjM1NCIgZm9udC1zaXplPSI5LjUiIGNsYXNzPSJzdGVwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj4zYjwvdGV4dD4KICA8dGV4dCB4PSIyODgiIHk9IjM1NCIgZm9udC1zaXplPSIxMC41IiBjbGFzcz0icyIgZm9udC13ZWlnaHQ9IjYwMCI+d2VhazwvdGV4dD4KCiAgPCEtLSBMYXllciAzIGdyb3VwIC0tPgogIDxjaXJjbGUgY3g9IjEwMCIgY3k9IjM5OSIgcj0iMTMiIGZpbGw9IiMwMDc4RDQiLz4KICA8dGV4dCB4PSIxMDAiIHk9IjQwNCIgZm9udC1zaXplPSIxMyIgZmlsbD0iI2ZmZmZmZiIgZm9udC13ZWlnaHQ9IjcwMCIgdGV4dC1hbmNob3I9Im1pZGRsZSI+MzwvdGV4dD4KCiAgPCEtLSBJbmZlcmVuY2UgRXh0ZW5zaW9uIChFUFApIOKAlCBhZ2VudGdhdGV3YXkgY2FsbHMgaXQgZGlyZWN0bHkgb24gdGhlIHdlYWsgcGF0aCAtLT4KICA8cmVjdCB4PSIxMzAiIHk9IjM2NiIgd2lkdGg9IjI4MCIgaGVpZ2h0PSI2NiIgcng9IjEwIiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9IiNDOUQ2RTIiIHN0cm9rZS13aWR0aD0iMS4zIi8+CiAgPHJlY3QgeD0iMTQyIiB5PSIzODAiIHdpZHRoPSIxOCIgaGVpZ2h0PSIxOCIgcng9IjQiIGZpbGw9IiM2NDc0OEIiLz4KICA8dGV4dCB4PSIxNzAiIHk9IjM5MiIgZm9udC1zaXplPSIxMy41IiBjbGFzcz0idCI+SW5mZXJlbmNlIEV4dGVuc2lvbiAoRVBQKTwvdGV4dD4KICA8dGV4dCB4PSIxNzAiIHk9IjQxMCIgZm9udC1zaXplPSIxMC41IiBjbGFzcz0icyI+cGlja3MgdGhlIGJlc3QgcG9kIGJ5PC90ZXh0PgogIDx0ZXh0IHg9IjE3MCIgeT0iNDI2IiBmb250LXNpemU9IjEwIiBmaWxsPSIjMDA2N0I4Ij5LVi1jYWNoZSAlIMK3IHF1ZXVlIGRlcHRoIMK3IExvUkEgYWZmaW5pdHk8L3RleHQ+CgogIDwhLS0gZmxvdyA0IC0tPgogIDxsaW5lIHgxPSIyNzAiIHkxPSI0MzIiIHgyPSIyNzAiIHkyPSI0NTgiIHN0cm9rZT0iIzVCNkI3QiIgc3Ryb2tlLXdpZHRoPSIxLjUiIG1hcmtlci1lbmQ9InVybCgjYXIpIi8+CiAgPGNpcmNsZSBjeD0iMjQ4IiBjeT0iNDQ1IiByPSI5IiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9IiM1QjZCN0IiIHN0cm9rZS13aWR0aD0iMS4zIi8+CiAgPHRleHQgeD0iMjQ4IiB5PSI0NDkiIGZvbnQtc2l6ZT0iMTAiIGNsYXNzPSJzdGVwIiB0ZXh0LWFuY2hvcj0ibWlkZGxlIj40PC90ZXh0PgoKICA8IS0tIEtBSVRPIC0tPgogIDxyZWN0IHg9IjEzMCIgeT0iNDU4IiB3aWR0aD0iMjgwIiBoZWlnaHQ9IjY2IiByeD0iMTAiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iI0M5RDZFMiIgc3Ryb2tlLXdpZHRoPSIxLjMiLz4KICA8cmVjdCB4PSIxNDIiIHk9IjQ3MiIgd2lkdGg9IjE4IiBoZWlnaHQ9IjE4IiByeD0iNCIgZmlsbD0iIzAwNzhENCIvPgogIDx0ZXh0IHg9IjE3MCIgeT0iNDg0IiBmb250LXNpemU9IjEzLjUiIGNsYXNzPSJ0Ij5LQUlUTyBXb3Jrc3BhY2U8L3RleHQ+CiAgPHRleHQgeD0iMTcwIiB5PSI1MDIiIGZvbnQtc2l6ZT0iMTAuNSIgY2xhc3M9InMiPkFJIHRvb2xjaGFpbiBvcGVyYXRvciBhZGQtb248L3RleHQ+CiAgPHRleHQgeD0iMTcwIiB5PSI1MTgiIGZvbnQtc2l6ZT0iMTAiIGZpbGw9IiMwMDY3QjgiPmFnZW50Z2F0ZXdheSBmb3J3YXJkcyB0byB0aGUgY2hvc2VuIHBvZDwvdGV4dD4KCiAgPCEtLSBmbG93IDUgLS0+CiAgPGxpbmUgeDE9IjI3MCIgeTE9IjUyNCIgeDI9IjI3MCIgeTI9IjU1MCIgc3Ryb2tlPSIjNUI2QjdCIiBzdHJva2Utd2lkdGg9IjEuNSIgbWFya2VyLWVuZD0idXJsKCNhcikiLz4KICA8Y2lyY2xlIGN4PSIyNDgiIGN5PSI1MzciIHI9IjkiIGZpbGw9IiNmZmZmZmYiIHN0cm9rZT0iIzVCNkI3QiIgc3Ryb2tlLXdpZHRoPSIxLjMiLz4KICA8dGV4dCB4PSIyNDgiIHk9IjU0MSIgZm9udC1zaXplPSIxMCIgY2xhc3M9InN0ZXAiIHRleHQtYW5jaG9yPSJtaWRkbGUiPjU8L3RleHQ+CgogIDwhLS0gdkxMTSBwb2RzIC0tPgogIDxnIGZvbnQtc2l6ZT0iMTEuNSI+CiAgICA8cmVjdCB4PSIxMzgiIHk9IjU1MCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjQyIiByeD0iOCIgZmlsbD0iI0U2RjJGQiIgc3Ryb2tlPSIjMDA3OEQ0IiBzdHJva2Utd2lkdGg9IjEuMiIvPgogICAgPHRleHQgeD0iMTc4IiB5PSI1NzUiIGZpbGw9IiMwMDY3QjgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPnZMTE08L3RleHQ+CiAgICA8cmVjdCB4PSIyMzAiIHk9IjU1MCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjQyIiByeD0iOCIgZmlsbD0iI0U2RjJGQiIgc3Ryb2tlPSIjMDA3OEQ0IiBzdHJva2Utd2lkdGg9IjEuMiIvPgogICAgPHRleHQgeD0iMjcwIiB5PSI1NzUiIGZpbGw9IiMwMDY3QjgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPnZMTE08L3RleHQ+CiAgICA8cmVjdCB4PSIzMjIiIHk9IjU1MCIgd2lkdGg9IjgwIiBoZWlnaHQ9IjQyIiByeD0iOCIgZmlsbD0iI0U2RjJGQiIgc3Ryb2tlPSIjMDA3OEQ0IiBzdHJva2Utd2lkdGg9IjEuMiIvPgogICAgPHRleHQgeD0iMzYyIiB5PSI1NzUiIGZpbGw9IiMwMDY3QjgiIHRleHQtYW5jaG9yPSJtaWRkbGUiPnZMTE08L3RleHQ+CiAgPC9nPgogIDx0ZXh0IHg9IjQzMCIgeT0iNTY4IiBmb250LXNpemU9IjEwLjUiIGNsYXNzPSJzIj5vbmUgcG9kIGNob3NlbiBieSBHUFUgc3RhdGU8L3RleHQ+CiAgPHRleHQgeD0iNDMwIiB5PSI1ODMiIGZvbnQtc2l6ZT0iMTAuNSIgY2xhc3M9InMiPsK3IHdlYWsgcGF0aCBlbmRzIGhlcmU8L3RleHQ+CgogIDwhLS0gbGVnZW5kIC0tPgogIDxyZWN0IHg9IjUwMCIgeT0iMzkyIiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHJ4PSIzIiBmaWxsPSIjMDA3OEQ0Ii8+CiAgPHRleHQgeD0iNTIyIiB5PSI0MDUiIGZvbnQtc2l6ZT0iMTEiIGNsYXNzPSJ0IiBmb250LXdlaWdodD0iNDAwIj5tYW5hZ2VkIEF6dXJlPC90ZXh0PgogIDxyZWN0IHg9IjUwMCIgeT0iNDE0IiB3aWR0aD0iMTYiIGhlaWdodD0iMTYiIHJ4PSIzIiBmaWxsPSIjZmZmZmZmIiBzdHJva2U9IiM2NDc0OEIiIHN0cm9rZS13aWR0aD0iMS41Ii8+CiAgPHRleHQgeD0iNTIyIiB5PSI0MjciIGZvbnQtc2l6ZT0iMTEiIGNsYXNzPSJ0IiBmb250LXdlaWdodD0iNDAwIj5vcGVuIHNvdXJjZSBvbiBBS1M8L3RleHQ+CiAgPHRleHQgeD0iNTAwIiB5PSI0NTIiIGZvbnQtc2l6ZT0iMTAiIGNsYXNzPSJzIj5OdW1iZXJlZCBiYWRnZXMgPSByZXF1ZXN0IG9yZGVyLjwvdGV4dD4KICA8dGV4dCB4PSI1MDAiIHk9IjQ2NiIgZm9udC1zaXplPSIxMCIgY2xhc3M9InMiPlR3byBlbmRpbmdzOiAzYSAoc3Ryb25nKSBvciA1ICh3ZWFrKS48L3RleHQ+Cjwvc3ZnPgoK" width="820" height="620" class="img_ev3q"></p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>A NOTE ON EXACTNESS</div><div class="admonitionContent_BuS1"><p>Several pieces here are young, and field names drift between releases — the Inference Extension renamed and restructured CRDs on its way to v1. Everything below was validated end-to-end on AKS in mid-2026 against Inference Extension v1.0.0 and agentgateway v1.3.1. Treat the manifests as the <em>shape</em> of the solution, pin your versions, and confirm fields against the docs at the end. The decomposition is stable; specific flags and CRD fields move quickly.</p></div></div>
<p>With the map in hand, the rest of this post builds it — bottom-up, serving first and the semantic decision last, because each layer needs the address of the one beneath it. First, the cluster everything runs on.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerequisites">Prerequisites<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites" translate="no">​</a></h2>
<ul>
<li class="">An Azure subscription with GPU quota for the VM family you'll serve on (this guide uses a single <code>Standard_NC*</code> GPU node pool, provisioned for you by KAITO). Quota isn't capacity — confirm the SKU is actually allocatable in your region before you rely on it.</li>
<li class="">A recent Azure CLI and <code>kubectl</code>.</li>
<li class="">An Azure OpenAI (or AI Foundry) resource with a current frontier deployment for the strong path. We use <code>gpt-5.1</code>; <code>gpt-4o</code> is deprecating in many regions and may not be newly deployable.</li>
</ul>
<p>Create a cluster with the serving and monitoring add-ons enabled. KAITO provisions and manages GPU node pools on demand, so you don't pre-create them:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aks create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group $RG \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name $CLUSTER \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --node-count 2 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-ai-toolchain-operator \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-oidc-issuer \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-azure-monitor-metrics \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --generate-ssh-keys</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az aks get-credentials --resource-group $RG --name $CLUSTER</span><br></div></code></pre></div></div>
<p>That's the whole cluster — no gateway add-on, no extra CRDs, no Istio to bootstrap. agentgateway is the front door (more on that in Layer 2). With the cluster up, start at the bottom: serving the weak model.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="layer-3--serving-and-the-load-balancing-trap">Layer 3 — Serving, and the load-balancing trap<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#layer-3--serving-and-the-load-balancing-trap" class="hash-link" aria-label="Direct link to Layer 3 — Serving, and the load-balancing trap" title="Direct link to Layer 3 — Serving, and the load-balancing trap" translate="no">​</a></h2>
<p>Start with the failure mode, because it's the whole reason this layer is more than a <code>Service</code> — and agents make it worse.</p>
<p>Round-robin assumes requests are interchangeable. LLM requests are anything but: a 100k-token context-stuffed agent step and a 200-token tool-argument fill differ by three orders of magnitude in cost. An agent fleet mixes both constantly. Spray them uniformly across identical pods and round-robin will cheerfully queue a cheap completion behind a giant prefill on a saturated pod while an idle pod sits next to it. Your throughput looks fine and your p99 — the thing an agent loop feels on <em>every</em> iteration — falls off a cliff.</p>
<p>The fix isn't more pods — it's <em>placing each request on the pod best able to take it right now</em>, by reading live KV-cache occupancy and queue depth. That capability is what the <strong>Endpoint Picker</strong> is. Everything else in this layer exists to feed it.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="31-the-pods-to-place-kaito">3.1 The pods to place: KAITO<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#31-the-pods-to-place-kaito" class="hash-link" aria-label="Direct link to 3.1 The pods to place: KAITO" title="Direct link to 3.1 The pods to place: KAITO" translate="no">​</a></h3>
<p>The Endpoint Picker needs a homogeneous pool of model servers exporting the right signals, and KAITO produces exactly that: declare a <code>Workspace</code>, point it at a preset, and KAITO provisions the GPU SKU, runs vLLM, and exposes an OpenAI-compatible Service. Because the engine is vLLM, every pod emits the metrics the Endpoint Picker routes on (<code>vllm:num_requests_waiting</code>, <code>vllm:kv_cache_usage_perc</code>). Apply <a href="https://github.com/Azure/AKS/blob/master/examples/llm-routing-on-aks/kaito-workspace.yaml" target="_blank" rel="noopener noreferrer" class=""><code>kaito-workspace.yaml</code></a> — it pins <code>instanceType: Standard_NC24ads_A100_v4</code>, <code>count: 2</code>, and <code>preset: phi-4-mini-instruct</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl create namespace llm</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f kaito-workspace.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get workspace workspace-phi-4-mini -n llm -w   # Ready can take ~10–20 min</span><br></div></code></pre></div></div>
<p>KAITO provisions the GPU node, pulls the model, and starts vLLM; the workspace flips to <code>Ready</code> when inference is up:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">NAME                   INSTANCE                   RESOURCEREADY   INFERENCEREADY   STATE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">workspace-phi-4-mini   Standard_NC24ads_A100_v4   True            True             Ready</span><br></div></code></pre></div></div>
<p>KAITO publishes a Service named <code>workspace-phi-4-mini</code> on port 80 and labels its pods <code>kaito.sh/workspace: workspace-phi-4-mini</code> — that label is how the next step selects the pool. One detail that will bite you later: the Service maps port 80 to the vLLM <strong>container's port 5000</strong>, and the Endpoint Picker routes to pod IPs directly, bypassing the Service. So the pool below targets <code>5000</code>, not <code>80</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="32-the-placement-logic-the-inference-extension">3.2 The placement logic: the Inference Extension<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#32-the-placement-logic-the-inference-extension" class="hash-link" aria-label="Direct link to 3.2 The placement logic: the Inference Extension" title="Direct link to 3.2 The placement logic: the Inference Extension" translate="no">​</a></h3>
<p>Pool membership — <em>which pods are the model server</em> — is a different concern from how a workload is weighted against that pool, so the Inference Extension splits them across two objects:</p>
<ul>
<li class="">An <strong><code>InferencePool</code></strong> — the set of model-server pods plus a reference to the <strong>Endpoint Picker (EPP)</strong>, the extension that an ext-proc-capable proxy calls to choose an endpoint per request. In this design that proxy is agentgateway (Layer 2); the InferencePool and EPP don't care who calls them.</li>
<li class="">An <strong><code>InferenceObjective</code></strong> — the per-workload treatment: an integer <code>priority</code> against a pool. It replaces the older <code>InferenceModel</code> object: where <code>InferenceModel</code> carried a <code>modelName</code> and a <code>criticality</code> enum, <code>InferenceObjective</code> keeps only an integer <code>priority</code> and takes the served-model name from the request instead.</li>
</ul>
<p>Install the CRDs and the Endpoint Picker (the project ships a Helm chart), pointing it at the KAITO pods. The EPP must serve <strong>plaintext</strong> gRPC to match agentgateway's ext-proc client — set <code>--secure-serving=false</code>, or the call to it resets before headers:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f https://github.com/kubernetes-sigs/gateway-api-inference-extension/releases/download/v1.0.0/manifests.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">helm install phi-epp \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  oci://registry.k8s.io/gateway-api-inference-extension/charts/inferencepool \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --version v1.0.0 -n llm \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --set inferencePool.modelServers.matchLabels."kaito\.sh/workspace"=workspace-phi-4-mini \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --set provider.name=none \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --set inferenceExtension.extraArgs="{--secure-serving=false}"</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>KEEP THE PICKER CLUSTER-INTERNAL</div><div class="admonitionContent_BuS1"><p><code>--secure-serving=false</code> disables TLS on the Endpoint Picker, so the ext-proc hop is plaintext gRPC. It's the simplest way to match agentgateway's client, but only safe because the EPP is reached cluster-internally over a <code>ClusterIP</code> Service. Don't expose it beyond the cluster, and restrict who can reach it — a <code>NetworkPolicy</code> that admits only the agentgateway pods, or a service mesh that re-encrypts the hop.</p></div></div>
<p>The Helm chart creates its own <code>InferencePool</code> named after the release, so you don't strictly need to apply one. If you do, <a href="https://github.com/Azure/AKS/blob/master/examples/llm-routing-on-aks/inference-pool.yaml" target="_blank" rel="noopener noreferrer" class=""><code>inference-pool.yaml</code></a> has the full <code>InferencePool</code> + <code>InferenceObjective</code> — with two fields that trip people up. The pool's selector needs a structured <code>matchLabels</code>, and its <code>targetPorts</code> must be the vLLM <strong>container</strong> port <code>5000</code> (the pool routes to pod IPs, bypassing the Service's port 80):</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">selector</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">matchLabels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">kaito.sh/workspace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> workspace</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">phi</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">4</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mini </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">targetPorts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">number</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5000</span><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># the vLLM container port, NOT the Service's 80</span><br></div></code></pre></div></div>
<p>The other trap is the API group: <code>InferencePool</code> lives in <code>inference.networking.k8s.io</code>, but <code>InferenceObjective</code> lives in <code>inference.networking.x-k8s.io</code> — different groups, easy to miss.</p>
<p>The Endpoint Picker is now running and watching the pool, but nothing calls it yet. It needs an ext-proc-capable proxy out front to consult it per request — and that proxy is the AI gateway in Layer 2, so we wire the call there rather than standing up a separate Gateway API gateway just to reach it.</p>
<p><strong>What this layer decided:</strong> only <em>which replica</em>, on GPU state. It has no idea what a "strong" model is, no budgets, no auth. Those are the next layer's job.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="layer-2--the-gateway-and-why-agents-need-it-most">Layer 2 — The gateway, and why agents need it most<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#layer-2--the-gateway-and-why-agents-need-it-most" class="hash-link" aria-label="Direct link to Layer 2 — The gateway, and why agents need it most" title="Direct link to Layer 2 — The gateway, and why agents need it most" translate="no">​</a></h2>
<p>The tempting shortcut is to let the router call backends directly. Resist it — and with agents the reason is sharper than with chat. An agent loop can spend an unbounded number of tokens before it decides to stop; without a budget and a rate limit <em>somewhere on the path</em>, a single misbehaving agent can run up a frontier bill or saturate your GPU fleet. A gateway is where that policy lives, written once, so nothing else has to reinvent it.</p>
<p>agentgateway is an open-source, OpenAI-compatible AI gateway built for exactly this traffic: provider abstraction, auth, token-based rate limiting, model-cost tracking, guardrails, and OpenTelemetry across backends — and, because it's agent-native, first-class MCP tool-serving and agent-to-agent routing when you grow into them. Here it does three jobs: hide the <strong>strong</strong> (Azure OpenAI) and <strong>weak</strong> (in-cluster) backends behind one endpoint, enforce policy on every call, and — because it speaks ext-proc itself — <strong>call the Endpoint Picker directly</strong> so the weak path gets GPU-state placement. That third job is what lets us drop the Gateway API gateway entirely: one data plane instead of two, and no dependency on the managed gateway's inference-extension support, which is still preview-gated on AKS today.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>CONFIG SHAPE</div><div class="admonitionContent_BuS1"><p>The snippets below were validated against agentgateway v1.3.1 and trimmed for the argument; pin field names to the <a href="https://agentgateway.dev/docs/" target="_blank" rel="noopener noreferrer" class="">agentgateway docs</a>. The full file is in the <a href="https://github.com/Azure/AKS/tree/master/examples/llm-routing-on-aks" target="_blank" rel="noopener noreferrer" class="">example manifests</a>.</p></div></div>
<p>The outer shape is <code>binds → listeners → routes → backends</code>. The one thing to internalize: an AI backend is <code>ai: { name, provider: { &lt;provider&gt;: {...} } }</code> — the provider nests under <code>provider:</code>, <code>name</code> is required, and the Azure provider needs <code>resourceName</code> <strong>and</strong> <code>resourceType</code> (its <code>model</code> is your Azure <em>deployment</em> name). Auth attaches as a route policy whose key the Azure provider sends as the <code>api-key</code> header (a static key keeps the example self-contained; for production on AKS, prefer Azure <strong>workload identity</strong>, which agentgateway supports natively for the Azure provider):</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># the strong route's backend (excerpt)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">backends</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">ai</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> aoai</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">strong</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">provider</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">azure</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">resourceName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"&lt;your-aoai-resource&gt;"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">resourceType</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> openAI</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">model</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> gpt</span><span class="token punctuation" style="color:#393A34">-</span><span class="token number" style="color:#36acaa">5.1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"2024-10-21"</span><br></div></code></pre></div></div>
<p>The <code>strong</code> route is a single <code>ai</code> backend pointed at Azure OpenAI. The <code>weak</code> route is where the front-door magic lives: a <code>service</code> backend pointed at the KAITO pods, with an <strong><code>inferenceRouting</code></strong> policy that hands each request to the Endpoint Picker over ext-proc and forwards to the pod it returns. Three details earned during validation, because the loader is strict about each:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># top-level: declare the model-server pool the picker selects from</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">services</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> phi</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">weak</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> llm</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">hostname</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> workspace</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">phi</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">4</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mini.llm.svc.cluster.local</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">vips</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain">                       </span><span class="token comment" style="color:#999988;font-style:italic"># required, even when empty</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">ports</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">5000</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5000</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># the vLLM container port</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># ...the weak route's backend:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">backends</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">service</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> llm/workspace</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">phi</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">4</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mini.llm.svc.cluster.local   </span><span class="token comment" style="color:#999988;font-style:italic"># namespace/HOSTNAME, not the service name</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">5000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">policies</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">inferenceRouting</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic"># a BACKEND policy, not a route policy</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">endpointPicker</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">host</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> phi</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">epp</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">epp.llm.svc.cluster.local</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">9002</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">destinationMode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> passthrough   </span><span class="token comment" style="color:#999988;font-style:italic"># trust the picker's choice</span><br></div></code></pre></div></div>
<p>The full two-route config — including the <code>strong</code> <code>ai</code> backend and both routes' policies — is in <a href="https://github.com/Azure/AKS/blob/master/examples/llm-routing-on-aks/agentgateway-config.yaml" target="_blank" rel="noopener noreferrer" class=""><code>agentgateway-config.yaml</code></a>. The credential never leaves the gateway, and on the <code>weak</code> route agentgateway itself is the thing that consults the picker.</p>
<p>The governance that makes this safe for agents lives there too: the <code>strong</code> route carries a token <strong>rate limit</strong> (a <code>localRateLimit</code> route policy) — the backstop against an agent loop that won't stop spending. agentgateway can also price every call in real dollars through a top-level <code>config.modelCatalog</code>, but its schema is nested and provider-scoped — the obvious flat form didn't load for us — so wire it from the <a href="https://agentgateway.dev/docs/" target="_blank" rel="noopener noreferrer" class="">agentgateway docs</a> for your release rather than copying a snippet.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>ONE PIECE STAYS YOURS TO RUN</div><div class="admonitionContent_BuS1"><p>You can swap agentgateway's governance for a managed AI gateway if you prefer, but that only covers the governance half — not the inference-aware front door. The ext-proc call to the Endpoint Picker, which places each weak-path request on the right GPU pod, has no managed equivalent today, so that piece stays yours to run whichever gateway fronts it.</p></div></div>
<p>Smoke-test both paths before the router exists:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># strong → Azure OpenAI</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">curl -s http://agentgateway.llm.svc.cluster.local:4000/strong/v1/chat/completions \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -H "Content-Type: application/json" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -d '{"model":"gpt-5.1","messages":[{"role":"user","content":"Say no in one word."}]}'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># weak → KAITO via the Endpoint Picker</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># (same shape RouteLLM sends: it appends /v1/chat/completions to api_base)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">curl -s http://agentgateway.llm.svc.cluster.local:4000/weak/v1/chat/completions \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -H "Content-Type: application/json" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -d '{"model":"phi-4-mini-instruct","messages":[{"role":"user","content":"Say hi in one word."}]}'</span><br></div></code></pre></div></div>
<p>Both return an OpenAI-shaped completion — the <code>strong</code> call comes back stamped with the resolved Azure deployment, the <code>weak</code> call from the in-cluster model, and neither caller had to know where the model lived:</p>
<div class="language-jsonc codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsonc codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">// strong → Azure OpenAI</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{ "model": "gpt-5.1-2025-11-13",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "choices": [{ "message": { "role": "assistant", "content": "No." } }],</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "usage": { "total_tokens": 29 } }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">// weak → KAITO / vLLM</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{ "model": "phi-4-mini-instruct",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "choices": [{ "message": { "role": "assistant", "content": "Hello." } }] }</span><br></div></code></pre></div></div>
<p><strong>What this layer decided:</strong> how to authenticate, meter, guardrail, and forward — on <em>policy</em>, never on content. It dispatches whatever model name it's handed. Choosing that name is Layer 1's job.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="layer-1--the-semantic-decision-and-the-threshold-that-is-the-whole-ballgame">Layer 1 — The semantic decision, and the threshold that is the whole ballgame<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#layer-1--the-semantic-decision-and-the-threshold-that-is-the-whole-ballgame" class="hash-link" aria-label="Direct link to Layer 1 — The semantic decision, and the threshold that is the whole ballgame" title="Direct link to Layer 1 — The semantic decision, and the threshold that is the whole ballgame" translate="no">​</a></h2>
<p>Here's the only layer that reads the prompt — and the one decision that actually moves the bill. For an agent firing hundreds of calls per task, it's also the layer with the most leverage: every easy call it keeps off the frontier model is multiplied by the loop.</p>
<p>RouteLLM is a set of routers trained on human-preference data to predict, per prompt, whether a cheap model can match a strong model's answer. On the paper's own model pair, the featured <code>mf</code> (matrix-factorization) router reaches <strong>~95% of GPT-4's quality on MT-Bench while making just ~26% of the GPT-4 calls</strong> — cutting cost by up to ~85% versus sending everything to GPT-4, by escalating only the genuinely hard prompts.</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>WHERE IT STRAINS</div><div class="admonitionContent_BuS1"><p>Those figures are for the pair RouteLLM was trained on — not your strong/weak pair. The router's quality predictor is fit to a particular pairing, so whether ~26% escalation holds with phi-4-mini as your weak model is something you confirm against your own traffic. And the router isn't free: scoring every prompt is itself a paid <code>text-embedding-3-small</code> round-trip, so the cost win depends on the price gap between strong and weak dwarfing that per-call overhead. That's the reason for the calibrate-then-close-the-loop step below, not a footnote to it.</p></div></div>
<p>The composition is the elegant part: <strong>RouteLLM's strong and weak upstreams are just agentgateway.</strong> It makes the semantic call, picks a model, and forwards into the governed data plane — so the decision layer inherits all of Layer 2's auth, budgets, and tracing without re-implementing a line of it.</p>
<p>One wiring detail the reviewer in you should catch: agentgateway routes by <strong>path</strong> (<code>/strong</code>, <code>/weak</code>), but RouteLLM hands its choice to litellm as a <em>model name</em>. So point each model at the matching agentgateway path with a per-model <code>api_base</code> rather than a single base URL:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># inside the RouteLLM container — each model maps to its agentgateway route</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python -m routellm.openai_server --routers mf \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --strong-model openai/gpt-5.1 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --weak-model   openai/phi-4-mini-instruct \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --config routellm-config.yaml   # sets api_base per model → /strong and /weak</span><br></div></code></pre></div></div>
<p>One more gotcha from running this against current models: RouteLLM sends <code>presence_penalty</code> / <code>frequency_penalty</code> by default, and <strong>litellm</strong> (which RouteLLM uses to reach each provider) raises <code>UnsupportedParamsError</code> when the target model doesn't accept them — newer frontier models like the gpt-5.x family don't. Set <code>litellm.drop_params=True</code> so litellm silently drops the unsupported params instead of failing the call.</p>
<p>Clients call RouteLLM with a model string that encodes both the router and the cost threshold:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">router-mf-0.11593</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        │   └── threshold: the strong/weak cutoff — calibrate it</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        └────── the matrix-factorization router</span><br></div></code></pre></div></div>
<p>That threshold is the ballgame. Set it too low and you escalate everything and pay frontier prices for "hello"; set it too high and quality craters on the hard steps that needed the strong model. Don't guess it — calibrate against a representative sample so a target fraction of <em>your</em> traffic escalates:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">python -m routellm.calibrate_threshold --routers mf --strong-model-pct 0.5</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># → 0.11593: the threshold that sends ~50% of calls to "strong"</span><br></div></code></pre></div></div>
<p>Then close the loop with the <em>real</em> numbers. Run live agent traffic and read the actual strong/weak split off <strong>agentgateway's</strong> metrics — not RouteLLM's estimate — and, once you've wired the cost catalog, the dollars too; nudge the threshold until the cost/quality trade sits where you want it. The router proposes; your own traffic disposes.</p>
<p>One caveat the rate card can't see: <strong>prompt caching</strong>. A cached input token costs a fraction of an uncached one — Azure OpenAI discounts cache hits, and the weak path's vLLM prefix cache is the very thing the Endpoint Picker routes on. Both caches are per-model and per-prefix, so per-call routing works against them: bounce a session between models and each one's cache for that growing context goes cold, so the <em>marginal</em> cost of a strong call whose prefix is still warm is much lower than its sticker rate, and the savings from offloading it are smaller than the headline prices imply. This is another reason to measure billed cost rather than multiply tokens by a static rate — and, where caching dominates over difficulty, to route at session or task granularity instead of per call. Per-call routing on prompt difficulty is still the right default; just don't let a token-rate estimate convince you the split is cheaper or more expensive than your bill says.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>MANAGED ALTERNATIVE FOR THIS LAYER</div><div class="admonitionContent_BuS1"><p><strong>Microsoft Foundry</strong>'s model router is a single managed deployment that routes among models by query complexity and cost — the managed analogue to RouteLLM. We feature RouteLLM precisely because the explicit, calibratable threshold is the heart of this design and worth holding in your own hands. If you'd rather not operate a router, the Foundry model router fills the same slot.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="observability-with-managed-azure-monitoring">Observability with managed Azure monitoring<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#observability-with-managed-azure-monitoring" class="hash-link" aria-label="Direct link to Observability with managed Azure monitoring" title="Direct link to Observability with managed Azure monitoring" translate="no">​</a></h2>
<p>Layer 1 trades quality for cost, so this section closes one question: can you trust the routing numbers, and can you watch the same signals the Endpoint Picker routes on? The answer splits by source — <strong>GPU and latency come from vLLM; the strong/weak split (and, once you wire the cost catalog, dollars) comes from agentgateway</strong> — so a complete view scrapes both.</p>
<p>To get there, add two <code>PodMonitor</code>s to the Managed Prometheus you enabled at cluster creation: one selecting the KAITO pods for vLLM's metrics on port 5000, one for agentgateway's on port 15020. Then link <strong>Azure Managed Grafana</strong> to the workspace. (One gotcha: the managed add-on uses Azure's CRDs under the <strong><code>azmonitoring.coreos.com</code></strong> API group, not upstream <code>monitoring.coreos.com</code>, so change a community manifest's <code>apiVersion</code> to <code>azmonitoring.coreos.com/v1</code> or the add-on won't scrape it.)</p>
<p><img decoding="async" loading="lazy" alt="Grafana dashboard of live metrics from the running cluster, scraped by Azure Managed Prometheus into an Azure Monitor workspace. The top row, &amp;quot;Layer 1–2 · Routing &amp;amp; cost (agentgateway),&amp;quot; holds three panels: request rate by route (weak steady around 2.5 req/s, strong around 0.7), a cumulative donut roughly 70% weak and 30% strong, and gateway p95 latency by route (strong around 2.5s, weak near zero). The bottom section, &amp;quot;Layer 3 · Weak-path GPU serving (vLLM via KAITO),&amp;quot; shows KV-cache utilization and queue depth both flat near zero under this light load, plus vLLM token throughput around 20 tokens/sec. The routing and cost panels come from agentgateway&amp;#39;s metrics; the GPU-serving panels come from vLLM&amp;#39;s." src="https://blog.aks.azure.com/assets/images/llm-routing-on-aks-observability-6ef0dda7c9e716e6b390e095b3d139fe.png" width="1379" height="1038" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-full-path-one-call">The full path, one call<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#the-full-path-one-call" class="hash-link" aria-label="Direct link to The full path, one call" title="Direct link to The full path, one call" translate="no">​</a></h2>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># trivial step → router picks weak → agentgateway → Endpoint Picker → KAITO pod</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">curl -s http://routellm.llm.svc.cluster.local:6060/v1/chat/completions \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -d '{"model":"router-mf-0.11593","messages":[{"role":"user","content":"What is 2+2?"}]}'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># hard step → router picks strong → agentgateway → Azure OpenAI</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">curl -s http://routellm.llm.svc.cluster.local:6060/v1/chat/completions \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  -d '{"model":"router-mf-0.11593",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       "messages":[{"role":"user","content":"Derive the EM algorithm and explain why each step cannot decrease the log-likelihood."}]}'</span><br></div></code></pre></div></div>
<p>The <code>model</code> field in each response is the receipt — it tells you which way the router decided. Against the calibrated threshold <code>0.11593</code>, <code>"What is 2+2?"</code> scores a strong-win-rate of <code>0.09</code> (below the line → weak) while the EM-algorithm prompt scores <code>0.29</code> (above → strong), so they come back from different models:</p>
<div class="language-jsonc codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-jsonc codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">// "What is 2+2?"             → "model": "phi-4-mini-instruct"   (weak, a fraction of a cent)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">// "Derive the EM algorithm…" → "model": "gpt-5.1-2025-11-13"    (strong, frontier pricing)</span><br></div></code></pre></div></div>
<p>Both hit the <em>same</em> endpoint with the <em>same</em> model string. Three decisions — content, policy, GPU state — resolved in one round trip, each by the layer that owns it. Now point your agent framework's base URL at that endpoint and every call in the loop gets sorted the same way.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="scale-it-to-your-problem">Scale it to your problem<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#scale-it-to-your-problem" class="hash-link" aria-label="Direct link to Scale it to your problem" title="Direct link to Scale it to your problem" translate="no">​</a></h2>
<p>You rarely need all of it on day one. Because the layers don't bleed into each other, you can adopt exactly the slice your problem demands:</p>
<ul>
<li class=""><strong>One hosted model, a few agents, no self-hosting?</strong> <strong>Layer 2 only</strong> — agentgateway for auth, budgets, and guardrails over Azure OpenAI. There's nothing to route <em>to</em> and no GPUs to place on.</li>
<li class=""><strong>Self-hosting at scale but one model class?</strong> <strong>KAITO + the Inference Extension</strong> give you managed serving and cache-aware placement. Skip semantic routing — there's only one model.</li>
<li class=""><strong>A real cost problem from agents sending easy steps to expensive models?</strong> Add <strong>RouteLLM</strong>. It earns its keep the moment you have a meaningful price gap and a meaningful fraction of easy traffic — which describes essentially every agent in a loop.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://blog.aks.azure.com/2026/06/29/llm-routing-on-aks#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>Routing agent traffic isn't one decision — it's three, each on its own signal: <strong>content</strong> picks the model (RouteLLM), <strong>policy</strong> governs the call (agentgateway), and <strong>GPU state</strong> picks the replica (the Inference Extension over KAITO-served vLLM). Keep those seams clean and you get a single OpenAI-compatible endpoint your agents point at, where every call is routed, metered, and traced by the layer that owns that concern — and where the easy calls an agent loop fires by the hundred quietly stay off the frontier bill.</p>
<p>From here, the obvious next moves all reinforce the same shape: autoscale the weak fleet on <code>vllm:num_requests_waiting</code> with the managed <strong>KEDA</strong> add-on so the GPU pool breathes with bursty agent traffic; lean into agentgateway's agent-native side by serving tools over <strong>MCP</strong> and routing <strong>agent-to-agent</strong> traffic through the same governed data plane; and add per-agent budgets at the gateway so spend stays attributable as more agents share the cluster.</p>
<p>The <a href="https://github.com/Azure/AKS/tree/master/examples/llm-routing-on-aks" target="_blank" rel="noopener noreferrer" class="">example manifests</a> have everything here ready to adapt. For the components themselves, see <a href="https://learn.microsoft.com/azure/aks/ai-toolchain-operator" target="_blank" rel="noopener noreferrer" class="">KAITO on AKS</a>, the <a href="https://gateway-api-inference-extension.sigs.k8s.io/" target="_blank" rel="noopener noreferrer" class="">Gateway API Inference Extension</a>, <a href="https://agentgateway.dev/docs/" target="_blank" rel="noopener noreferrer" class="">agentgateway</a>, <a href="https://github.com/lm-sys/RouteLLM" target="_blank" rel="noopener noreferrer" class="">RouteLLM</a> (and its <a href="https://arxiv.org/abs/2406.18665" target="_blank" rel="noopener noreferrer" class="">paper</a>), and <a href="https://learn.microsoft.com/azure/azure-monitor/containers/kubernetes-monitoring-enable" target="_blank" rel="noopener noreferrer" class="">Managed Prometheus and Grafana for AKS</a>.</p>]]></content:encoded>
            <category>AI Inference</category>
            <category>Agent</category>
            <category>agentgateway</category>
            <category>RouteLLM</category>
            <category>KAITO</category>
            <category>GPU</category>
        </item>
        <item>
            <title><![CDATA[Deploy Anyscale on Azure with Terraform: a step-by-step guide]]></title>
            <link>https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform</link>
            <guid>https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform</guid>
            <pubDate>Mon, 15 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Deploy Anyscale on Azure end-to-end with Terraform, AKS managed Gateway API, Istio-based app routing, and the AzAPI provider for newly released services.]]></description>
            <content:encoded><![CDATA[<p>A few weeks ago at Microsoft Build, the <a href="https://www.anyscale.com/blog/anyscale-on-azure-public-preview-build-and-deploy-ai-scale" target="_blank" rel="noopener noreferrer" class="">public preview of Anyscale on Azure</a> was announced. If you're not familiar, <a href="https://learn.microsoft.com/azure/anyscale-on-azure/overview" target="_blank" rel="noopener noreferrer" class="">Anyscale on Azure</a> is a managed platform for running distributed AI/ML workloads with <a href="https://www.ray.io/" target="_blank" rel="noopener noreferrer" class="">Ray</a> on AKS. It's an Azure Native integration, a co-engineered effort between Anyscale and Microsoft, that deploys an operator onto your AKS cluster and integrates with Microsoft Entra ID for single sign-on (SSO).</p>
<p>The official <a href="https://learn.microsoft.com/azure/anyscale-on-azure/quickstart-azure-cli-gateway-envoy" target="_blank" rel="noopener noreferrer" class="">quickstart</a> walks you through deploying Anyscale on Azure using the Azure CLI and Azure Portal with Envoy Gateway for ingress. That's a solid starting point, but if you're like me, you want everything in Terraform so your infrastructure is repeatable, version-controlled, and easy to tear down.</p>
<p>In this post, we'll walk through a Terraform configuration that deploys the full Anyscale on Azure stack, swapping out Envoy Gateway for <a href="https://learn.microsoft.com/azure/aks/app-routing-gateway-api" target="_blank" rel="noopener noreferrer" class="">AKS managed Gateway API with Istio-based app routing</a>. With this approach, AKS handles the gateway lifecycle and you skip the step of installing and configuring Envoy Gateway entirely.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-were-building">What we're building<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#what-were-building" class="hash-link" aria-label="Direct link to What we're building" title="Direct link to What we're building" translate="no">​</a></h2>
<p>Before we dive into the code, here's a quick overview of what the Terraform configuration creates:</p>
<ul>
<li class="">A resource group to hold everything</li>
<li class="">An AKS cluster (Standard tier) with Gateway API and Istio-based app routing enabled</li>
<li class="">The Anyscale operator installed as an AKS cluster extension</li>
<li class="">A user-assigned managed identity with a federated credential for the Anyscale operator</li>
<li class="">An Azure Container Registry (Standard SKU) for container images</li>
<li class="">A storage account with hierarchical namespace (HNS) enabled and a private blob container</li>
<li class="">The Anyscale Cloud and Cloud Resource registered via the AzAPI provider (<code>Anyscale.Platform/clouds</code>)</li>
<li class="">Role assignments for storage, container registry, and AKS access</li>
</ul>
<p>The full Terraform code is available as a <a href="https://gist.github.com/pauldotyu/4226b4d735f1903c7cb6a0f3d35bc655" target="_blank" rel="noopener noreferrer" class="">GitHub Gist</a>. In this post, I'll walk through each piece so you understand what's happening and why.</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>warning</div><div class="admonitionContent_BuS1"><p>This walkthrough is designed to get you up and running quickly. It's a great way to understand the components that go into an Anyscale on Azure deployment and how they fit together, but it's not intended for production use as-is. For production environments, follow <a href="https://learn.microsoft.com/azure/cloud-adoption-framework/ready/landing-zone/" target="_blank" rel="noopener noreferrer" class="">Azure landing zone architectures</a> and apply your organization's security and governance best practices around networking, identity, logging, and policy.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="prerequisites">Prerequisites<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#prerequisites" class="hash-link" aria-label="Direct link to Prerequisites" title="Direct link to Prerequisites" translate="no">​</a></h2>
<p>Before you get started, make sure you have the following:</p>
<ul>
<li class=""><a href="https://learn.microsoft.com/cli/azure/install-azure-cli" target="_blank" rel="noopener noreferrer" class="">Azure CLI</a> authenticated to a subscription</li>
<li class=""><a href="https://developer.hashicorp.com/terraform/install" target="_blank" rel="noopener noreferrer" class="">Terraform</a></li>
<li class=""><a href="https://docs.anyscale.com/reference/quickstart-cli" target="_blank" rel="noopener noreferrer" class="">Anyscale CLI</a> (installed via <a href="https://pipx.pypa.io/stable/" target="_blank" rel="noopener noreferrer" class="">pipx</a>)</li>
<li class=""><a href="https://jqlang.github.io/jq/download/" target="_blank" rel="noopener noreferrer" class="">jq</a> for parsing Terraform outputs</li>
<li class=""><a href="https://kubernetes.io/docs/tasks/tools/" target="_blank" rel="noopener noreferrer" class="">kubectl</a> for interacting with the AKS cluster</li>
</ul>
<p>Your subscription also needs the following resource providers registered:</p>
<ul>
<li class=""><code>Anyscale.Platform</code></li>
<li class=""><code>Microsoft.ContainerService</code></li>
<li class=""><code>Microsoft.ContainerRegistry</code></li>
<li class=""><code>Microsoft.Storage</code></li>
<li class=""><code>Microsoft.ManagedIdentity</code></li>
</ul>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>The AzureRM Terraform provider will automatically register most required resource providers during deployment. If your account lacks permission to register providers, register them manually with <code>az provider register --namespace &lt;provider-name&gt;</code> before running <code>terraform apply</code>.</p></div></div>
<div class="theme-admonition theme-admonition-caution admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>caution</div><div class="admonitionContent_BuS1"><p>Anyscale on Azure is currently in preview and available in select regions. See <a href="https://learn.microsoft.com/azure/anyscale-on-azure/supported-regions" target="_blank" rel="noopener noreferrer" class="">supported regions</a> for the latest list.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-set-up-the-terraform-providers">Step 1: Set up the Terraform providers<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#step-1-set-up-the-terraform-providers" class="hash-link" aria-label="Direct link to Step 1: Set up the Terraform providers" title="Direct link to Step 1: Set up the Terraform providers" translate="no">​</a></h2>
<p>Create a new directory for your Terraform configuration and create a file named <code>main.tf</code>. For simplicity, we'll put everything in a single file so it's easy to follow along. In a real project, you'd probably want to split things up into separate files. See the <a href="https://developer.hashicorp.com/terraform/language/style#file-names" target="_blank" rel="noopener noreferrer" class="">Terraform style guide on file names</a> for best practices.</p>
<p>We'll need a few providers: <code>azurerm</code> for Azure resources, <code>azapi</code> for the Anyscale Platform resources, <code>azuread</code> for the service principal lookup, and a couple of utility providers. Add the following to your <code>main.tf</code>:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">terraform {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  required_providers {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    azapi = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      source  = "Azure/azapi"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      version = "~&gt; 2.10.0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    azuread = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      source  = "hashicorp/azuread"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      version = "~&gt; 3.8.0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    azurerm = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      source  = "hashicorp/azurerm"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      version = "~&gt; 4.76.0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    local = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      source  = "hashicorp/local"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      version = "~&gt; 2.9.0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    random = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      source  = "hashicorp/random"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      version = "~&gt; 3.9.0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">provider "azurerm" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  features {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    resource_group {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      prevent_deletion_if_contains_resources = false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>We're using <code>azapi</code> here because the <code>Anyscale.Platform</code> resource type is brand new and not yet in the <code>azurerm</code> provider. The <code>azapi</code> provider lets us work with any ARM resource type directly, including preview ones. At the time of this writing, the <code>Anyscale.Platform</code> resource provider is not yet fully documented. I'll go into how I figured out the API schema for undocumented resources like this when we get to Step 5.</p>
<p>We also set <code>prevent_deletion_if_contains_resources = false</code> on the <code>azurerm</code> provider so that <code>terraform destroy</code> can cleanly remove the resource group without getting blocked by the resources inside it.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-create-the-foundational-resources">Step 2: Create the foundational resources<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#step-2-create-the-foundational-resources" class="hash-link" aria-label="Direct link to Step 2: Create the foundational resources" title="Direct link to Step 2: Create the foundational resources" translate="no">​</a></h2>
<p>Let's start with the basics: a resource group, some random naming helpers, and the storage and container registry resources that Anyscale needs. Add the following to your <code>main.tf</code>:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">variable "location" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  description = "Azure region. Must support Anyscale on Azure."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  type        = string</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  default     = "westus3"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  validation {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    condition     = contains(["westcentralus", "eastus", "eastus2", "westus2", "westus3", "southcentralus"], var.location)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    error_message = "Must be a region that supports Anyscale on Azure."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">data "azurerm_client_config" "current" {}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "random_integer" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  min = 10</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  max = 99</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "random_string" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  length  = 4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  special = false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  upper   = false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  lower   = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  numeric = false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">locals {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  random_name = "anyscale${random_integer.example.result}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_resource_group" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name     = "rg-${local.random_name}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  location = var.location</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>The <code>location</code> variable has a validation block that restricts you to regions where Anyscale on Azure is currently supported. This saves you from potential deployment failures later.</p>
<p>Next, add the container registry and storage account:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_container_registry" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name                = "${local.random_name}${random_string.example.result}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resource_group_name = azurerm_resource_group.example.name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  location            = azurerm_resource_group.example.location</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  sku                 = "Standard"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  admin_enabled       = false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "anyscale-cloud" = local.random_name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_storage_account" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name                            = "${local.random_name}${random_string.example.result}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resource_group_name             = azurerm_resource_group.example.name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  location                        = azurerm_resource_group.example.location</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  account_kind                    = "StorageV2"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  account_tier                    = "Standard"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  account_replication_type        = "LRS"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  access_tier                     = "Hot"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  min_tls_version                 = "TLS1_2"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  https_traffic_only_enabled      = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  is_hns_enabled                  = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  default_to_oauth_authentication = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  shared_access_key_enabled       = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  blob_properties {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    cors_rule {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      allowed_headers    = ["*"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      allowed_methods    = ["DELETE", "GET", "HEAD", "POST", "PUT"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      allowed_origins    = ["https://*.anyscale.com"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      exposed_headers    = ["*"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      max_age_in_seconds = 3600</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "anyscale-cloud" = local.random_name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_storage_container" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name                  = "anyscale-data"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  storage_account_id    = azurerm_storage_account.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  container_access_type = "private"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>A few things to note here. The storage account has <code>is_hns_enabled = true</code> because Anyscale uses Azure Data Lake Storage Gen2 (ADLS Gen2) for its cloud storage. HNS gives you hierarchical file system semantics on top of blob storage, which is what the <code>abfss://</code> URI scheme expects. The CORS rule allows the Anyscale web console to interact with the storage account directly from the browser.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-create-the-aks-cluster-with-gateway-api">Step 3: Create the AKS cluster with Gateway API<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#step-3-create-the-aks-cluster-with-gateway-api" class="hash-link" aria-label="Direct link to Step 3: Create the AKS cluster with Gateway API" title="Direct link to Step 3: Create the AKS cluster with Gateway API" translate="no">​</a></h2>
<p>This is where things get interesting. Instead of using the <code>azurerm_kubernetes_cluster</code> resource, we're using <code>azapi_resource</code> to create the AKS cluster. The managed Gateway API and Istio-based app routing features also <a href="https://learn.microsoft.com/azure/aks/app-routing-gateway-api" target="_blank" rel="noopener noreferrer" class="">went GA recently</a>, but the ability to configure these features hasn't landed in the <code>azurerm</code> provider yet. So we use <code>azapi</code> to hit the AKS API directly. Add the following to your <code>main.tf</code>:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azapi_resource" "aks" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  type      = "Microsoft.ContainerService/managedClusters@2026-03-02-preview"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  parent_id = azurerm_resource_group.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  location  = azurerm_resource_group.example.location</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name      = "aks-${local.random_name}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  schema_validation_enabled = false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  body = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    identity = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      type = "SystemAssigned"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    },</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    properties = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      dnsPrefix = "aks-${local.random_name}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      agentPoolProfiles = [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          name              = "systempool"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          mode              = "System"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          enableAutoScaling = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          minCount          = 3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          maxCount          = 6</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      ingressProfile = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        gatewayAPI = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          installation = "Standard"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        webAppRouting = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          gatewayAPIImplementations = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            appRoutingIstio = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              mode = "Enabled"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      oidcIssuerProfile = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        enabled = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      securityProfile = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        workloadIdentity = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          enabled = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    sku = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      name = "Base"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      tier = "Standard"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  response_export_values = [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "properties.oidcIssuerProfile.issuerURL",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "properties.identityProfile.kubeletidentity.objectId"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Let's break down the key parts of the <code>ingressProfile</code>:</p>
<ul>
<li class=""><strong><code>gatewayAPI.installation = "Standard"</code></strong> installs the Gateway API CRDs and controller on the cluster</li>
<li class=""><strong><code>webAppRouting.gatewayAPIImplementations.appRoutingIstio.mode = "Enabled"</code></strong> enables the Istio-based implementation, which gives you the <code>approuting-istio</code> gateway class</li>
</ul>
<p>We also enable OIDC issuer and workload identity on the cluster. These are required for the Anyscale operator to authenticate with Azure services using federated credentials instead of storing secrets.</p>
<p>The <code>response_export_values</code> block tells <code>azapi</code> to capture the OIDC issuer URL and the kubelet identity object ID from the cluster response, which we'll need for the federated credential and role assignments later.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-set-up-identity-and-role-assignments">Step 4: Set up identity and role assignments<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#step-4-set-up-identity-and-role-assignments" class="hash-link" aria-label="Direct link to Step 4: Set up identity and role assignments" title="Direct link to Step 4: Set up identity and role assignments" translate="no">​</a></h2>
<p>The Anyscale operator needs a managed identity with specific permissions to access resources on your behalf. We'll create a user-assigned managed identity, set up a federated credential so it can authenticate from within the cluster, and assign the necessary roles. Add the following to your <code>main.tf</code>:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azuread_service_principal" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  client_id    = "086bc555-6989-4362-ba30-fded273e432b" # Anyscale Kubernetes Operator Auth</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  use_existing = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_user_assigned_identity" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  location            = azurerm_resource_group.example.location</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name                = "${local.random_name}-operator-identity"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resource_group_name = azurerm_resource_group.example.name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "anyscale-cloud" = local.random_name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_federated_identity_credential" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name                      = azurerm_user_assigned_identity.example.name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  audience                  = ["api://AzureADTokenExchange"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  issuer                    = azapi_resource.aks.output.properties.oidcIssuerProfile.issuerURL</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  user_assigned_identity_id = azurerm_user_assigned_identity.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  subject                   = "system:serviceaccount:anyscale-operator:anyscale-operator"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>The <code>azuread_service_principal</code> resource references the well-known Anyscale Kubernetes Operator Auth application in your tenant. The <code>use_existing = true</code> flag tells Terraform to look up the existing service principal rather than trying to create one. This is needed so the Anyscale operator can authenticate against your cluster.</p>
<p>The federated identity credential is the glue between the Kubernetes service account (<code>anyscale-operator</code> in the <code>anyscale-operator</code> namespace) and the Azure managed identity. When the operator pod starts, it can exchange its Kubernetes service account token for an Azure access token without needing any stored secrets.</p>
<p>Now add the role assignments:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_role_assignment" "acr_pull" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  principal_id                     = azapi_resource.aks.output.properties.identityProfile.kubeletidentity.objectId</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  role_definition_name             = "AcrPull"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  scope                            = azurerm_container_registry.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  skip_service_principal_aad_check = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_role_assignment" "blob_data_owner" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  principal_id                     = azurerm_user_assigned_identity.example.principal_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  role_definition_name             = "Storage Blob Data Owner"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  scope                            = azurerm_storage_account.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  skip_service_principal_aad_check = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_role_assignment" "acr_push" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  principal_id                     = azurerm_user_assigned_identity.example.principal_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  role_definition_name             = "AcrPush"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  scope                            = azurerm_container_registry.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  skip_service_principal_aad_check = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_role_assignment" "acr_task_contributor" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  principal_id                     = azurerm_user_assigned_identity.example.principal_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  role_definition_name             = "Container Registry Tasks Contributor"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  scope                            = azurerm_container_registry.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  skip_service_principal_aad_check = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Here's what each role does:</p>
<ul>
<li class=""><strong>AcrPull</strong> for the kubelet identity so the cluster can pull container images</li>
<li class=""><strong>Storage Blob Data Owner</strong> for the operator identity to read/write data in the storage account</li>
<li class=""><strong>AcrPush</strong> and <strong>Container Registry Tasks Contributor</strong> for the operator identity to push images and run ACR tasks</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-register-the-anyscale-cloud-resources">Step 5: Register the Anyscale Cloud resources<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#step-5-register-the-anyscale-cloud-resources" class="hash-link" aria-label="Direct link to Step 5: Register the Anyscale Cloud resources" title="Direct link to Step 5: Register the Anyscale Cloud resources" translate="no">​</a></h2>
<p>Now we register the Anyscale Cloud and Cloud Resource using the <code>Anyscale.Platform</code> resource provider through <code>azapi</code>. These resources tell the Anyscale control plane about your Azure environment.</p>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>You might be wondering how I figured out the resource type, API version, and payload structure for <code>Anyscale.Platform</code>. At the time of writing, this resource provider isn't documented in the <a href="https://learn.microsoft.com/azure/templates/" target="_blank" rel="noopener noreferrer" class="">Azure resource reference</a>. That's pretty common with newly released services. The trick is to go through the quickstart workflow in the Azure portal, complete the setup wizard, and then export the ARM template from the resource group. The exported template shows you exactly what resource provider, API version, and properties ARM uses under the hood. From there, you can translate it into <code>azapi_resource</code> blocks in Terraform.</p><p>This is a useful technique to keep in your back pocket. Anytime a new Azure service launches and you want to automate it with Terraform before the <code>azurerm</code> provider catches up, the portal's template export gives you the schema you need.</p></div></div>
<div class="theme-admonition theme-admonition-danger admonition_xJq3 alert alert--danger"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>danger</div><div class="admonitionContent_BuS1"><p>Since Anyscale on Azure is in public preview, the <code>Anyscale.Platform</code> API is using a preview version (<code>2026-02-01-preview</code>). There's a good chance the API shape will change as the service moves toward general availability. If you're building on top of this, be prepared to update your Terraform configuration when new API versions are released.</p></div></div>
<p>Add the following to your <code>main.tf</code>:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azapi_resource" "anyscale_cloud" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  type      = "Anyscale.Platform/clouds@2026-02-01-preview"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  parent_id = azurerm_resource_group.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  location  = azurerm_resource_group.example.location</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name      = local.random_name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  schema_validation_enabled = false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  body = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    properties = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      acrResourceId = azurerm_container_registry.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "anyscale-cloud" = local.random_name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  response_export_values = [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "properties.ssoUrl",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "properties.cloudResourceId"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  depends_on = [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    azapi_resource.aks</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">resource "azapi_resource" "anyscale_cloud_resource" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  type      = "Anyscale.Platform/clouds/cloudResources@2026-02-01-preview"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  parent_id = azapi_resource.anyscale_cloud.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  location  = azurerm_resource_group.example.location</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name      = "default"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  schema_validation_enabled = false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  body = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    properties = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      provider                    = "Azure"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      computeStack                = "K8S"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      cloudStorageBucketEndpoint  = azurerm_storage_account.example.primary_blob_endpoint</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      cloudStorageBucketName      = "abfss://${azurerm_storage_container.example.name}@${azurerm_storage_account.example.primary_dfs_host}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      anyscaleOperatorIamIdentity = azurerm_user_assigned_identity.example.principal_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  tags = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "anyscale-cloud" = local.random_name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  response_export_values = [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "properties.cloudResourceId"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  depends_on = [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    azapi_resource.aks</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>The <code>Anyscale.Platform/clouds</code> resource registers a new Anyscale "cloud" (their term for a connected infrastructure environment) and associates it with your container registry. The child <code>cloudResources</code> resource provides the details about compute (Kubernetes), storage (the ADLS Gen2 bucket), and the operator identity.</p>
<p>Notice the <code>cloudStorageBucketName</code> uses the <code>abfss://</code> URI format. This is the Azure Blob File System (ABFS) scheme that ADLS Gen2 uses, and it's how Anyscale accesses your storage.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-install-the-anyscale-operator-extension">Step 6: Install the Anyscale operator extension<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#step-6-install-the-anyscale-operator-extension" class="hash-link" aria-label="Direct link to Step 6: Install the Anyscale operator extension" title="Direct link to Step 6: Install the Anyscale operator extension" translate="no">​</a></h2>
<p>The Anyscale operator is deployed as an AKS cluster extension. This is where we wire up the gateway configuration. Add the following to your <code>main.tf</code>:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_kubernetes_cluster_extension" "anyscale_operator" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name           = "anyscaleoperator"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  cluster_id     = azapi_resource.aks.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  extension_type = "Anyscale.AKS.Operator"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  release_train  = "stable"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  plan {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    name      = "anyscale-operator"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    product   = "anyscale-operator-aks"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    publisher = "anyscale1750870039553"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  configuration_settings = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "global.auth.audience"          = "api://086bc555-6989-4362-ba30-fded273e432b/.default"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "global.auth.iamIdentity"       = azurerm_user_assigned_identity.example.client_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "global.cloudDeploymentId"      = azapi_resource.anyscale_cloud_resource.output.properties.cloudResourceId</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "global.controlPlaneURL"        = "https://console.azure.anyscale.com"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "workloads.serviceAccount.name" = "anyscale-operator"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "networking.gateway.enabled"    = "true"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "networking.gateway.name"       = "gateway"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "networking.gateway.className"  = "approuting-istio"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "networking.gateway.namespace"  = "anyscale-operator"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "networking.gateway.apiVersion" = "gateway.networking.k8s.io/v1"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "networking.gateway.hostname"   = "${replace(azapi_resource.anyscale_cloud_resource.output.properties.cloudResourceId, "_", "-")}.${azapi_resource.aks.location}.cloudapp.azure.com"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  depends_on = [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    azurerm_federated_identity_credential.example</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>The key configuration settings to pay attention to:</p>
<ul>
<li class=""><strong><code>networking.gateway.className = "approuting-istio"</code></strong> tells the operator to use the AKS managed Istio gateway class</li>
<li class=""><strong><code>networking.gateway.apiVersion = "gateway.networking.k8s.io/v1"</code></strong> ensures we're using the GA Gateway API spec</li>
<li class=""><strong><code>networking.gateway.hostname</code></strong> constructs a DNS label that Azure will use to assign a public FQDN to the gateway's load balancer</li>
</ul>
<p>The <code>plan</code> block references the Azure Marketplace listing for the Anyscale operator. This is how Azure knows what extension to install and which publisher it comes from.</p>
<p>We also need a role assignment so you can manage the Anyscale platform resources. Add this to your <code>main.tf</code>:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_role_assignment" "anyscale_platform_contributor" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  principal_id         = data.azurerm_client_config.current.object_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  role_definition_name = "Anyscale Platform Contributor Role"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  scope                = azapi_resource.anyscale_cloud.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-7-outputs-and-the-gateway-template">Step 7: Outputs and the Gateway template<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#step-7-outputs-and-the-gateway-template" class="hash-link" aria-label="Direct link to Step 7: Outputs and the Gateway template" title="Direct link to Step 7: Outputs and the Gateway template" translate="no">​</a></h2>
<p>Before we deploy, we need two more things: Terraform output values so we can grab resource names after the deployment, and a template file for the Gateway manifest.</p>
<p>First, add the <code>local_file</code> resource to your <code>main.tf</code>. It takes a template (which we'll create next) and renders it into <code>anyscale-gateway.yaml</code> with the actual cloud resource ID:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "local_file" "anyscale_gateway" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  filename = "anyscale-gateway.yaml"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  content = templatefile("anyscale-gateway.tmpl",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      ANYSCALE_CLOUD_RESOURCE_ID = replace(azapi_resource.anyscale_cloud_resource.output.properties.cloudResourceId, "_", "-")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  )</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Next, add the following outputs to your <code>main.tf</code>:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">output "rg_name" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  value = azurerm_resource_group.example.name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">output "aks_name" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  value = azapi_resource.aks.name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">output "anyscale_cloud_id" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  value = split("/", azapi_resource.anyscale_cloud.output.properties.ssoUrl)[4]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">output "anyscale_cloud_resource_id" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  value = azapi_resource.anyscale_cloud_resource.output.properties.cloudResourceId</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Finally, create a file named <code>anyscale-gateway.tmpl</code> in the same directory. Terraform uses this template to generate the Gateway manifest with your cloud resource ID injected:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> gateway.networking.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> anyscale</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">operator</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">gatewayClassName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> approuting</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">istio</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">infrastructure</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">annotations</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">service.beta.kubernetes.io/azure-dns-label-name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> $</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">ANYSCALE_CLOUD_RESOURCE_ID</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">listeners</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> http</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">80</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">protocol</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> HTTP</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">allowedRoutes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">namespaces</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">from</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Same</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> https</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">443</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">protocol</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> HTTPS</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">hostname</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"*.i.azure.anyscaleuserdata.com"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">tls</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">mode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Terminate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">certificateRefs</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Secret</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> anyscale</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">$</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">ANYSCALE_CLOUD_RESOURCE_ID</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">certificate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">allowedRoutes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">namespaces</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">from</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Same</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> https</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">session</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">443</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">protocol</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> HTTPS</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">hostname</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"*.s.azure.anyscaleuserdata.com"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">tls</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">mode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Terminate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">certificateRefs</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Secret</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> anyscale</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">$</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">ANYSCALE_CLOUD_RESOURCE_ID</span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">certificate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">allowedRoutes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">namespaces</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">from</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Same</span><br></div></code></pre></div></div>
<p>The Gateway defines three listeners: one for HTTP traffic and two for HTTPS. The HTTPS listeners handle Anyscale's interactive (<code>*.i.</code>) and session (<code>*.s.</code>) subdomains with TLS termination. The <code>azure-dns-label-name</code> annotation tells Azure to assign a public DNS label to the gateway's load balancer IP.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploy-it">Deploy it<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#deploy-it" class="hash-link" aria-label="Direct link to Deploy it" title="Direct link to Deploy it" translate="no">​</a></h2>
<p>With all the Terraform code in place, it's time to deploy. Sign in to Azure and set your subscription:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az login</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az account set -s &lt;subscription-id&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export ARM_SUBSCRIPTION_ID=$(az account show --query id -o tsv)</span><br></div></code></pre></div></div>
<p>Initialize Terraform to download the providers:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">terraform init</span><br></div></code></pre></div></div>
<p>Then format and validate to make sure everything is clean:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">terraform fmt</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">terraform validate</span><br></div></code></pre></div></div>
<p>If validation passes, go ahead and apply:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">terraform apply</span><br></div></code></pre></div></div>
<p>Review the plan and type <code>yes</code> to proceed. The deployment takes about 7-10 minutes since it's creating an AKS cluster, installing the Anyscale operator extension, and registering the cloud resources.</p>
<p>Once it's done, grab the outputs and connect to the cluster:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">read -r \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  RG_NAME \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  AKS_NAME \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ANYSCALE_CLOUD_ID \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ANYSCALE_CLOUD_RESOURCE_ID &lt;&lt;&lt; "$(terraform output -json | jq -r \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    '[.rg_name.value,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      .aks_name.value,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      .anyscale_cloud_id.value,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      .anyscale_cloud_resource_id.value] | @tsv')"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az aks get-credentials -g $RG_NAME -n $AKS_NAME</span><br></div></code></pre></div></div>
<p>Then deploy the gateway:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f anyscale-gateway.yaml</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="verify-the-deployment">Verify the deployment<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#verify-the-deployment" class="hash-link" aria-label="Direct link to Verify the deployment" title="Direct link to Verify the deployment" translate="no">​</a></h2>
<p>Check that the Anyscale operator and gateway-related pods are running:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get po -A</span><br></div></code></pre></div></div>
<p>You should see pods in the <code>anyscale-operator</code> namespace for the operator itself, and Istio-related pods in the gateway infrastructure namespaces.</p>
<p>Now sign in to the Anyscale console. I installed the Anyscale CLI using <a href="https://pipx.pypa.io/stable/" target="_blank" rel="noopener noreferrer" class="">pipx</a>, which I like because it installs Python CLI tools in isolated environments, similar to how <code>npx</code> works in the Node.js world. You get the tool without polluting your global Python packages.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pipx install anyscale</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export ANYSCALE_HOST=https://console.azure.anyscale.com</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">anyscale login</span><br></div></code></pre></div></div>
<p>Verify the cloud is registered and healthy:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">anyscale cloud list</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">anyscale cloud verify --id $ANYSCALE_CLOUD_ID</span><br></div></code></pre></div></div>
<p>The <code>cloud list</code> command should show your cloud with the <code>AZURE</code> provider and your region. The <code>cloud verify</code> command runs a series of checks against your cluster, including operator health, identity, and networking. You want to see all checks come back as <code>PASSED</code>.</p>
<p>From here, you can follow the <a href="https://learn.microsoft.com/azure/anyscale-on-azure/quickstart-azure-cli-gateway-envoy#run-your-first-workload" target="_blank" rel="noopener noreferrer" class="">quickstart guide to run your first workload</a>. You can also open the Anyscale console directly from the Azure portal by navigating to your Anyscale resource and selecting the console link. That's where you can explore workspaces, submit jobs, and manage your Ray clusters.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cleanup">Cleanup<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#cleanup" class="hash-link" aria-label="Direct link to Cleanup" title="Direct link to Cleanup" translate="no">​</a></h2>
<p>When you're done experimenting, tear everything down:</p>
<div class="theme-admonition theme-admonition-danger admonition_xJq3 alert alert--danger"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M5.05.31c.81 2.17.41 3.38-.52 4.31C3.55 5.67 1.98 6.45.9 7.98c-1.45 2.05-1.7 6.53 3.53 7.7-2.2-1.16-2.67-4.52-.3-6.61-.61 2.03.53 3.33 1.94 2.86 1.39-.47 2.3.53 2.27 1.67-.02.78-.31 1.44-1.13 1.81 3.42-.59 4.78-3.42 4.78-5.56 0-2.84-2.53-3.22-1.25-5.61-1.52.13-2.03 1.13-1.89 2.75.09 1.08-1.02 1.8-1.86 1.33-.67-.41-.66-1.19-.06-1.78C8.18 5.31 8.68 2.45 5.05.32L5.03.3l.02.01z"></path></svg></span>danger</div><div class="admonitionContent_BuS1"><p>If you have any running workspaces, make sure to terminate them first. You can do this from the Anyscale console UI or via the CLI:</p><div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">anyscale workspace_v2 terminate --project default -n &lt;workspace-name&gt;</span><br></div></code></pre></div></div><p>During the preview, if the Azure infrastructure is removed while workspaces are still running, those workspaces can end up in a state where they can't fully terminate, which prevents the Anyscale cloud from being deleted. This is expected to improve as the service works towards general availability.</p></div></div>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">terraform destroy</span><br></div></code></pre></div></div>
<p>Review the plan and type <code>yes</code> to confirm. This will remove all the resources, including the AKS cluster, Anyscale cloud registration, storage, and container registry.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>In this post, we walked through deploying Anyscale on Azure using Terraform with AKS managed Gateway API and Istio-based app routing. Along the way, we picked up a useful technique for figuring out API schemas when the docs haven't caught up yet, and saw how the <code>azapi</code> provider lets you automate new Azure services before the <code>azurerm</code> provider adds support.</p>
<p>The full Terraform code is available as a <a href="https://gist.github.com/pauldotyu/4226b4d735f1903c7cb6a0f3d35bc655" target="_blank" rel="noopener noreferrer" class="">GitHub Gist</a>. Clone it, tweak the variables, and you should be up and running in under 10 minutes.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-next">What's next?<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#whats-next" class="hash-link" aria-label="Direct link to What's next?" title="Direct link to What's next?" translate="no">​</a></h3>
<ul>
<li class="">Explore the <a href="https://learn.microsoft.com/azure/anyscale-on-azure/" target="_blank" rel="noopener noreferrer" class="">Anyscale on Azure documentation</a> for more configuration options</li>
<li class="">Check out the <a href="https://docs.ray.io/" target="_blank" rel="noopener noreferrer" class="">Ray documentation</a> for building distributed applications</li>
<li class="">Try scaling your Ray cluster with different node pool configurations and GPU-enabled VMs</li>
<li class="">Look into <a href="https://docs.anyscale.com/workspaces/get-started/" target="_blank" rel="noopener noreferrer" class="">Anyscale workspaces</a> for interactive development on your cluster</li>
</ul>
<p>Have questions or run into issues? Drop us a note in <a href="https://github.com/Azure/AKS/discussions" target="_blank" rel="noopener noreferrer" class="">GitHub Discussions</a> or open an <a href="https://github.com/Azure/AKS/issues/new" target="_blank" rel="noopener noreferrer" class="">issue</a>.</p>
<p>Happy deploying!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="resources">Resources<a href="https://blog.aks.azure.com/2026/06/15/get-anyscale-on-azure-up-and-running-with-terraform#resources" class="hash-link" aria-label="Direct link to Resources" title="Direct link to Resources" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://gist.github.com/pauldotyu/4226b4d735f1903c7cb6a0f3d35bc655" target="_blank" rel="noopener noreferrer" class="">Full Terraform code for this walkthrough</a></li>
<li class=""><a href="https://www.anyscale.com/blog/anyscale-on-azure-public-preview-build-and-deploy-ai-scale" target="_blank" rel="noopener noreferrer" class="">Anyscale on Azure public preview announcement</a></li>
<li class=""><a href="https://learn.microsoft.com/azure/anyscale-on-azure/overview" target="_blank" rel="noopener noreferrer" class="">What is Anyscale on Azure?</a></li>
<li class=""><a href="https://learn.microsoft.com/azure/anyscale-on-azure/quickstart-azure-cli-gateway-envoy" target="_blank" rel="noopener noreferrer" class="">Quickstart: Deploy Anyscale on Azure</a></li>
<li class=""><a href="https://learn.microsoft.com/azure/aks/gateway-api" target="_blank" rel="noopener noreferrer" class="">AKS managed Gateway API</a></li>
<li class=""><a href="https://learn.microsoft.com/azure/aks/app-routing-gateway-api" target="_blank" rel="noopener noreferrer" class="">Istio-based app routing with Gateway API on AKS</a></li>
<li class=""><a href="https://registry.terraform.io/providers/Azure/azapi/latest" target="_blank" rel="noopener noreferrer" class="">AzAPI Terraform provider</a></li>
<li class=""><a href="https://docs.ray.io/" target="_blank" rel="noopener noreferrer" class="">Ray documentation</a></li>
</ul>]]></content:encoded>
            <category>Anyscale</category>
            <category>Ray</category>
            <category>App Routing</category>
            <category>Terraform</category>
        </item>
        <item>
            <title><![CDATA[App Routing Gateway API is GA: Here's a Demo]]></title>
            <link>https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga</link>
            <guid>https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga</guid>
            <pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[App Routing's Gateway API implementation is now GA, with built-in Azure DNS and Azure Key Vault TLS integration plus JSON access logs on gateway proxies.]]></description>
            <content:encoded><![CDATA[<p>The AKS App Routing add-on's Kubernetes Gateway API implementation — <code>approuting-istio</code> — is <strong>generally available</strong>. Together with that, the <strong>Managed Gateway API installation</strong> for AKS is also GA, so the CRDs, the controller stack, and the gateway data plane you need to run Gateway API on AKS are all now first-class, supported features.</p>
<p><img decoding="async" loading="lazy" alt="Cartoon with Key Vault, Istio, and DNS" src="https://blog.aks.azure.com/assets/images/Designer-f63cf63294484f54d29f19ec716bd2f8.jpg" width="1536" height="1024" class="img_ev3q"></p>
<p>Since <a class="" href="https://blog.aks.azure.com/2026/03/18/app-routing-gateway-api">our March preview announcement</a>, we've also shipped two of our most requested capabilities that gated a real production story for Gateway API on AKS:</p>
<ul>
<li class=""><strong>Azure DNS and Azure Key Vault, wired in for you.</strong> No more manually deploying a <code>SecretProviderClass</code>, a sync pod, or a separate <code>external-dns</code> instance just to get a TLS-terminated, DNS-resolvable hostname. Drop two <code>tls.options</code> on a listener, apply an <code>ExternalDNS</code> CR, and you're done.</li>
<li class=""><strong>Access logs, enabled out of the box.</strong> Every gateway proxy writes a structured JSON access log line per request to stdout. <code>kubectl logs</code> the gateway deployment and you're already debugging — no <code>Telemetry</code> resource, no <code>EnvoyFilter</code>, no opt-in flag. This closes one of the last parity gaps with the nginx experience our users are accustomed to.</li>
</ul>
<p>The rest of this post walks through both. We'll create a cluster, expose a sample app over the Gateway API, see access logs land on stdout, and then layer on DNS + TLS using the <code>ClusterExternalDNS</code> and <code>ExternalDNS</code> CRDs plus listener TLS options. A working DNS zone and Key Vault are part of the demo, so by the end you'll have HTTPS records resolving in a zone you own.</p>
<p>:::note Rollout</p>
<p><a href="https://learn.microsoft.com/azure/aks/app-routing-gateway-api" target="_blank" rel="noopener noreferrer" class="">App Routing Istio</a> and the <a href="https://learn.microsoft.com/azure/aks/managed-gateway-api" target="_blank" rel="noopener noreferrer" class="">Managed Gateway API installation</a> both went GA as part of the <strong>AKS 20260428 release</strong>, which has rolled out to all regions — those features are available everywhere today. The DNS/TLS operator integration and access-logging-by-default ship with the <strong>AKS 20260529 release</strong>; track region-by-region progress on the <a href="https://releases.aks.azure.com/AKSRelease" target="_blank" rel="noopener noreferrer" class="">AKS release tracker</a>. Even following the release rolling out to your region, there may be some delays (on the order of a couple of days) that may prevent you from using it. In this case, it's best to use a region that AKS rolled out to earlier.</p>
<p>:::</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-new">What's new<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#whats-new" class="hash-link" aria-label="Direct link to What's new" title="Direct link to What's new" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="access-logs-by-default">Access logs by default<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#access-logs-by-default" class="hash-link" aria-label="Direct link to Access logs by default" title="Direct link to Access logs by default" translate="no">​</a></h3>
<p>Every Envoy gateway proxy that AKS provisions for your <code>Gateway</code> resources now writes one structured JSON access log line per request to its container log out of the box. The very first thing you can do once traffic flows is <code>kubectl logs</code> the gateway deployment and watch requests arrive. No Istio resources needed to manage it, and no opt-in flag. This brings the Gateway API experience closer to parity with the nginx-based experience our users are accustomed to.</p>
<p>Because the logs land on stdout, they flow through the standard container log pipeline, so you can collect them centrally instead of tailing each gateway by hand. Enable the <strong>Azure Monitor (Container Insights) add-on</strong>, and each gateway proxy's access logs are collected into a <a href="https://learn.microsoft.com/azure/azure-monitor/logs/log-analytics-workspace-overview" target="_blank" rel="noopener noreferrer" class="">Log Analytics workspace</a>, where you can query, retain, and alert on them with KQL. You can enable it when you create or update the cluster (<code>az aks create/update --enable-addons monitoring --workspace-resource-id &lt;workspace-id&gt;</code>), or onboard an existing cluster through the portal — see <a href="https://learn.microsoft.com/azure/azure-monitor/containers/kubernetes-monitoring-enable" target="_blank" rel="noopener noreferrer" class="">Enable monitoring for Kubernetes clusters</a> and the <a href="https://learn.microsoft.com/azure/aks/monitor-aks" target="_blank" rel="noopener noreferrer" class="">Monitor AKS</a> overview. Once it's enabled, the per-request access log lines show up in the <a href="https://learn.microsoft.com/azure/azure-monitor/containers/container-insights-logs-schema" target="_blank" rel="noopener noreferrer" class=""><code>ContainerLogV2</code> table</a>, so a query like <code>ContainerLogV2 | where PodName startswith "httpbin-gateway-approuting-istio"</code> gives you every request the gateway served across all replicas.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="dns-and-tls-integration-via-the-app-routing-operator">DNS and TLS integration via the App Routing operator<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#dns-and-tls-integration-via-the-app-routing-operator" class="hash-link" aria-label="Direct link to DNS and TLS integration via the App Routing operator" title="Direct link to DNS and TLS integration via the App Routing operator" translate="no">​</a></h3>
<p>At preview, DNS and TLS automation was a major piece of the ingress-nginx experience the Gateway API path didn't match. That gap is now closed via two paths in the App Routing operator:</p>
<ul>
<li class=""><strong>TLS</strong> — A pair of listener <code>tls.options</code> keys (<code>kubernetes.azure.com/tls-cert-keyvault-uri</code> and <code>kubernetes.azure.com/tls-cert-service-account</code>) tell the operator to provision a <code>SecretProviderClass</code>, sync the certificate from Azure Key Vault as a <code>kubernetes.io/tls</code> Secret, and patch the listener's <code>certificateRefs</code> for you.</li>
<li class=""><strong>DNS</strong> — Two CRDs, <code>ClusterExternalDNS</code> (cluster-scoped) and <code>ExternalDNS</code> (namespace-scoped), let you stand up an <code>external-dns</code> instance scoped to one or more Azure DNS zones and tell it to watch <code>Gateway</code>/<code>HTTPRoute</code>/<code>GRPCRoute</code> resources. Records appear automatically based on the hostnames on your routes.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="demo-from-cluster-create-to-https-with-dns">Demo: from cluster create to HTTPS-with-DNS<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#demo-from-cluster-create-to-https-with-dns" class="hash-link" aria-label="Direct link to Demo: from cluster create to HTTPS-with-DNS" title="Direct link to Demo: from cluster create to HTTPS-with-DNS" translate="no">​</a></h2>
<p>The rest of this post is a hands-on walkthrough that takes you through our newly-GA'd features along with the enhancements we're so excited about. To follow along you'll need:</p>
<ul>
<li class="">Azure CLI 2.86.0 or later (<code>az --version</code> to check, <code>az upgrade</code> to update).</li>
<li class="">An Azure subscription and resource group, plus permission to create resources in it.</li>
<li class="">Sufficient Key Vault RBAC on your own Azure identity to create certificates — <code>Key Vault Certificates Officer</code> is the minimum, <code>Key Vault Administrator</code> is fine. Grant this on the vault you'll create in step 5b before running the cert-creation command.</li>
</ul>
<p>Export these once and reuse them throughout:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">export RESOURCE_GROUP=&lt;your-resource-group&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export CLUSTER=&lt;your-cluster-name&gt;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export LOCATION=&lt;your-azure-region&gt;</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="1-create-the-cluster-or-update-an-existing-one">1. Create the cluster (or update an existing one)<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#1-create-the-cluster-or-update-an-existing-one" class="hash-link" aria-label="Direct link to 1. Create the cluster (or update an existing one)" title="Direct link to 1. Create the cluster (or update an existing one)" translate="no">​</a></h3>
<p>The three flags that matter are <code>--enable-app-routing-istio</code> (turns on the App Routing Gateway API implementation), <code>--enable-gateway-api</code> (installs the AKS-managed Gateway API CRDs), and <code>--enable-app-routing</code> (deploys the App Routing operator, which manages the DNS/TLS integration). All APIs are GA — no preview-feature registration and no <code>aks-preview</code> extension required.</p>
<p><strong>On a new cluster:</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aks create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group $RESOURCE_GROUP \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name $CLUSTER \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --location $LOCATION \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-app-routing-istio \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-app-routing \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-gateway-api \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-oidc-issuer \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-workload-identity \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-addons azure-keyvault-secrets-provider</span><br></div></code></pre></div></div>
<p><strong>On an existing cluster:</strong></p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aks update \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group $RESOURCE_GROUP \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name $CLUSTER \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-app-routing \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-app-routing-istio \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-gateway-api \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-oidc-issuer \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-workload-identity</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># CSI addon needs its own command on existing clusters</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az aks enable-addons \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group $RESOURCE_GROUP \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name $CLUSTER \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --addons azure-keyvault-secrets-provider</span><br></div></code></pre></div></div>
<p><code>--enable-oidc-issuer</code> and <code>--enable-workload-identity</code> are required for the DNS/TLS demo later — both DNS and TLS use Microsoft Entra Workload Identity to authenticate to Azure DNS and Key Vault. The <code>azure-keyvault-secrets-provider</code> addon installs the Secrets Store CSI Driver, which the operator uses to materialize Key Vault certificates as Kubernetes Secrets. Set them all now even if you don't have a workload to expose yet.</p>
<p>Pull credentials so <code>kubectl</code> works:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aks get-credentials --resource-group $RESOURCE_GROUP --name $CLUSTER</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="2-confirm-the-control-plane-is-up">2. Confirm the control plane is up<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#2-confirm-the-control-plane-is-up" class="hash-link" aria-label="Direct link to 2. Confirm the control plane is up" title="Direct link to 2. Confirm the control plane is up" translate="no">​</a></h3>
<p>The App Routing Gateway API implementation runs <code>istiod</code> in the <code>aks-istio-system</code> namespace.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get pods -n aks-istio-system</span><br></div></code></pre></div></div>
<div class="language-output codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-output codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">NAME                      READY   STATUS    RESTARTS   AGE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">istiod-54b4ff45cf-htph8   1/1     Running   0          3m15s</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">istiod-54b4ff45cf-wlvgd   1/1     Running   0          3m</span><br></div></code></pre></div></div>
<p>You'll also see the <code>app-routing-system</code> namespace with NGINX still running:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get pods -n app-routing-system</span><br></div></code></pre></div></div>
<p>You can ignore this for the rest of the demo. NGINX-by-default is on its way out as part of the <a class="" href="https://blog.aks.azure.com/2026/03/18/app-routing-gateway-api#why-now-the-ingress-nginx-retirement">Ingress-NGINX retirement</a>. Once we stop deploying it after November 2026, it won't appear for users by default.</p>
<p>The <code>approuting-istio</code> <code>GatewayClass</code> is what you'll target with your <code>Gateway</code> resources:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get gatewayclass approuting-istio</span><br></div></code></pre></div></div>
<div class="language-output codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-output codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">NAME               CONTROLLER                    ACCEPTED   AGE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">approuting-istio   istio.aks.azure.com/gateway-controller   True       4m</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="3-expose-a-sample-app-via-gateway-api">3. Expose a sample app via Gateway API<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#3-expose-a-sample-app-via-gateway-api" class="hash-link" aria-label="Direct link to 3. Expose a sample app via Gateway API" title="Direct link to 3. Expose a sample app via Gateway API" translate="no">​</a></h3>
<p>We'll deploy <code>httpbin</code>, front it with a single <code>Gateway</code> listener over plain HTTP, attach an <code>HTTPRoute</code>, and verify traffic flows. (We'll make this HTTPS in section 5.)</p>
<p>Deploy <code>httpbin</code> in the <code>default</code> namespace:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f https://raw.githubusercontent.com/istio/istio/release-1.27/samples/httpbin/httpbin.yaml</span><br></div></code></pre></div></div>
<p>Create the <code>Gateway</code> and <code>HTTPRoute</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f - &lt;&lt;EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">apiVersion: gateway.networking.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kind: Gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">metadata:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name: httpbin-gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">spec:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  gatewayClassName: approuting-istio</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  listeners:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - name: http</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    port: 80</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    protocol: HTTP</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    allowedRoutes:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      namespaces:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        from: Same</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">---</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">apiVersion: gateway.networking.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kind: HTTPRoute</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">metadata:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name: httpbin</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">spec:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  parentRefs:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - name: httpbin-gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  hostnames: ["httpbin.example.com"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  rules:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - matches:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    - path:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        type: PathPrefix</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        value: /get</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    backendRefs:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    - name: httpbin</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      port: 8000</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></div></code></pre></div></div>
<p>AKS provisions the Envoy <code>Deployment</code>, <code>Service</code>, <code>HorizontalPodAutoscaler</code>, and <code>PodDisruptionBudget</code> for the <code>Gateway</code> automatically. Wait for the Gateway to be programmed and grab its external IP:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl wait --for=condition=programmed gateways.gateway.networking.k8s.io httpbin-gateway --timeout=120s</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export INGRESS_HOST=$(kubectl get gateways.gateway.networking.k8s.io httpbin-gateway -ojsonpath='{.status.addresses[0].value}')</span><br></div></code></pre></div></div>
<p>Send a request:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl -s -I -H "Host: httpbin.example.com" "http://$INGRESS_HOST/get"</span><br></div></code></pre></div></div>
<p>You should see an <code>HTTP/1.1 200 OK</code>.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="4-see-access-logs-on-the-gateway-proxy">4. See access logs on the gateway proxy<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#4-see-access-logs-on-the-gateway-proxy" class="hash-link" aria-label="Direct link to 4. See access logs on the gateway proxy" title="Direct link to 4. See access logs on the gateway proxy" translate="no">​</a></h3>
<p>Each <code>Gateway</code> resource is backed by a managed <code>Deployment</code> named <code>&lt;gateway-name&gt;-approuting-istio</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get deployment httpbin-gateway-approuting-istio</span><br></div></code></pre></div></div>
<div class="language-output codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-output codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">NAME                               READY   UP-TO-DATE   AVAILABLE   AGE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">httpbin-gateway-approuting-istio   2/2     2            2           1m</span><br></div></code></pre></div></div>
<p>Tail its logs and re-issue a curl:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl logs deploy/httpbin-gateway-approuting-istio --tail=20 -f</span><br></div></code></pre></div></div>
<p>You should see an access log for each of your requests. That's the JSON-formatted Envoy access log written to stdout, on by default — useful for sanity checks, post-mortem analysis, or shipping into Azure Monitor / Log Analytics via the standard container log pipeline.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="5-add-azure-dns-and-tls">5. Add Azure DNS and TLS<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#5-add-azure-dns-and-tls" class="hash-link" aria-label="Direct link to 5. Add Azure DNS and TLS" title="Direct link to 5. Add Azure DNS and TLS" translate="no">​</a></h3>
<p>Now for the best part (well, at least in my opinion). We'll create an Azure DNS zone we own, an Azure Key Vault with a real cert for a hostname in that zone, a user-assigned managed identity that can write to both, and then wire everything in via two CRDs and two listener <code>tls.options</code>.</p>
<p>For this demo we'll use the zone <code>ga-demo.dev</code> with two sub-hosts, <code>a.ga-demo.dev</code> and <code>b.ga-demo.dev</code>. Substitute your own zone name.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">export ZONE_NAME=ga-demo.dev</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export KV_NAME=approuting-ga-demo-kv  # must be globally unique</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export UAMI_NAME=approuting-ga-demo-id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export SA_NAME=approuting-demo-sa</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="5a-create-an-azure-dns-zone">5a. Create an Azure DNS zone<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#5a-create-an-azure-dns-zone" class="hash-link" aria-label="Direct link to 5a. Create an Azure DNS zone" title="Direct link to 5a. Create an Azure DNS zone" translate="no">​</a></h4>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az network dns zone create --resource-group $RESOURCE_GROUP --name $ZONE_NAME</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export ZONE_ID=$(az network dns zone show --resource-group $RESOURCE_GROUP --name $ZONE_NAME --query id -o tsv)</span><br></div></code></pre></div></div>
<p>The zone's authoritative nameservers are listed under <code>az network dns zone show ... --query nameServers</code>. You can find this on the Azure portal too. If you're using a registrar-owned domain in production, you'd point the registrar at those NS records. But for a one-off demo, you can resolve through the zone NS directly with <code>dig @&lt;ns&gt; a.${ZONE_NAME}</code> which is what we'll do later to ensure our curl resolves correctly.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="5b-create-an-azure-key-vault-and-a-self-signed-wildcard-cert">5b. Create an Azure Key Vault and a self-signed wildcard cert<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#5b-create-an-azure-key-vault-and-a-self-signed-wildcard-cert" class="hash-link" aria-label="Direct link to 5b. Create an Azure Key Vault and a self-signed wildcard cert" title="Direct link to 5b. Create an Azure Key Vault and a self-signed wildcard cert" translate="no">​</a></h4>
<p>We'll mint a self-signed wildcard cert directly in Key Vault so it covers both <code>a.ga-demo.dev</code> and <code>b.ga-demo.dev</code> from a single certificate. (For production, import a CA-issued PFX with <code>az keyvault certificate import</code> instead.)</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az keyvault create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name $KV_NAME \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group $RESOURCE_GROUP \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --location $LOCATION \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --enable-rbac-authorization true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Cert policy: wildcard subject + SAN so one cert covers every sub-host of the zone</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">cat &gt; /tmp/cert-policy.json &lt;&lt;EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">{</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "issuerParameters": { "name": "Self" },</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "keyProperties": { "exportable": true, "keyType": "RSA", "keySize": 2048, "reuseKey": false },</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "secretProperties": { "contentType": "application/x-pkcs12" },</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "x509CertificateProperties": {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "subject": "CN=*.${ZONE_NAME}",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "subjectAlternativeNames": { "dnsNames": ["*.${ZONE_NAME}", "${ZONE_NAME}"] },</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "validityInMonths": 12,</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "keyUsage": ["digitalSignature", "keyEncipherment"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az keyvault certificate create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --vault-name $KV_NAME \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name approuting-demo-cert \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --policy @/tmp/cert-policy.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Capture the unversioned cert URI so the listener tracks rotations automatically</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export CERT_URI=$(az keyvault certificate show \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --vault-name $KV_NAME \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name approuting-demo-cert \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --query id -o tsv | sed 's|/[^/]*$||')</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "Cert URI: $CERT_URI"</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="5c-create-a-managed-identity-role-assignments-and-federated-credentials">5c. Create a managed identity, role assignments, and federated credentials<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#5c-create-a-managed-identity-role-assignments-and-federated-credentials" class="hash-link" aria-label="Direct link to 5c. Create a managed identity, role assignments, and federated credentials" title="Direct link to 5c. Create a managed identity, role assignments, and federated credentials" translate="no">​</a></h4>
<p>Both the operator's DNS controller and the gateway listener's TLS sync authenticate to Azure as a user-assigned managed identity (UAMI), federated to a Kubernetes ServiceAccount via the cluster's OIDC issuer.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># 1. Create the UAMI</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az identity create --resource-group $RESOURCE_GROUP --name $UAMI_NAME</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export UAMI_CLIENT_ID=$(az identity show --resource-group $RESOURCE_GROUP --name $UAMI_NAME --query clientId -o tsv)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export UAMI_PRINCIPAL_ID=$(az identity show --resource-group $RESOURCE_GROUP --name $UAMI_NAME --query principalId -o tsv)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 2. Grant DNS Zone Contributor on the zone, Key Vault Secrets User on the vault</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az role assignment create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --assignee-object-id $UAMI_PRINCIPAL_ID \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --assignee-principal-type ServicePrincipal \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --role "DNS Zone Contributor" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --scope $ZONE_ID</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az role assignment create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --assignee-object-id $UAMI_PRINCIPAL_ID \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --assignee-principal-type ServicePrincipal \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --role "Key Vault Secrets User" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --scope $(az keyvault show --name $KV_NAME --query id -o tsv)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export OIDC_ISSUER=$(az aks show --resource-group $RESOURCE_GROUP --name $CLUSTER --query oidcIssuerProfile.issuerUrl -o tsv)</span><br></div></code></pre></div></div>
<p>We'll create the <code>app-a</code> and <code>app-b</code> namespaces now and federate the same UAMI to a <code>$SA_NAME</code> ServiceAccount in each. Federated credentials are subject-specific — each <code>(namespace, ServiceAccount)</code> pair needs its own:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">for ns in app-a app-b; do</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  kubectl create namespace $ns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  az identity federated-credential create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --identity-name $UAMI_NAME \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --resource-group $RESOURCE_GROUP \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --name approuting-demo-fic-$ns \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --issuer $OIDC_ISSUER \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --subject "system:serviceaccount:$ns:$SA_NAME" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    --audiences "api://AzureADTokenExchange"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  kubectl apply -n $ns -f - &lt;&lt;EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">apiVersion: v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kind: ServiceAccount</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">metadata:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name: $SA_NAME</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  annotations:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    azure.workload.identity/client-id: $UAMI_CLIENT_ID</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  labels:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    azure.workload.identity/use: "true"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">done</span><br></div></code></pre></div></div>
<p>The <code>azure.workload.identity/use: "true"</code> label is required; it's what tells the AKS Workload Identity webhook to inject the projected OIDC token volume and the <code>AZURE_*</code> env vars into pods that consume this SA. Without it, the operator's placeholder pod gets a default Kubernetes SA token instead of one signed by your cluster's OIDC issuer, and Entra rejects it with <code>AADSTS700211: No matching federated identity record</code>.</p>
<p>The same ServiceAccount drives both the DNS controller and the listener's TLS sync. One identity, two roles in Azure, one SA per namespace.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="5d-deploy-two-gateways-in-two-namespaces">5d. Deploy two gateways in two namespaces<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#5d-deploy-two-gateways-in-two-namespaces" class="hash-link" aria-label="Direct link to 5d. Deploy two gateways in two namespaces" title="Direct link to 5d. Deploy two gateways in two namespaces" translate="no">​</a></h4>
<p>Deploy <code>httpbin</code> in each namespace, then create one <code>Gateway</code> per namespace with an HTTPS listener pointing at the Key Vault cert URI and the SA. Each gateway covers its own sub-host:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">for ns in app-a app-b; do</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  kubectl apply -n $ns -f https://raw.githubusercontent.com/istio/istio/release-1.27/samples/httpbin/httpbin.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">done</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">for pair in "app-a:a" "app-b:b"; do</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  ns=${pair%%:*}; sub=${pair##*:}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  fqdn=${sub}.${ZONE_NAME}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  kubectl apply -n $ns -f - &lt;&lt;EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">apiVersion: gateway.networking.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kind: Gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">metadata:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name: ${sub}-gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  labels:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    app: approuting-demo</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    zone: ${sub}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">spec:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  gatewayClassName: approuting-istio</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  listeners:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - name: https</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    hostname: $fqdn</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    port: 443</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    protocol: HTTPS</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    tls:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      mode: Terminate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      options:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        kubernetes.azure.com/tls-cert-keyvault-uri: $CERT_URI</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        kubernetes.azure.com/tls-cert-service-account: $SA_NAME</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    allowedRoutes:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      namespaces:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        from: Same</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">---</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">apiVersion: gateway.networking.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kind: HTTPRoute</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">metadata:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name: ${sub}-route</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">spec:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  parentRefs:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - name: ${sub}-gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  hostnames: ["$fqdn"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  rules:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - matches:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    - path:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        type: PathPrefix</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        value: /get</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    backendRefs:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    - name: httpbin</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      port: 8000</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">done</span><br></div></code></pre></div></div>
<p>The TLS options tell the App Routing operator to:</p>
<ol>
<li class="">Resolve <code>$CERT_URI</code> against the Key Vault, using the workload identity bound to <code>$SA_NAME</code> in the listener's namespace.</li>
<li class="">Provision a <code>SecretProviderClass</code> named <code>kv-gw-cert-&lt;gateway&gt;-&lt;listener&gt;</code>.</li>
<li class="">Sync the certificate as a <code>kubernetes.io/tls</code> Secret of the same name in the listener's namespace.</li>
<li class="">Patch the listener's <code>certificateRefs</code> to point at the Secret.</li>
</ol>
<p>You can confirm the SPCs and Secrets exist:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get secretproviderclass,secret -n app-a</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get secretproviderclass,secret -n app-b</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="5e-scenario-a--clusterexternaldns-records-for-both-gateways">5e. Scenario A — <code>ClusterExternalDNS</code>: records for both gateways<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#5e-scenario-a--clusterexternaldns-records-for-both-gateways" class="hash-link" aria-label="Direct link to 5e-scenario-a--clusterexternaldns-records-for-both-gateways" title="Direct link to 5e-scenario-a--clusterexternaldns-records-for-both-gateways" translate="no">​</a></h4>
<p><code>ClusterExternalDNS</code> is cluster-scoped: a single CR points at one or more Azure DNS zones and watches Gateway resources cluster-wide. The operator deploys an <code>external-dns</code> instance in the namespace named under <code>resourceNamespace</code>; the SA in that namespace authenticates to Azure DNS.</p>
<p>Apply it:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f - &lt;&lt;EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">apiVersion: approuting.kubernetes.azure.com/v1alpha1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kind: ClusterExternalDNS</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">metadata:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name: demo-cluster-dns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">spec:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resourceName: demo-cluster-dns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resourceNamespace: app-a</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  dnsZoneResourceIDs:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - $ZONE_ID</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resourceTypes:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  identity:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    type: workloadIdentity</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    serviceAccount: $SA_NAME</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></div></code></pre></div></div>
<p>After a minute, both records show up in the zone:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az network dns record-set a list --resource-group $RESOURCE_GROUP --zone-name $ZONE_NAME -o table</span><br></div></code></pre></div></div>
<div class="language-output codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-output codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Name    ResourceGroup              Ttl    Type    AutoRegistered    Metadata</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">------  -------------------------  -----  ------  ----------------  --------</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">a       &lt;your-rg&gt;                  300    A       False</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">b       &lt;your-rg&gt;                  300    A       False</span><br></div></code></pre></div></div>
<p>That's <code>ClusterExternalDNS</code>: one CR, one <code>external-dns</code> deployment, records for both gateways across both namespaces.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="5f-scenario-b--externaldns-namespace-scoped-with-label-filtering">5f. Scenario B — <code>ExternalDNS</code>: namespace-scoped, with label filtering<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#5f-scenario-b--externaldns-namespace-scoped-with-label-filtering" class="hash-link" aria-label="Direct link to 5f-scenario-b--externaldns-namespace-scoped-with-label-filtering" title="Direct link to 5f-scenario-b--externaldns-namespace-scoped-with-label-filtering" translate="no">​</a></h4>
<p>Now flip the model. Delete the <code>ClusterExternalDNS</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl delete clusterexternaldns demo-cluster-dns</span><br></div></code></pre></div></div>
<p>To make the namespace + label scope concrete, deploy a <strong>third</strong> gateway in <code>app-a</code> labeled <code>zone=c</code>. We'll point a namespace-scoped <code>ExternalDNS</code> at it and watch a fresh record appear — without touching the existing <code>a</code> and <code>b</code> records:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -n app-a -f - &lt;&lt;EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">apiVersion: gateway.networking.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kind: Gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">metadata:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name: c-gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  labels:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    app: approuting-demo</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    zone: c</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">spec:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  gatewayClassName: approuting-istio</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  listeners:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - name: https</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    hostname: c.${ZONE_NAME}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    port: 443</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    protocol: HTTPS</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    tls:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      mode: Terminate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      options:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        kubernetes.azure.com/tls-cert-keyvault-uri: $CERT_URI</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        kubernetes.azure.com/tls-cert-service-account: $SA_NAME</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    allowedRoutes:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      namespaces:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        from: Same</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">---</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">apiVersion: gateway.networking.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kind: HTTPRoute</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">metadata:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name: c-route</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">spec:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  parentRefs:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - name: c-gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  hostnames: ["c.${ZONE_NAME}"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  rules:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - matches:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    - path:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        type: PathPrefix</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        value: /get</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    backendRefs:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    - name: httpbin</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      port: 8000</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl wait -n app-a --for=condition=programmed gateway c-gateway --timeout=240s</span><br></div></code></pre></div></div>
<p>Now create a namespace-scoped <code>ExternalDNS</code> in <code>app-a</code> with a label filter for <code>zone=c</code>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -n app-a -f - &lt;&lt;EOF</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">apiVersion: approuting.kubernetes.azure.com/v1alpha1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kind: ExternalDNS</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">metadata:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name: demo-ns-dns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">spec:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resourceName: demo-ns-dns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  dnsZoneResourceIDs:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - $ZONE_ID</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resourceTypes:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  identity:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    type: workloadIdentity</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    serviceAccount: $SA_NAME</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  filters:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    gatewayLabels: "zone=c"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOF</span><br></div></code></pre></div></div>
<p>Two scoping rules apply at once:</p>
<ol>
<li class=""><strong>Namespace scope</strong> — <code>ExternalDNS</code> only watches resources in its own namespace, so <code>b-gateway</code> in <code>app-b</code> is invisible to it.</li>
<li class=""><strong>Label filter</strong> — within <code>app-a</code>, only gateways carrying <code>zone=c</code> are picked up. The label key/value is rendered into the underlying <code>external-dns</code> controller's label selector.</li>
</ol>
<p>After about a minute, a new <code>c</code> record appears alongside whatever was already there from the cluster-scoped run:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az network dns record-set a list --resource-group $RESOURCE_GROUP --zone-name $ZONE_NAME -o table</span><br></div></code></pre></div></div>
<div class="language-output codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-output codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">Name    ResourceGroup              Ttl    Type    AutoRegistered    Metadata</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">------  -------------------------  -----  ------  ----------------  --------</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">a       &lt;your-rg&gt;                  300    A       False</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">b       &lt;your-rg&gt;                  300    A       False</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">c       &lt;your-rg&gt;                  300    A       False</span><br></div></code></pre></div></div>
<p>The new namespace-scoped controller is responsible for <code>c</code> only. <code>a-gateway</code> (also in <code>app-a</code>) is excluded by the label filter; <code>b-gateway</code> is excluded by the namespace filter.</p>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="5g-verify-https-end-to-end">5g. Verify HTTPS end-to-end<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#5g-verify-https-end-to-end" class="hash-link" aria-label="Direct link to 5g. Verify HTTPS end-to-end" title="Direct link to 5g. Verify HTTPS end-to-end" translate="no">​</a></h4>
<p>Grab the zone's authoritative nameserver, resolve the FQDN through it, and feed the result straight into <code>curl --resolve</code>. That keeps the test self-contained — you don't need the registrar pointing at Azure DNS or your local resolver to know about the zone:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">NS=$(az network dns zone show --resource-group $RESOURCE_GROUP --name $ZONE_NAME --query 'nameServers[0]' -o tsv | sed 's/\.$//')</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">GATEWAY_IP=$(dig +short @${NS} a.${ZONE_NAME} | tail -1)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "Resolved a.${ZONE_NAME} -&gt; $GATEWAY_IP via $NS"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Self-signed cert, so -k. Use --cacert if you exported the chain.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">curl -k -I --resolve "a.${ZONE_NAME}:443:${GATEWAY_IP}" "https://a.${ZONE_NAME}/get"</span><br></div></code></pre></div></div>
<p>You should see <code>HTTP/2 200</code>. The certificate Envoy presents is the one synced from Key Vault. You can re-tail the gateway deployment's logs (<code>kubectl logs deploy/a-gateway-approuting-istio -n app-a</code>) to see the access log line for the request.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="next-steps">Next steps<a href="https://blog.aks.azure.com/2026/06/10/app-routing-gateway-api-ga#next-steps" class="hash-link" aria-label="Direct link to Next steps" title="Direct link to Next steps" translate="no">​</a></h2>
<ul>
<li class=""><strong>Migrate</strong>: If you're on the NGINX add-on, see the <a href="https://learn.microsoft.com/azure/aks/app-routing-nginx-to-gateway-api-migration" target="_blank" rel="noopener noreferrer" class="">Ingress to Gateway API migration guide</a>. Managed NGINX support continues through November 2026.</li>
<li class=""><strong>Deepen</strong>: The full reference docs are at <a href="https://learn.microsoft.com/azure/aks/app-routing-gateway-api" target="_blank" rel="noopener noreferrer" class="">Configure ingress with the Kubernetes Gateway API</a> and <a href="https://learn.microsoft.com/azure/aks/app-routing-gateway-api-tls" target="_blank" rel="noopener noreferrer" class="">Secure ingress with the application routing Gateway API implementation</a>.</li>
<li class=""><strong>Track</strong>: <a href="https://releases.aks.azure.com/AKSRelease" target="_blank" rel="noopener noreferrer" class="">AKS release tracker</a> and the <a href="https://github.com/Azure/AKS/releases" target="_blank" rel="noopener noreferrer" class="">AKS release notes</a>.</li>
<li class=""><strong>Feedback</strong>: Open an issue at <a href="https://github.com/Azure/AKS/issues" target="_blank" rel="noopener noreferrer" class="">Azure/AKS issues</a>.</li>
</ul>]]></content:encoded>
            <category>App Routing</category>
            <category>Gateway API</category>
            <category>Networking</category>
            <category>Istio</category>
            <category>DNS</category>
        </item>
        <item>
            <title><![CDATA[Announcing the public preview of AKS on bare metal]]></title>
            <link>https://blog.aks.azure.com/2026/06/02/aks-baremetal-public-preview</link>
            <guid>https://blog.aks.azure.com/2026/06/02/aks-baremetal-public-preview</guid>
            <pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[AKS on bare metal is now available in public preview!]]></description>
            <content:encoded><![CDATA[<p><em>AKS runs everywhere — now on bare metal at the edge</em></p>
<p>Our vision for Azure Kubernetes Service has always been simple: AKS runs everywhere. The same Azure Kubernetes Service you run across Azure regions is the same platform you run in sovereign and private clouds, and on OEM hardware. One platform, every substrate — so that the skills, the tooling, and the operational model you already know come with you wherever your applications need to run.</p>
<p>Today, we're adding the newest member to that family — and it answers one of the strongest signals we've heard from customers at the edge. <strong>AKS now runs directly on bare-metal, small-form-factor devices at the edge, available today in public preview.</strong></p>
<p><img decoding="async" loading="lazy" alt="An overview of the AKS on bare metal resource in Azure Portal" src="https://blog.aks.azure.com/assets/images/AKS-on-baremetal-Overview-2a78eabb61792ef54d7e1bd5bcd7a1b1.png" width="2839" height="1066" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-bare-metal">Why bare metal<a href="https://blog.aks.azure.com/2026/06/02/aks-baremetal-public-preview#why-bare-metal" class="hash-link" aria-label="Direct link to Why bare metal" title="Direct link to Why bare metal" translate="no">​</a></h2>
<p>For years, customers have told us the same thing: the edge forces hard trade-offs on Kubernetes. They want the consistency of a managed Kubernetes platform in the places where their business actually happens — the retail back room, the factory floor, the quick-service restaurant, the branch office, the remote site. But the edge is not the datacenter. Space, power, and budget are constrained. Hardware is small and sometimes ruggedized. Connectivity is intermittent, and sites often need to keep running through disconnection. Given the Kubernetes control plane runs locally on the device, deployed workloads continue to operate normally during connectivity loss. Only portal visibility and Azure management actions are interrupted until the connection is restored.</p>
<p>What customers have asked us for is precise: run real Kubernetes directly on the hardware, with no hypervisor underneath and no virtualization layer to license or maintain; do it on lightweight edge hardware rather than datacenter-class servers; and let it keep operating through intermittent connectivity.</p>
<p>That is exactly what AKS on bare metal delivers. This is Azure Kubernetes Service running directly on the device with no hypervisor, on small-form-factor hardware, and resilient to intermittent connectivity. It is not a different product for the edge. It is the same AKS, supporting a new class of hardware at the edge.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="from-power-on-to-a-running-aks-cluster">From power-on to a running AKS cluster<a href="https://blog.aks.azure.com/2026/06/02/aks-baremetal-public-preview#from-power-on-to-a-running-aks-cluster" class="hash-link" aria-label="Direct link to From power-on to a running AKS cluster" title="Direct link to From power-on to a running AKS cluster" translate="no">​</a></h2>
<p>The experience is deliberately simple. On-site, someone plugs in a USB drive with an OS image from the Azure portal, powers on the device, waits a couple of minutes, and removes the drive. That's the only step that happens at the location. During this <a href="https://techcommunity.microsoft.com/blog/azurearcblog/announcing-public-preview-simplified-machine-provisioning-for-azure-local/4496811" target="_blank" rel="noopener noreferrer" class="">zero-touch provisioning</a> process, a device voucher is written back to the USB drive and all configuration shifts to Azure — no deep infrastructure expertise required on site.</p>
<p>From there, everything happens in Azure. The machine is onboarded through the <a href="https://learn.microsoft.com/en-us/azure/azure-local/whats-new" target="_blank" rel="noopener noreferrer" class="">Azure Local small form factor</a> creation experience — create a site, upload the voucher, and the machine comes up as Provisioned. The cluster creation looks exactly like AKS anywhere else: configure Azure RBAC, set the control-plane networking, enable Azure Monitor, and deploy. When it completes, you have an AKS cluster running on Azure Linux, on bare metal, fully manageable from Azure: workloads, services, ingress, and Kubernetes upgrades, all without leaving the portal.</p>
<p><img decoding="async" loading="lazy" alt="Upgrade your AKS on bare metal cluster via the Azure Portal" src="https://blog.aks.azure.com/assets/images/aks-on-baremetal-upgrade-66b3fc6408b6af0aea7e0c0668c5c593.png" width="1630" height="1059" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="one-consistent-aks-experience-from-cloud-to-edge">One consistent AKS experience, from cloud to edge<a href="https://blog.aks.azure.com/2026/06/02/aks-baremetal-public-preview#one-consistent-aks-experience-from-cloud-to-edge" class="hash-link" aria-label="Direct link to One consistent AKS experience, from cloud to edge" title="Direct link to One consistent AKS experience, from cloud to edge" translate="no">​</a></h2>
<p>Provisioning is only half the story. What matters at the edge is how you operate the cluster over time — and here the principle is simple: AKS on bare metal is just an AKS cluster. There's no edge-specific console, no separate operational model, no exceptions to learn.</p>
<p>Because it's a first-class AKS cluster, it shows up in Azure Kubernetes Fleet Manager right alongside your cloud clusters, managed and monitored with the same controls and the same tooling you already use everywhere else. The same AKS, the same experience, consistent across cloud and edge — now including bare metal.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="available-today">Available today<a href="https://blog.aks.azure.com/2026/06/02/aks-baremetal-public-preview#available-today" class="hash-link" aria-label="Direct link to Available today" title="Direct link to Available today" translate="no">​</a></h2>
<p>This is what "AKS everywhere" was always meant to become: not a fragmented collection of edge-specific products and tools, but one Kubernetes platform that follows your applications to every place they need to run — now including the smallest, most constrained, and most remote hardware at the edge.</p>
<p><strong>AKS on bare metal is now in public preview.</strong> We can't wait to see what you build on it.</p>
<p>Join the preview today at <a href="https://aka.ms/aks-edge-baremetal" target="_blank" rel="noopener noreferrer" class="">aka.ms/aks-edge-baremetal</a>.</p>]]></content:encoded>
            <category>AKS on bare metal</category>
            <category>Azure Kubernetes Fleet Manager</category>
        </item>
        <item>
            <title><![CDATA[Scaling multi-node LLM inference with NVIDIA Dynamo-Grove on AKS (Part 4)]]></title>
            <link>https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4</link>
            <guid>https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4</guid>
            <pubDate>Tue, 02 Jun 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how NVIDIA Grove scheduler works with Dynamo to schedule, start, and scale inference deployments to match the underlying inference architecture.]]></description>
            <content:encoded><![CDATA[<p><em>This blog post is co-authored with <a href="https://www.linkedin.com/in/nikharmaheshwari/" target="_blank" rel="noopener noreferrer" class="">Nikhar Maheshwari</a>,
<a href="https://www.linkedin.com/in/anish-maddipoti/" target="_blank" rel="noopener noreferrer" class="">Anish Maddipoti</a>,
<a href="https://www.linkedin.com/in/rohan-s-varma/" target="_blank" rel="noopener noreferrer" class="">Rohan Varma</a>,
<a href="https://www.linkedin.com/in/clement-ai/" target="_blank" rel="noopener noreferrer" class="">Clement Pakkam Isaac</a>, and
<a href="https://www.linkedin.com/in/stephen-mcc/" target="_blank" rel="noopener noreferrer" class="">Stephen Mccoulough</a> from NVIDIA.</em></p>
<p>In the <a href="https://blog.aks.azure.com/tags/dynamo-series" target="_blank" rel="noopener noreferrer" class="">first three blog posts</a> of this series, we introduced <a href="https://www.nvidia.com/en-us/ai/dynamo/" target="_blank" rel="noopener noreferrer" class="">NVIDIA Dynamo</a> on AKS, covered SLO-driven scaling with the Dynamo Planner and Profiler, and explored KV-cache-aware routing to establish the foundations for high-performance LLM serving. In this post, we move up one layer of the inference stack: how to describe and operate the distributed inference workload on a Kubernetes cluster.</p>
<p>As inference deployments evolve from simple model-serving replicas to systems with disaggregated prefill and decode phases, multi-node model instances, and explicit startup dependencies, an inference deployment benefits from a single API that describes the full serving pipeline: which roles exist, how they relate, and what constitutes a <strong>deployable unit</strong> of inference-serving capacity.</p>
<p><a href="https://developer.nvidia.com/grove" target="_blank" rel="noopener noreferrer" class="">NVIDIA Grove</a> addresses this layer of the stack with a Kubernetes-native API for describing an AI inference service — so the platform can schedule, start, and scale the deployment in units that match the inference architecture.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-kubernetes-orchestration-gap-for-distributed-inference">The Kubernetes orchestration gap for distributed inference<a href="https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4#the-kubernetes-orchestration-gap-for-distributed-inference" class="hash-link" aria-label="Direct link to The Kubernetes orchestration gap for distributed inference" title="Direct link to The Kubernetes orchestration gap for distributed inference" translate="no">​</a></h2>
<p>Modern AI inference workloads are fundamentally different from the traditional distributed workloads built with independent, stateless microservices. A production LLM deployment may include distinct roles – frontend services, KV-aware routers, prefill and decode workers – that are tightly coupled in how they must be scheduled, started, and scaled. They require scheduling in groups, starting up in a specific order, and scaling in ratios that preserve an end-to-end serving path. For larger models, a single prefill or decode instance may span several pods; where a complete deployment of each instance is necessary for a functional serving capacity.</p>
<p>Kubernetes primitives can independently schedule and run these components, but they do not natively understand the inter-component relationships required for the deployment to operate as an inference service:</p>
<ul>
<li class="">Which pods form one usable model instance?</li>
<li class="">Which roles must start before others?</li>
<li class="">Which components scale independently, and which must scale as a group?</li>
</ul>
<p>Using existing Kubernetes primitives alone, operations teams are forced to spread these considerations across bespoke manifests, labels, scripts, or custom controllers. This can create orchestration challenges and worsen the deployment observability.</p>
<p><a href="https://lws.sigs.k8s.io/docs/overview/" target="_blank" rel="noopener noreferrer" class="">LeaderWorkerSet (LWS)</a> is an Upstream API that addresses a part of this problem by managing a leader pod and its worker pods as a replicated unit, which generally maps well to multi-node model instances. A full Dynamo-style deployment, however, can contain multiple leader-worker units across prefill and decode, plus routing and other service components. LWS may model those leader-worker groups, but it is not intended to describe the full inference service across them. Additionally, LWS couples multi-node scaling units with a leader-worker pattern, and not all multi-node use cases map to this cleanly. Overall, scheduling, startup, and scaling for such modern deployments require a more flexible Kubernetes-native workload abstraction.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="nvidia-grove-for-kubernetes-native-ai-scheduling">NVIDIA Grove for Kubernetes-native AI scheduling<a href="https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4#nvidia-grove-for-kubernetes-native-ai-scheduling" class="hash-link" aria-label="Direct link to NVIDIA Grove for Kubernetes-native AI scheduling" title="Direct link to NVIDIA Grove for Kubernetes-native AI scheduling" translate="no">​</a></h2>
<p>NVIDIA Grove is an open-source Kubernetes API, available as a modular component of NVIDIA Dynamo, for defining both simple single-node and complex multi-node AI workloads. Where standard Kubernetes resources describe individual pods and services, Grove enables you to describe an entire inference serving system’s components and orchestration requirements as one object: routing, prefill, decode, leader-worker groups/multi-node units, startup dependencies, and scaling boundaries — all expressed in a single workload specification.</p>
<p>From that specification, Grove takes responsibility for coordinating the Kubernetes resources required to run the service. Scheduling, startup, and scaling have a shared view of the workload: which pods form a usable instance and which roles belong together. Scaling actions can target a single role, a complete multi-node model instance, or a full service replica. Startup dependencies between roles are part of the workload definition rather than external scripts.</p>
<p>The result is a workload API that matches how distributed inference systems are actually built: individual roles with different resource profiles, grouped model instances that must scale together, and complete service replicas that represent usable end-to-end serving capacity.</p>
<p>In an inference deployment, Dynamo provides the serving stack: inference workers, routing, the Planner, KV cache management, and backend integrations such as vLLM, TensorRT-LLM, and SGLang. Grove defines the Kubernetes orchestration layer around this stack, a.k.a. the contract between the inference system and the cluster. Grove can also be deployed standalone or integrated with other high-performance inference frameworks.</p>
<p><img decoding="async" loading="lazy" alt="Figure 1: End-to-end Grove workflow" src="https://blog.aks.azure.com/assets/images/grove_workflow-e6a7c703dc2effb7808f2fc41f43f6ee.png" width="4133" height="2088" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-grove-models-the-workload">How Grove models the workload<a href="https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4#how-grove-models-the-workload" class="hash-link" aria-label="Direct link to How Grove models the workload" title="Direct link to How Grove models the workload" translate="no">​</a></h2>
<p>Grove's user-facing model is built around three hierarchical primitives that together describe the full shape of an inference deployment, from individual roles up to the complete service:</p>
<table><thead><tr><th>Grove primitive</th><th>What it represents</th><th>Example in model inference</th></tr></thead><tbody><tr><td>PodClique</td><td>A group of pods with the same role and pod template. It has its own replica count, resource needs, and optional autoscaling policy</td><td>Router pods, prefill workers, decode workers, frontend pods</td></tr><tr><td>PodCliqueScalingGroup</td><td>A group of PodClique objects that must scale together while preserving role ratios</td><td>A multi-node prefill instance with one leader and four workers</td></tr><tr><td>PodCliqueSet</td><td>The top-level workload object that describes the full inference system</td><td>Router + prefill group + decode group, with startup order and scaling policy</td></tr></tbody></table>
<p>Using this hierarchy, Grove generates the lower-level Kubernetes resources the cluster needs to schedule, start, and scale the workload. Users work at the inference deployment level, while Grove translates the intent into Grove managed pods and other resources, including the scheduler-facing PodGang resources that encode the scheduling constraints.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="coordinating-scheduling-startup-and-scalability">Coordinating scheduling, startup, and scalability<a href="https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4#coordinating-scheduling-startup-and-scalability" class="hash-link" aria-label="Direct link to Coordinating scheduling, startup, and scalability" title="Direct link to Coordinating scheduling, startup, and scalability" translate="no">​</a></h2>
<p>Once Grove has the workload specification, it uses the same hierarchy to guide how the workload is scheduled, started, and scaled.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="scheduling">Scheduling<a href="https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4#scheduling" class="hash-link" aria-label="Direct link to Scheduling" title="Direct link to Scheduling" translate="no">​</a></h3>
<p>For multi-node inference, serving capacity arrives as a collection of complete instances. A prefill or decode instance that spans several pods can serve traffic only when the full set can run together. Gang scheduling gives Kubernetes an all-or-nothing contract: either all pods in the instance are scheduled together, or none are. Grove creates the scheduler-facing PodGang resources automatically from the workload specification, so this guarantee is part of the workload model rather than a rule that teams have to enforce separately.</p>
<p>Gang scheduling ensures that each individual component of a deployment is allocated as a complete unit, for example, a full prefill instance or a full decode instance. However, this alone does not guarantee that the deployment can actually serve end-to-end requests: the scheduler might schedule several complete prefill instances without any matching decode capacity, or vice versa.</p>
<p>Grove addresses this limitation with hierarchical gang scheduling. Instead of enforcing completeness only at the instance level, Grove also enforces it at the service level. The workload specifies higher-level viability constraints, for example, that at least one complete prefill instance and one complete decode instance must be scheduled together before the deployment is considered ready. The scheduler therefore treats these interdependent components as a “higher-level gang”: it schedules the required combination only when enough resources are available. This prevents the system from allocating individually complete but functionally unusable configurations that cannot process requests end to end.</p>
<p>On an AKS cluster, Grove-managed inference pods can be scheduled by a gang-aware scheduler like the <a href="https://github.com/kai-scheduler/KAI-Scheduler" target="_blank" rel="noopener noreferrer" class="">KAI Scheduler</a>, an open-source Kubernetes-native scheduler built for AI workloads with native PodGang awareness. Grove defines the inference workload - the roles, groups, dependencies, and scaling boundaries - while KAI gives Kubernetes the scheduling behavior needed to place those pods as complete units. Together, they enable these capabilities to be expressed and enforced cleanly within the Kubernetes-native infrastructure.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>When deploying Dynamo workloads with Grove and KAI scheduler, it is recommended that the cluster is set up with dedicated resources for the KAI scheduler to avoid scheduling conflicts. General-purpose workloads can continue to use the default kube-scheduler on separate node pools, so the two schedulers do not compete for the same node capacity.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="startup">Startup<a href="https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4#startup" class="hash-link" aria-label="Direct link to Startup" title="Direct link to Startup" translate="no">​</a></h3>
<p>Scheduling is only the first step. Distributed serving systems often have startup dependencies; for example, workers need to be discoverable before leaders start, and routers or frontends may need to be ready before model workers register. Grove integrates <strong>startup order</strong> with the workload definition through <code>cliqueStartupType</code> and <code>startsAfter</code>, instead of leaving it to scripts or external conventions.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="scaling">Scaling<a href="https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4#scaling" class="hash-link" aria-label="Direct link to Scaling" title="Direct link to Scaling" translate="no">​</a></h3>
<p>Grove has multiple scaling boundaries in a hierarchy model: a PodClique for one role, a PodCliqueScalingGroup for a complete leader-worker unit, and a PodCliqueSet for the full service. This preserves independent scaling even when scheduling is coordinated: prefill and decode can be scheduled as viable units while still scaling separately in response to various bottlenecks.</p>
<p>Different scaling boundaries also integrate well with the Dynamo Planner (covered in <a href="https://blog.aks.azure.com/2026/01/22/dynamo-on-aks-part-2" target="_blank" rel="noopener noreferrer" class="">Part 2 of this blog series</a>). While the Planner reasons about traffic shape, sequence lengths, and time-to-first-token (TTFT) or inter-token latency (ITL) targets to decide when capacity should change, Grove ensures this decision targets the appropriate Grove workload boundary: a PodClique for a single role, a PodCliqueScalingGroup for a complete leader-worker instance, or the PodCliqueSet for the end-to-end inference service. Ultimately, Planner-driven scale-out doesn't just add pods; it adds a complete, gang-scheduled inference instance that is ready to serve.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="modeling-inference-deployments-with-grove">Modeling inference deployments with Grove<a href="https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4#modeling-inference-deployments-with-grove" class="hash-link" aria-label="Direct link to Modeling inference deployments with Grove" title="Direct link to Modeling inference deployments with Grove" translate="no">​</a></h2>
<p>Grove's <a href="https://github.com/ai-dynamo/grove/blob/main/docs/api-reference/operator-api.md#podcliqueset" target="_blank" rel="noopener noreferrer" class="">PodCliqueSet API</a> is flexible enough to cover a range of inference deployment shapes:</p>
<p><strong>Aggregated serving</strong>: where a single PodClique is sufficient; each worker handles the full request lifecycle, and scaling means adding more copies of that worker pod.</p>
<p><strong>Disaggregated serving</strong>: router, prefill, and decode become separate PodCliques, each scaling independently in response to different traffic pressures.</p>
<p><strong>Multi-node</strong>: when a single model instance spans multiple pods, a PodCliqueScalingGroup binds the leader and worker PodCliques into one scaling unit.</p>
<p>The Grove GitHub repository includes <a href="https://github.com/ai-dynamo/grove/tree/main/operator/samples/user-guide/01_core-concepts" target="_blank" rel="noopener noreferrer" class="">reference manifests</a> for these deployment patterns, giving teams concrete starting points for mapping their own serving architecture into PodCliques, PodCliqueScalingGroups, and PodCliqueSets.</p>
<p>See Grove in action below as it orchestrates a multi-node Llama 3.1 70B deployment on AKS. The demo serves <a href="https://huggingface.co/nvidia/Llama-3.1-70B-Instruct-FP8" target="_blank" rel="noopener noreferrer" class="">Llama-3.1-70B-Instruct-FP8</a> on 16x NVIDIA H100 (2 nodes of <a href="https://learn.microsoft.com/azure/virtual-machines/sizes/gpu-accelerated/ndh100v5-series" target="_blank" rel="noopener noreferrer" class="">Azure Standard_ND96isr_H100_v5</a>) with tp=16. The walkthrough shows a DynamoGraphDeployment being translated into Grove resources, worker pods landing across H100 nodes, and scale-out adding a second gang-scheduled two-pod worker as a grouped unit.</p>
<p><a href="https://asciinema.org/a/TLoDssztIJ3XZVpo" target="_blank" rel="noopener noreferrer" class=""><img decoding="async" loading="lazy" src="https://asciinema.org/a/TLoDssztIJ3XZVpo.svg" alt="Demo: Multi-node inference with NVIDIA Dynamo + Grove on AKS" class="img_ev3q"></a></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="looking-ahead">Looking ahead<a href="https://blog.aks.azure.com/2026/06/02/dynamo-on-aks-part-4#looking-ahead" class="hash-link" aria-label="Direct link to Looking ahead" title="Direct link to Looking ahead" translate="no">​</a></h2>
<p>By introducing this workload layer, Grove provides Kubernetes with a structured contract for orchestrating the gang scheduling and scaling requirements of AI workloads. This also provides the foundation for handling complex challenges that modern inference workloads are facing today.</p>
<p>One such challenge is <strong>topology-aware scheduling</strong>. As deployments span multiple nodes and accelerator domains, scheduling decisions become critical to performance. Without topology awareness, gang-scheduled instances may be placed on nodes that are physically or network-topologically distant from one another, introducing <em>communication bottlenecks</em> that undermine the efficiency that the inference stack is designed to deliver.</p>
<p>Rolling updates introduce another layer of complexity. In disaggregated inference, prefill and decode components usually have compatibility constraints across framework versions, requiring cross-version traffic to be blocked during upgrades. This can strand capacity across versions: for example, a rollout might leave eight old-version prefill instances but only two old-version decode instances, reducing the amount of end-to-end capacity that can actually serve requests. Updating these systems safely requires rollout behavior that <em>preserves viable serving capacity</em> across the pipeline, rather than updating each role as an independent pool of pods.</p>
<p>In Part 5, we’ll build on the Grove workload model introduced here and explore how this structured view of the serving pipeline enables more advanced orchestration, including topology-aware scheduling for distributed inference and safer rolling updates for disaggregated serving pipelines. We’ll also share how we’re collaborating with the NVIDIA Grove and Dynamo communities toward a shared goal: building a Kubernetes-native orchestration layer for the demands of modern inference.</p>]]></content:encoded>
            <category>Dynamo on AKS series</category>
            <category>AI</category>
            <category>Performance</category>
            <category>Open Source</category>
        </item>
        <item>
            <title><![CDATA[Apply Copy Fail and DirtyFrag CVE mitigations at-scale using Azure Kubernetes Fleet Manager]]></title>
            <link>https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager</link>
            <guid>https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager</guid>
            <pubDate>Mon, 11 May 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[See how to apply mitigations for CVE-2026-31431 (Copy Fail) and CVE-2026-43284 / CVE-2026-43500 (DirtyFrag) across AKS clusters using Azure Kubernetes Fleet Manager with a DaemonSet and node image upgrades.]]></description>
            <content:encoded><![CDATA[<p>This post shows how to use Azure Kubernetes Fleet Manager to simplify the safe rollout of mitigations for <a href="https://github.com/advisories/GHSA-2274-3hgr-wxv6" target="_blank" rel="noopener noreferrer" class="">CVE-2026-31431</a> ("Copy Fail") and <a href="https://github.com/advisories/GHSA-mmw8-mxmc-8w2r" target="_blank" rel="noopener noreferrer" class="">CVE-2026-43284</a> / CVE-2026-43500 ("DirtyFrag") across multiple AKS clusters. This vulnerability allows a container to escalate to root on the node and impacts AKS Linux nodes until mitigations are applied. Existing nodes require either a node image upgrade or a self-service DaemonSet mitigation.</p>
<p>The approaches covered in this post apply mitigations as follows:</p>
<ol>
<li class=""><a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#immediate-mitigation---daemonset-rollout" class="">Immediate mitigation</a>: deploy a DaemonSet using Fleet Manager resource placement. Best for clusters where a node image upgrade can't yet be applied.</li>
<li class=""><a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#permanent-mitigation---node-image-upgrade" class="">Permanent mitigation</a>: upgrade node images using Fleet Manager update runs. Applies patches when new node image version is available in the cluster's region.</li>
</ol>
<p>The original AKS advisory and detailed mitigation guide for per-cluster application can be found in <a href="https://github.com/Azure/AKS/issues/5753" target="_blank" rel="noopener noreferrer" class="">GitHub Issue 5753</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="before-you-begin">Before you begin<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#before-you-begin" class="hash-link" aria-label="Direct link to Before you begin" title="Direct link to Before you begin" translate="no">​</a></h2>
<ul>
<li class="">Azure CLI version 2.86.0 or later installed (<a href="https://learn.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest" target="_blank" rel="noopener noreferrer" class="">instructions</a>).</li>
<li class="">Fleet Manager Azure CLI extension installed and updated.</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az extension add --name fleet</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az extension update --name fleet</span><br></div></code></pre></div></div>
<ul>
<li class="">A Fleet Manager with a hub cluster (<a href="https://learn.microsoft.com/azure/kubernetes-fleet/quickstart-create-fleet-and-members?pivots=public-hub" target="_blank" rel="noopener noreferrer" class="">instructions</a>). You can add a hub cluster if you are already using Fleet without one.</li>
<li class="">AKS clusters added as member clusters with update group and member labels set. Groups and labels are required for controlling the rollout via placement and update runs.</li>
</ul>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az fleet member create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group $GROUP \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --fleet-name $FLEET \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name $MEMBER_CLUSTER_NAME \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --update-group canary \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --labels upgroup=canary</span><br></div></code></pre></div></div>
<ul>
<li class="">kubectl configured for the hub cluster (<a href="https://learn.microsoft.com/azure/kubernetes-fleet/access-fleet-hub-cluster-kubernetes-api?pivots=public-hub" target="_blank" rel="noopener noreferrer" class="">instructions</a>).</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="immediate-mitigation---daemonset-rollout">Immediate mitigation - DaemonSet rollout<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#immediate-mitigation---daemonset-rollout" class="hash-link" aria-label="Direct link to Immediate mitigation - DaemonSet rollout" title="Direct link to Immediate mitigation - DaemonSet rollout" translate="no">​</a></h2>
<div class="theme-admonition theme-admonition-important admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>important</div><div class="admonitionContent_BuS1"><p>Make sure to review the steps in <a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#before-you-begin" class="">before you begin</a>, especially adding labels to member clusters.</p></div></div>
<p>For immediate protection, apply a DaemonSet that disables the vulnerable kernel module. The DaemonSet is deployed into its own Namespace on each cluster, making it easy to discern intent and easier to distribute across clusters. Given how DaemonSets function, the protection is applied to the node, even though we don't use the <code>kube-system</code> Namespace as shown in the <a href="https://github.com/Azure/AKS/issues/5753" target="_blank" rel="noopener noreferrer" class="">original AKS mitigation guide</a>.</p>
<p>Fleet Manager's <a href="https://learn.microsoft.com/azure/kubernetes-fleet/quickstart-resource-propagation?tabs=azure-cli" target="_blank" rel="noopener noreferrer" class="">cluster-scoped resource placement</a> enables:</p>
<ul>
<li class="">Multi-cluster selection for a resource via <a href="https://learn.microsoft.com/azure/kubernetes-fleet/quickstart-resource-propagation?tabs=azure-cli#use-clusterresourceplacement-to-place-resources-onto-member-clusters" target="_blank" rel="noopener noreferrer" class="">ClusterResourcePlacement</a>.</li>
<li class="">Controlled rollout of the resource via <a href="https://learn.microsoft.com/azure/kubernetes-fleet/howto-staged-update-run?pivots=cluster-scope" target="_blank" rel="noopener noreferrer" class="">ClusterStagedUpdateRun</a>.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-create-the-mitigation-namespace-and-daemonset">Step 1: Create the mitigation Namespace and DaemonSet<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-1-create-the-mitigation-namespace-and-daemonset" class="hash-link" aria-label="Direct link to Step 1: Create the mitigation Namespace and DaemonSet" title="Direct link to Step 1: Create the mitigation Namespace and DaemonSet" translate="no">​</a></h3>
<p>Create the <code>01-kernel-lpe-mitigate.yaml</code> file with the following contents. The DaemonSet blocks vulnerable modules covered by the CVEs.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Namespace</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">cve</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mitigate</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">ns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> apps/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> DaemonSet</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mitigate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">cve</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mitigate</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">ns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">labels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mitigate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">purpose</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> security</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mitigation</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">selector</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">matchLabels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mitigate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">template</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">labels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mitigate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">hostPID</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">priorityClassName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> system</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">node</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">critical</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">nodeSelector</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">kubernetes.io/os</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> linux</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">tolerations</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">operator</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Exists</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">containers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> mitigate</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> mcr.microsoft.com/cbl</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mariner/busybox</span><span class="token punctuation" style="color:#393A34">:</span><span class="token number" style="color:#36acaa">2.0</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">command</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> /bin/sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">c</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              echo "=== Kernel LPE Module Mitigation ==="</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">              echo "Covers: CVE-2026-31431 (algif_aead), DirtyFrag (esp4/esp6/rxrpc)"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              MODULES="algif_aead esp4 esp6 rxrpc"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              for mod in $MODULES; do</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                if </span><span class="token tag" style="color:#00009f">!</span><span class="token plain"> grep </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">qs "install $</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">mod</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> /bin/false" /host/etc/modprobe.d/</span><span class="token important">*.conf</span><span class="token plain"> 2</span><span class="token punctuation" style="color:#393A34">&gt;</span><span class="token plain">/dev/null; then</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  printf "install %s /bin/false\nblacklist %s\n" "$mod" "$mod" </span><span class="token punctuation" style="color:#393A34">&gt;</span><span class="token punctuation" style="color:#393A34">&gt;</span><span class="token plain"> /host/etc/modprobe.d/disable</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe.conf</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  echo "Blocked $</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">mod</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                else</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  echo "$</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">mod</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> already blocked"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                fi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                if chroot /host grep </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">q "^$</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">mod</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> " /proc/modules 2</span><span class="token punctuation" style="color:#393A34">&gt;</span><span class="token plain">/dev/null; then</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  if chroot /host modprobe </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">r "$mod" 2</span><span class="token punctuation" style="color:#393A34">&gt;</span><span class="token plain">/dev/null; then</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    echo "Unloaded $</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">mod</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain">"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  else</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token key atrule" style="color:#00a4db">echo "WARNING</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Could not unload $</span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain">mod</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"> (in use). Reboot node."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                  fi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                fi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              done</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              echo "=== Mitigation complete. Sleeping ==="</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              sleep infinity</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">resources</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">requests</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token key atrule" style="color:#00a4db">cpu</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 10m</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token key atrule" style="color:#00a4db">memory</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 16Mi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">limits</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token key atrule" style="color:#00a4db">memory</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 32Mi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">securityContext</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">privileged</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">volumeMounts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> host</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">root</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token key atrule" style="color:#00a4db">mountPath</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /host</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> host</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">root</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">hostPath</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">path</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Directory</span><br></div></code></pre></div></div>
<p>Stage the mitigation namespace and DaemonSet on the Fleet Manager hub cluster.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f 01-kernel-lpe-mitigate.yaml</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>The DaemonSet is not instantiated (0 pods are scheduled) on the Fleet Manager hub cluster. This is expected behavior.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-create-a-clusterresourceplacement">Step 2: Create a ClusterResourcePlacement<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-2-create-a-clusterresourceplacement" class="hash-link" aria-label="Direct link to Step 2: Create a ClusterResourcePlacement" title="Direct link to Step 2: Create a ClusterResourcePlacement" translate="no">​</a></h3>
<p>Create a placement manifest <code>02-fleet-pick-clusters-mitigation.yaml</code> that selects all Fleet Manager member clusters (<code>PickAll</code>) as suitable to receive the Namespace and its DaemonSet. We set the strategy to <code>External</code> so we can define and control the rollout order for clusters, which we will do in following steps.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> placement.kubernetes</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">fleet.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ClusterResourcePlacement</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">cve</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mit</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">ns</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">place</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">resourceSelectors</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">group</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">version</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Namespace</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">cve</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mitigate</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">ns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">policy</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">placementType</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> PickAll</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">strategy</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> External</span><br></div></code></pre></div></div>
<p>Apply the placement on Fleet Manager hub cluster.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f 02-fleet-pick-clusters-mitigation.yaml</span><br></div></code></pre></div></div>
<p>You can validate how many clusters will receive the mitigation using this command.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get clusterresourceplacement kernel-lpe-cve-mit-ns-place -o jsonpath="{.status.conditions[?(@.type=='ClusterResourcePlacementScheduled')].message}"</span><br></div></code></pre></div></div>
<p>The message in the response shows how many clusters were selected.</p>
<div class="language-output codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-output codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">found all cluster needed as specified by the scheduling policy, found 2 cluster(s)</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-create-a-clusterstagedupdatestrategy">Step 3: Create a ClusterStagedUpdateStrategy<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-3-create-a-clusterstagedupdatestrategy" class="hash-link" aria-label="Direct link to Step 3: Create a ClusterStagedUpdateStrategy" title="Direct link to Step 3: Create a ClusterStagedUpdateStrategy" translate="no">​</a></h3>
<p>A strategy is used to define the rollout order and soak duration per rollout stage. Note that all selected clusters must be in a stage. This is determined by the labels set of the cluster when it was added to the Fleet Manager.</p>
<p>In this sample <code>03-fleet-mitigation-rollout-strategy.yaml</code> we have three stages, with a 4 hour soak time between each stage. You can also add approvals if required.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> placement.kubernetes</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">fleet.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ClusterStagedUpdateStrategy</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">cve</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mit</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">ds</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">strategy</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">stages</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> stage1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">labelSelector</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">matchLabels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">upgroup</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> canary</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">afterStageTasks</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> TimedWait</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">waitTime</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 4h</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">maxConcurrency</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> stage2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">labelSelector</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">matchLabels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">upgroup</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> nonprod</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">afterStageTasks</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> TimedWait</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">waitTime</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 4h</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">maxConcurrency</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> stage3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">labelSelector</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">matchLabels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">upgroup</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> prod</span><br></div></code></pre></div></div>
<p>Apply the strategy on Fleet Manager hub cluster.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f 03-fleet-mitigation-rollout-strategy.yaml</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-start-rollout">Step 4: Start rollout<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-4-start-rollout" class="hash-link" aria-label="Direct link to Step 4: Start rollout" title="Direct link to Step 4: Start rollout" translate="no">​</a></h3>
<p>Finally, create a ClusterStagedUpdateRun <code>04-fleet-mitigation-rollout-run.yaml</code> to begin the rollout.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>To start the rollout later, set <code>state</code> to <code>Initialize</code>. You can patch the resource to <code>Run</code> to begin rollout (see below).</p></div></div>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> placement.kubernetes</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">fleet.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ClusterStagedUpdateRun</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">cve</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mit</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">ds</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">rollout</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">placementName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">cve</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mit</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">ns</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">place</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">stagedRolloutStrategyName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> kernel</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">lpe</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">cve</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">mit</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">ds</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">strategy</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">state</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Run</span><br></div></code></pre></div></div>
<p>Apply the update run on Fleet Manager hub cluster. If you have <code>Run</code> as the state, the rollout begins.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f 04-fleet-mitigation-rollout-run.yaml</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-monitor-and-control-rollout">Step 5: Monitor and control rollout<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-5-monitor-and-control-rollout" class="hash-link" aria-label="Direct link to Step 5: Monitor and control rollout" title="Direct link to Step 5: Monitor and control rollout" translate="no">​</a></h3>
<p>Use the following command to retrieve the status of the rollout. Inspect the <code>conditions</code> and <code>stageStatuses</code> arrays to review progress.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get clusterstagedupdaterun kernel-lpe-cve-mit-ds-rollout -o yaml</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>You can also use the preview version of <a href="https://github.com/kubefleet-dev/kubefleet-headlamp-plugin" target="_blank" rel="noopener noreferrer" class="">KubeFleet's Headlamp plugin</a> to help monitor and control the rollout. Connect it to the Fleet Manager hub cluster to use.</p></div></div>
<p>If you want to stop the rollout, you can patch the update run:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl patch clusterstagedupdaterun kernel-lpe-cve-mit-ds-rollout --type merge -p '{"spec":{"state":"Stop"}}'</span><br></div></code></pre></div></div>
<p>Restart the rollout by patching the update run:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl patch clusterstagedupdaterun kernel-lpe-cve-mit-ds-rollout --type merge -p '{"spec":{"state":"Run"}}'</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-6-validate-mitigation-is-applied">Step 6: Validate mitigation is applied<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-6-validate-mitigation-is-applied" class="hash-link" aria-label="Direct link to Step 6: Validate mitigation is applied" title="Direct link to Step 6: Validate mitigation is applied" translate="no">​</a></h3>
<p>Select clusters in your fleet that have received the mitigation and run the following check.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl logs -n kernel-lpe-cve-mitigate-ns -l app=kernel-lpe-mitigate</span><br></div></code></pre></div></div>
<p>If the mitigation is applied successfully you should see.</p>
<div class="language-output codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-output codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">=== Kernel LPE Module Mitigation ===</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Covers: CVE-2026-31431 (algif_aead), DirtyFrag (esp4/esp6/rxrpc)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Blocked algif_aead</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Blocked esp4 </span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Blocked esp6</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Blocked rxrpc</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">=== Mitigation complete. Sleeping ===</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>In some cases where the module is in use, the node will require a reboot. The log message will indicate this is required.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-7-roll-back-or-remove">Step 7: Roll back or remove<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-7-roll-back-or-remove" class="hash-link" aria-label="Direct link to Step 7: Roll back or remove" title="Direct link to Step 7: Roll back or remove" translate="no">​</a></h3>
<p>If issues occur, or the DaemonSet is no longer required as the node image has been updated to a permanently patched release, you can remove the mitigation DaemonSet by deleting the ClusterResourcePlacement on the Fleet Manager hub cluster.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl delete clusterresourceplacement kernel-lpe-cve-mit-ns-place</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="daemonset-rollout-outcome">DaemonSet Rollout Outcome<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#daemonset-rollout-outcome" class="hash-link" aria-label="Direct link to DaemonSet Rollout Outcome" title="Direct link to DaemonSet Rollout Outcome" translate="no">​</a></h3>
<ul>
<li class="">Immediate mitigation applied across clusters via DaemonSet.</li>
<li class="">Controlled rollout reduces risk, allows testing on clusters in batches.</li>
<li class="">No node reboot required when module not in use.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="permanent-mitigation---node-image-upgrade">Permanent mitigation - node image upgrade<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#permanent-mitigation---node-image-upgrade" class="hash-link" aria-label="Direct link to Permanent mitigation - node image upgrade" title="Direct link to Permanent mitigation - node image upgrade" translate="no">​</a></h2>
<div class="theme-admonition theme-admonition-important admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>important</div><div class="admonitionContent_BuS1"><ul>
<li class="">
<p>Make sure to review the steps in <a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#before-you-begin" class="">before you begin</a>, especially adding update groups to member clusters.</p>
</li>
<li class="">
<p>Depending on the regional spread of your clusters, it is possible that the update run may pause for periods as it waits for the node image to be published to a region. This is expected behavior. You can apply the DaemonSet to your clusters to ensure they are protected while waiting for the updated node image to be available.</p>
</li>
</ul></div></div>
<p>Once AKS provides a permanent mitigation via updated node images you can use Fleet Manager update runs to update the node image across your clusters.</p>
<p>Fleet Manager's <a href="https://learn.microsoft.com/azure/kubernetes-fleet/concepts-update-orchestration" target="_blank" rel="noopener noreferrer" class="">safe multi-cluster updates</a> feature enables:</p>
<ul>
<li class="">Application of consistent node image across clusters using the <a href="https://learn.microsoft.com/azure/kubernetes-fleet/concepts-update-orchestration#nodeimage-channel" target="_blank" rel="noopener noreferrer" class="">Node Image channel</a>.</li>
<li class="">Controlled rollout of the node image update via <a href="https://learn.microsoft.com/azure/kubernetes-fleet/update-create-update-strategy?tabs=azure-portal" target="_blank" rel="noopener noreferrer" class="">Update Strategies</a>.</li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-define-an-update-strategy">Step 1: Define an update strategy<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-1-define-an-update-strategy" class="hash-link" aria-label="Direct link to Step 1: Define an update strategy" title="Direct link to Step 1: Define an update strategy" translate="no">​</a></h3>
<p>An update strategy defines the order in which clusters will receive the node image update. Strategies are defined using a simple JSON format as shown next.</p>
<p>In the following sample we have one group of clusters per stage, with clusters grouped based on the <code>update-group</code> on the member cluster. We continue with the 4 hour soak time and have a 100% concurrency which allows up to 50 clusters to update at once. You can also set these up via the Azure portal if desired.</p>
<div class="language-json codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-json codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token property" style="color:#36acaa">"stages"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"stage-1"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"maxConcurrency"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"100%"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"groups"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"canary"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token property" style="color:#36acaa">"maxConcurrency"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"100%"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"afterStageWaitInSeconds"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">14400</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"stage-2"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"maxConcurrency"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"100%"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"groups"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"nonprod"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token property" style="color:#36acaa">"maxConcurrency"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"100%"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">]</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"afterStageWaitInSeconds"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">14400</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"stage-3"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"maxConcurrency"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"100%"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token property" style="color:#36acaa">"groups"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">{</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token property" style="color:#36acaa">"name"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"prod"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                    </span><span class="token property" style="color:#36acaa">"maxConcurrency"</span><span class="token operator" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"100%"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">}</span><br></div></code></pre></div></div>
<p>Save the strategy as a file (<code>nodeimage-strategy.json</code>) and then submit via the Azure CLI to create the Strategy ready for use in Fleet Manager.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">export GROUP=resource-group</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export FLEET=fleet-name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export STRATEGY=copyfail-mitigation-strategy</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">az fleet updatestrategy create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group $GROUP \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --fleet-name $FLEET \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name $STRATEGY \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --stages nodeimage-strategy.json</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>To update a strategy after you have created it, make changes to the JSON file and simply call the <code>create</code> command again.</p></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-create-an-update-run-for-node-image-upgrades">Step 2: Create an update run for node image upgrades<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-2-create-an-update-run-for-node-image-upgrades" class="hash-link" aria-label="Direct link to Step 2: Create an update run for node image upgrades" title="Direct link to Step 2: Create an update run for node image upgrades" translate="no">​</a></h3>
<p>Next, let's create a new update run for updating just the node image on selected clusters.</p>
<p>The update run won't automatically start, so you can create this once AKS releases the patched node images, then start the run at a later time that suits your schedule.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az fleet updaterun create \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group $GROUP \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --fleet-name $FLEET \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name copyfail-nodeimage-update \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --upgrade-type NodeImageOnly \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --update-strategy-name $STRATEGY</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-start-the-update-run">Step 3: Start the update run<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-3-start-the-update-run" class="hash-link" aria-label="Direct link to Step 3: Start the update run" title="Direct link to Step 3: Start the update run" translate="no">​</a></h3>
<p>Once you are satisfied that all clusters to be upgraded are included, you can start the update run.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az fleet updaterun start \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --fleet-name $FLEET \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group $GROUP \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name copyfail-nodeimage-update</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-monitor-progress">Step 4: Monitor progress<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#step-4-monitor-progress" class="hash-link" aria-label="Direct link to Step 4: Monitor progress" title="Direct link to Step 4: Monitor progress" translate="no">​</a></h3>
<p>You can review the status using the Azure CLI shown below, or use the <a href="https://learn.microsoft.com/azure/kubernetes-fleet/update-orchestration?tabs=azure-portal#manage-an-update-run" target="_blank" rel="noopener noreferrer" class="">Azure portal's web interface</a> to view the rollout graphically.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az fleet updaterun show \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --resource-group $GROUP \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --fleet-name $FLEET \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --name copyfail-nodeimage-update</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="node-image-upgrade-outcome">Node Image Upgrade Outcome<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#node-image-upgrade-outcome" class="hash-link" aria-label="Direct link to Node Image Upgrade Outcome" title="Direct link to Node Image Upgrade Outcome" translate="no">​</a></h3>
<ul>
<li class="">All nodes are updated to the patched node image.</li>
<li class="">DaemonSet no longer required and can be removed.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary-and-next-steps">Summary and next steps<a href="https://blog.aks.azure.com/2026/05/11/kernel-lpe-cve-patching-at-scale-with-fleet-manager#summary-and-next-steps" class="hash-link" aria-label="Direct link to Summary and next steps" title="Direct link to Summary and next steps" translate="no">​</a></h2>
<p>In this post we've looked at the multiple ways in which Azure Kubernetes Fleet Manager can help you safely apply mitigations for the Copy Fail CVE across all your clusters.</p>
<p>Fleet Manager continues to add new capabilities regularly, with the goal of simplifying at-scale Kubernetes cluster management for everyone.</p>
<p>Here's some recommended next steps:</p>
<ul>
<li class=""><a href="https://github.com/Azure/AKS/issues/5753" target="_blank" rel="noopener noreferrer" class="">Review AKS Advisory and Mitigation for CVE-2026-31431 (Copy Fail)</a>.</li>
<li class=""><a href="https://learn.microsoft.com/azure/kubernetes-fleet/concepts-update-orchestration" target="_blank" rel="noopener noreferrer" class="">Learn more about Fleet Manager Safe Multi-cluster Updates</a>.</li>
<li class=""><a href="https://learn.microsoft.com/azure/kubernetes-fleet/concepts-resource-placement?pivots=cluster-scope" target="_blank" rel="noopener noreferrer" class="">Learn more about Fleet Manager resource placement</a>.</li>
<li class=""><a href="https://aka.ms/kubernetes-fleet/roadmap" target="_blank" rel="noopener noreferrer" class="">View the Fleet Manager public roadmap</a>. Feature requests always welcome!</li>
<li class=""><a href="https://aka.ms/kubefleet/commcalls" target="_blank" rel="noopener noreferrer" class="">Join a KubeFleet community call</a> to learn about Fleet Manager's open source resource placement.</li>
</ul>]]></content:encoded>
            <category>Security</category>
            <category>Azure Kubernetes Fleet Manager</category>
            <category>KubeFleet</category>
        </item>
        <item>
            <title><![CDATA[Securing Argo CD with Microsoft Entra ID: a step-by-step guide]]></title>
            <link>https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra</link>
            <guid>https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra</guid>
            <pubDate>Wed, 22 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Learn how to secure the Argo CD extension for AKS with Microsoft Entra ID using Terraform, workload identity, OIDC, RBAC, and policy-based access control.]]></description>
            <content:encoded><![CDATA[<p>AKS has offered GitOps support for a while now with the <a href="https://learn.microsoft.com/azure/azure-arc/kubernetes/tutorial-use-gitops-flux2?tabs=azure-cli" target="_blank" rel="noopener noreferrer" class="">Flux v2 cluster extension</a>, and many users have been asking for Argo CD support as well. A few weeks ago at KubeCon Europe 2026, the <a href="https://techcommunity.microsoft.com/blog/azurearcblog/announcing-public-preview-of-argo-cd-extension-on-aks-and-azure-arc-enabled-kube/4504497" target="_blank" rel="noopener noreferrer" class="">public preview of Argo CD extension for AKS was announced</a>. Running Argo CD as an AKS extension means you can lean on Azure for enhanced security and operations: automatic patch releases so you're always up to date, reduced CVEs with images based on Azure Linux, easy single sign-on (SSO) with Microsoft Entra ID, and workload identity federation so Argo CD can authenticate to Azure services without managing secrets.</p>
<p>In this post, we'll focus on the identity side of that story. We'll walk through integrating Argo CD with Microsoft Entra ID to give your developers SSO access using their existing corporate identities; no separate passwords to manage. You also get the full power of Azure's identity platform: multi-factor authentication (MFA), conditional access policies, and group-based role assignments to control who can do what in Argo CD. We'll automate the entire setup with Terraform so the configuration is repeatable, version-controlled, and easy to adapt across environments.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="overview">Overview<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#overview" class="hash-link" aria-label="Direct link to Overview" title="Direct link to Overview" translate="no">​</a></h2>
<p>By now, <a href="https://argo-cd.readthedocs.io/en/stable/" target="_blank" rel="noopener noreferrer" class="">Argo CD</a> probably needs no introduction. But if you are new to Argo CD, it is a declarative, GitOps continuous delivery tool for Kubernetes. It allows you to manage your Kubernetes applications using Git repositories as the source of truth.</p>
<p>Argo CD is heavily used in production environments and many have already adopted the open-source version of Argo CD. With the new Argo CD extension for AKS, you can now easily deploy the software on your cluster as an Azure-native resource. But what does that mean? Well, for one, it means you can now leverage Azure CLI, Azure Resource Manager (ARM) templates, Bicep, or Terraform to manage your Argo CD deployments as you bootstrap clusters. It also means you no longer need to worry about upgrading and patching Argo CD. Azure handles that for you, so you can focus on deploying applications.</p>
<p>By default, the Argo CD installation comes with a built-in admin user. However, for production environments, it is recommended to use an external identity provider for authentication and authorization. Microsoft Entra ID is a great choice for this as it provides robust security features and seamless integration with Azure services. My motto has always been "ditch the passwords" and using Microsoft Entra ID for authentication allows you to do just that. You can leverage features like multi-factor authentication (MFA), conditional access policies, and role-based access control (RBAC) to secure your Argo CD deployment.</p>
<p>At a high level, the process of securing Argo CD with Microsoft Entra ID involves the following steps:</p>
<ol>
<li class="">Create a Microsoft Entra ID application for Argo CD</li>
<li class="">Create OIDC configuration settings to connect Microsoft Entra ID and enable workload identity for Argo CD</li>
<li class="">Create policy mappings to grant Microsoft Entra ID users/groups access to Argo CD</li>
<li class="">Create an AKS cluster with OIDC issuer and workload identity enabled</li>
<li class="">Deploy the Argo CD extension for AKS</li>
<li class="">Verify the integration</li>
</ol>
<p>There's a bit of prerequisite setup to go through, especially when it comes to configuring Microsoft Entra ID applications and OIDC settings, but don't worry, we'll automate all of that using Terraform and I'll go through each step in detail. The end result will be a secure Argo CD deployment that is integrated with Microsoft Entra ID for authentication and authorization.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-0-prerequisites">Step 0: Prerequisites<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#step-0-prerequisites" class="hash-link" aria-label="Direct link to Step 0: Prerequisites" title="Direct link to Step 0: Prerequisites" translate="no">​</a></h2>
<p>Before you get started, make sure you have the following prerequisites in place:</p>
<ul>
<li class=""><a href="https://aka.ms/azure-free-account" target="_blank" rel="noopener noreferrer" class="">Azure subscription</a></li>
<li class=""><a href="https://learn.microsoft.com/cli/azure/install-azure-cli?view=azure-cli-latest" target="_blank" rel="noopener noreferrer" class="">Azure CLI</a></li>
<li class=""><a href="https://kubernetes.io/docs/reference/kubectl/" target="_blank" rel="noopener noreferrer" class="">kubectl</a></li>
<li class=""><a href="https://developer.hashicorp.com/terraform/install" target="_blank" rel="noopener noreferrer" class="">Terraform</a></li>
<li class=""><a href="https://argo-cd.readthedocs.io/en/stable/cli_installation/" target="_blank" rel="noopener noreferrer" class="">Argo CD CLI (optional)</a></li>
</ul>
<p>Within your Azure tenant/subscription, you will also need to have the necessary permissions to create Microsoft Entra ID applications and manage Azure resources.</p>
<p>Unless you are the Global Administrator in your tenant, you will need to have one of the following roles to be able to create Microsoft Entra ID applications:</p>
<ul>
<li class=""><a href="https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#application-administrator" target="_blank" rel="noopener noreferrer" class="">Application Administrator</a></li>
<li class=""><a href="https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#application-developer" target="_blank" rel="noopener noreferrer" class="">Application Developer</a></li>
<li class=""><a href="https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#cloud-application-administrator" target="_blank" rel="noopener noreferrer" class="">Cloud Application Administrator</a></li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-1-terraform-provider-setup">Step 1: Terraform provider setup<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#step-1-terraform-provider-setup" class="hash-link" aria-label="Direct link to Step 1: Terraform provider setup" title="Direct link to Step 1: Terraform provider setup" translate="no">​</a></h2>
<p>Create a new directory for your Terraform configuration and navigate to it and create a new file named <code>main.tf</code>.</p>
<p>In this file, we will define the Terraform providers for <a href="https://registry.terraform.io/providers/hashicorp/azurerm/latest" target="_blank" rel="noopener noreferrer" class="">Azure Resource Manager (azurerm)</a> and <a href="https://registry.terraform.io/providers/hashicorp/azuread/latest" target="_blank" rel="noopener noreferrer" class="">Microsoft Entra ID (azuread)</a>. The <code>azurerm</code> provider will allow us to manage Azure resources, while the <code>azuread</code> provider will allow us to manage Microsoft Entra ID applications and related resources.</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">terraform {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  required_providers {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    azuread = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      source  = "hashicorp/azuread"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      version = "~&gt; 3.8.0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    azurerm = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      source  = "hashicorp/azurerm"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      version = "~&gt; 4.69.0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">provider "azurerm" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  features {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    resource_group {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      prevent_deletion_if_contains_resources = false</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-2-create-microsoft-entra-id-application-for-argo-cd">Step 2: Create Microsoft Entra ID application for Argo CD<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#step-2-create-microsoft-entra-id-application-for-argo-cd" class="hash-link" aria-label="Direct link to Step 2: Create Microsoft Entra ID application for Argo CD" title="Direct link to Step 2: Create Microsoft Entra ID application for Argo CD" translate="no">​</a></h2>
<p>Next, let's create some data sources to query Microsoft Entra ID for the necessary information to set up our application and OIDC configuration. Add the following code to your <code>main.tf</code> file to get a list of well-known application IDs, the current client's configuration, and the service principal for Microsoft Graph.</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">data "azuread_application_published_app_ids" "well_known" {}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">data "azuread_client_config" "current" {}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">data "azuread_service_principal" "msgraph" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  client_id = data.azuread_application_published_app_ids.well_known.result["MicrosoftGraph"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Since the OIDC configuration for Argo CD requires the IDs of specific Microsoft Graph scopes, we will also create a local variable named <code>msgraph_scopes</code> that includes all the scopes from the Microsoft Graph service principal. This will allow us to easily reference the scope IDs when we create our Microsoft Entra ID application for Argo CD.</p>
<p>Add the following code to your <code>main.tf</code> file to create the <code>msgraph_scopes</code> local variable:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">locals {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  msgraph_scopes = { for s in data.azuread_service_principal.msgraph.oauth2_permission_scopes : s.value =&gt; s }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Now we are ready to create our Microsoft Entra ID application for Argo CD. This application will represent your Argo CD in Microsoft Entra ID and will be used for authentication and authorization.</p>
<p>Add the following code to your <code>main.tf</code> file to create the application:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azuread_application" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  display_name            = "myargocdapp"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  owners                  = [data.azuread_client_config.current.object_id]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  sign_in_audience        = "AzureADMyOrg"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  group_membership_claims = ["ApplicationGroup"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  web {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    redirect_uris = [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      "https://localhost:9000/auth/callback"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  public_client {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    redirect_uris = [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      "http://localhost:8085/auth/callback"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  required_resource_access {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    resource_app_id = "00000003-0000-0000-c000-000000000000" # Microsoft Graph</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    resource_access {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      id   = local.msgraph_scopes["openid"].id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      type = "Scope"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    resource_access {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      id   = local.msgraph_scopes["profile"].id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      type = "Scope"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    resource_access {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      id   = local.msgraph_scopes["email"].id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      type = "Scope"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    resource_access {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      id   = local.msgraph_scopes["User.Read"].id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      type = "Scope"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  optional_claims {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    id_token {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      name      = "groups"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      essential = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Here's what each part of the application configuration does:</p>
<ul>
<li class=""><strong><code>sign_in_audience = "AzureADMyOrg"</code></strong> — restricts sign-in to users in your Microsoft Entra ID tenant only</li>
<li class=""><strong><code>group_membership_claims = ["ApplicationGroup"]</code></strong> — includes group membership in the token, which we'll use later for RBAC role mapping</li>
<li class=""><strong><code>web</code> and <code>public_client</code> redirect URIs</strong> — required for the OIDC authentication flow with Argo CD (the <code>web</code> URI is for the browser-based flow, and the <code>public_client</code> URI is for the CLI). Since we're testing locally, we use localhost URIs here, but for production you'll want to update these to match your deployment</li>
<li class=""><strong><code>required_resource_access</code></strong> — requests Microsoft Graph API permissions (<code>openid</code>, <code>profile</code>, <code>email</code>, and <code>User.Read</code>) needed for the OIDC flow and to retrieve user information</li>
<li class=""><strong><code>optional_claims</code></strong> — includes the user's group memberships in the ID token, enabling group-based access control in Argo CD</li>
</ul>
<p>The workload identity integration requires Argo CD to exchange its Kubernetes service account token for a Microsoft Entra ID token, so we will need to create a federated identity credential for the application to establish trust between Microsoft Entra ID and the cluster's OIDC issuer.</p>
<p>Add the following code to your <code>main.tf</code> file to create the federated identity credential for the application:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azuread_application_federated_identity_credential" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  application_id = azuread_application.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  display_name   = azuread_application.example.display_name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  audiences      = ["api://AzureADTokenExchange"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  issuer         = azurerm_kubernetes_cluster.example.oidc_issuer_url</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  subject        = "system:serviceaccount:argocd:argocd-server"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Next, we need to create a service principal for the application. The service principal is what will be used to grant permissions and access to resources in Azure.</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azuread_service_principal" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  client_id = azuread_application.example.client_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Now we need to grant the service principal the necessary permissions to access Microsoft Graph on behalf of the user. Add the following code to your <code>main.tf</code> file to create the delegated permission grant:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azuread_service_principal_delegated_permission_grant" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  service_principal_object_id          = azuread_service_principal.example.object_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resource_service_principal_object_id = data.azuread_service_principal.msgraph.object_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  claim_values                         = ["openid", "profile", "email", "User.Read"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>info</div><div class="admonitionContent_BuS1"><p>The permission grant is optional. Without it, users will be asked to consent to the permissions. By creating this permission grant, we are pre-consenting on behalf of the users in our tenant so that they won't see the consent prompt when they sign in to Argo CD.</p></div></div>
<p>With the service principal and permissions in place, we can now grant Microsoft Entra ID users and groups access to Argo CD by creating app role assignments. Add the following code to your <code>main.tf</code> file to retrieve a Microsoft Entra ID group object ID.</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">data "azuread_group" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  display_name = "Argo CD Admins" # Update this to match your group name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>info</div><div class="admonitionContent_BuS1"><p>You might already have an existing group in Microsoft Entra ID that you want to use for Argo CD access. If so, update the <code>display_name</code> in the <code>azuread_group</code> data source above to match the name of your group. If not, create a new group in Microsoft Entra ID and add your user accounts to that group and then update the <code>display_name</code> accordingly.</p></div></div>
<p>Finally, add the following code to your <code>main.tf</code> file to allow members of the Microsoft Entra ID group to have access to the Argo CD application.</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azuread_app_role_assignment" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  app_role_id         = "00000000-0000-0000-0000-000000000000" # Default app role</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  principal_object_id = data.azuread_group.example.object_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resource_object_id  = azuread_service_principal.example.object_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-3-create-aks-cluster">Step 3: Create AKS cluster<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#step-3-create-aks-cluster" class="hash-link" aria-label="Direct link to Step 3: Create AKS cluster" title="Direct link to Step 3: Create AKS cluster" translate="no">​</a></h2>
<p>Now comes the easy part, creating the AKS cluster 😎</p>
<p>Add the following code to your <code>main.tf</code> file to create a resource group:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_resource_group" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name     = "myresourcegroup"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  location = "westus3" # Update to your preferred region</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Add the following code to your <code>main.tf</code> file to create an AKS cluster with OIDC issuer and workload identity enabled:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_kubernetes_cluster" "example" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name                      = "myakscluster"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  location                  = azurerm_resource_group.example.location</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  resource_group_name       = azurerm_resource_group.example.name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  dns_prefix                = "myakscluster"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  oidc_issuer_enabled       = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  workload_identity_enabled = true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  default_node_pool {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    name       = "default"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    node_count = 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  identity {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    type = "SystemAssigned"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>warning</div><div class="admonitionContent_BuS1"><p>This sample configuration creates a very basic AKS standard cluster. It is crucial to enable the OIDC issuer and workload identity for the integration with Microsoft Entra ID as these features allow the cluster to issue OIDC tokens that Argo CD pods can use to exchange with Microsoft Entra ID for authentication and authorization. You could also choose to deploy an AKS Automatic cluster instead which will have the OIDC issuer and workload identity enabled by default.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-4-deploy-argo-cd-extension-for-aks">Step 4: Deploy Argo CD extension for AKS<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#step-4-deploy-argo-cd-extension-for-aks" class="hash-link" aria-label="Direct link to Step 4: Deploy Argo CD extension for AKS" title="Direct link to Step 4: Deploy Argo CD extension for AKS" translate="no">​</a></h2>
<p>Okay, the moment we've all been waiting for, deploying the Argo CD extension for AKS!</p>
<p>Before we can deploy the extension, we need to create the necessary configuration settings for the OIDC integration with Microsoft Entra ID. This includes the OIDC configuration and the RBAC policy mappings.</p>
<p>Add the following code to your <code>main.tf</code> file to create more local variables for the OIDC configuration and RBAC policy mappings:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">locals {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  oidc_config = &lt;&lt;EOT</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">name: Microsoft Entra ID</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">issuer: https://login.microsoftonline.com/${data.azuread_client_config.current.tenant_id}/v2.0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">clientID: ${azuread_application.example.client_id}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">azure:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  useWorkloadIdentity: true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">requestedIDTokenClaims:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  groups:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    essential: true</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">requestedScopes:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - openid</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - profile</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  - email</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOT</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  policy_csv = &lt;&lt;EOT</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">g, "${data.azuread_group.example.object_id}", role:admin</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">EOT</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>This OIDC configuration specifies the issuer URL for Microsoft Entra ID, the client ID of our application, and the requested scopes and claims. The RBAC policy mapping specifies that members of the Microsoft Entra ID group we referenced earlier will be assigned the "admin" role in Argo CD.</p>
<p>With the configuration settings in place, we can now deploy the Argo CD extension for AKS. The nice thing here is that even though the Argo CD extension is in public preview, we can deploy it using the <code>azurerm_kubernetes_cluster_extension</code> resource in Terraform since the underlying call to Azure Resource Manager does not require any special handling for preview extensions.</p>
<p>Add the following code to your <code>main.tf</code> file to deploy the Argo CD extension for AKS:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">resource "azurerm_kubernetes_cluster_extension" "argocd" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  name           = "argocd"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  cluster_id     = azurerm_kubernetes_cluster.example.id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  extension_type = "Microsoft.ArgoCD"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  release_train  = "Preview"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  configuration_settings = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "azure.workloadIdentity.enabled"          = "true"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "azure.workloadIdentity.clientId"         = azuread_application.example.client_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "azure.workloadIdentity.entraSSOClientId" = azuread_application.example.client_id</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "redis-ha.enabled"                        = "false"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "global.domain"                           = "localhost:9000"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "configs.cm.admin\\.enabled"              = "false"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "configs.cm.oidc\\.config"                = local.oidc_config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    "configs.rbac.policy\\.csv"               = local.policy_csv</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<div class="theme-admonition theme-admonition-info admonition_xJq3 alert alert--info"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M7 2.3c3.14 0 5.7 2.56 5.7 5.7s-2.56 5.7-5.7 5.7A5.71 5.71 0 0 1 1.3 8c0-3.14 2.56-5.7 5.7-5.7zM7 1C3.14 1 0 4.14 0 8s3.14 7 7 7 7-3.14 7-7-3.14-7-7-7zm1 3H6v5h2V4zm0 6H6v2h2v-2z"></path></svg></span>info</div><div class="admonitionContent_BuS1"><p>This extension is primarily based on the <a href="https://github.com/argoproj/argo-helm/tree/main/charts/argo-cd" target="_blank" rel="noopener noreferrer" class="">open-source Argo CD Helm chart</a>, so the configuration settings follow a similar pattern to Helm values with some Azure-specific additions. Here's a quick breakdown:</p><ul>
<li class=""><strong><code>azure.workloadIdentity.*</code></strong> — convenience settings that wire up the workload identity integration for you</li>
<li class=""><strong><code>configs.cm.admin.enabled = "false"</code></strong> — disables the built-in admin user since we're using Microsoft Entra ID</li>
<li class=""><strong><code>global.domain = "localhost:9000"</code></strong> — sets the domain Argo CD uses for redirect URLs (update this for production)</li>
<li class=""><strong><code>configs.cm.oidc.config</code></strong> and <strong><code>configs.rbac.policy.csv</code></strong> — the OIDC and RBAC settings we defined earlier</li>
</ul></div></div>
<p>You might be asking, what about the version of the Argo CD extension? Since this extension is managed by Azure, we can omit the version and let Azure deploy the latest version and automatically upgrade it when new versions are available. This is one of the benefits of using an Azure-managed extension as it reduces the operational overhead of managing the extension and ensures that you are always running a secure and up-to-date version.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="step-5-run-terraform-and-verify">Step 5: Run Terraform and verify<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#step-5-run-terraform-and-verify" class="hash-link" aria-label="Direct link to Step 5: Run Terraform and verify" title="Direct link to Step 5: Run Terraform and verify" translate="no">​</a></h2>
<p>We're nearly there! The last step is to add some output values to our <code>main.tf</code> file so that we can easily retrieve the resource group name and AKS cluster name after the deployment. Then we'll run <code>terraform init</code> and <code>terraform apply</code> to create all the resources. Once the deployment is complete, we can retrieve the credentials for our AKS cluster and verify that we can sign in to Argo CD using Microsoft Entra ID.</p>
<p>Add the following code to your <code>main.tf</code> file to create the output values:</p>
<div class="language-hcl codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-hcl codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">output "rg_name" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  description = "The name of the Resource Group"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  value       = azurerm_resource_group.example.name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">output "aks_name" {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  description = "The name of the AKS cluster"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  value       = azurerm_kubernetes_cluster.example.name</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
<p>Open a terminal, navigate to the directory containing your <code>main.tf</code> file, and run the following commands:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">terraform init</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">terraform apply</span><br></div></code></pre></div></div>
<p>The <code>terraform init</code> command will initialize the Terraform working directory and download the necessary providers, while the <code>terraform apply</code> command will create all the resources defined in your <code>main.tf</code> file. Review the plan output and type <code>yes</code> to proceed with the deployment.</p>
<p>Once the deployment is complete, run the following command to retrieve the credentials for your AKS cluster:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">az aks get-credentials --resource-group $(terraform output -raw rg_name) --name $(terraform output -raw aks_name)</span><br></div></code></pre></div></div>
<p>Now that we have the credentials for our AKS cluster, we can verify that we can sign in to Argo CD using Microsoft Entra ID. Since we set the <code>global.domain</code> to <code>localhost:9000</code>, we will need to port-forward the Argo CD server to access it locally. Run the following command to port-forward the Argo CD server:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl port-forward svc/argocd-server -n argocd 9000:443</span><br></div></code></pre></div></div>
<p>Open a web browser and navigate to <a href="https://localhost:9000/" target="_blank" rel="noopener noreferrer" class="">https://localhost:9000</a>. You should be presented with a warning about an untrusted certificate. This is expected since we're using a self-signed certificate for local testing. Proceed past the warning to see the Argo CD sign-in page. Select the <strong>Log in via Microsoft Entra ID</strong> button and single sign-on (SSO) should kick in.</p>
<p>Navigate to the "User Info" page and you should see your Microsoft Entra ID user information along with the group claims.</p>
<p>If you have the Argo CD CLI installed, you can also verify the sign-in and view user information from the command line as well.</p>
<p>Keep the port-forwarding session running and open a new terminal window, then run the following command to sign in to Argo CD using SSO:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">argocd login localhost:9000 --sso --insecure</span><br></div></code></pre></div></div>
<p>You should see a browser window open to complete the authentication flow. After you sign in, you can run the following command to view your user information:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">argocd account get-user-info</span><br></div></code></pre></div></div>
<p>You will see the same user information and group claims as you saw in the web UI, confirming that the OIDC integration with Microsoft Entra ID is working correctly.</p>
<p>Congratulations! You have successfully secured Argo CD with Microsoft Entra ID for authentication and authorization. You can now manage your Kubernetes applications using Argo CD with the confidence that your deployment is secure and integrated with Azure services.</p>
<p>:::tip Going to production?</p>
<p>This walkthrough uses localhost URLs for local testing. Before deploying to production, make sure to:</p>
<ul>
<li class="">Update the <code>web</code> redirect URI in the Entra ID app registration and <code>global.domain</code> in the extension config to your real domain</li>
<li class="">Set up an Ingress controller with a valid TLS certificate</li>
<li class="">Consider adding granular RBAC roles beyond <code>role:admin</code> (for example, <code>role:readonly</code> for dev teams)</li>
<li class="">Review the <a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/" target="_blank" rel="noopener noreferrer" class="">Argo CD RBAC documentation</a> for fine-grained policy options</li>
</ul>
<p>:::</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="cleanup">Cleanup<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#cleanup" class="hash-link" aria-label="Direct link to Cleanup" title="Direct link to Cleanup" translate="no">​</a></h2>
<p>When you're done experimenting, you can tear down all the resources created in this walkthrough by running:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">terraform destroy</span><br></div></code></pre></div></div>
<p>Review the plan output and type <code>yes</code> to confirm. This will remove the AKS cluster, Argo CD extension, Microsoft Entra ID application, and all related resources.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="summary">Summary<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#summary" class="hash-link" aria-label="Direct link to Summary" title="Direct link to Summary" translate="no">​</a></h2>
<p>In this post, we walked through how to secure Argo CD with Microsoft Entra ID for authentication and authorization. We covered the steps to create a Microsoft Entra ID application for Argo CD, configure OIDC settings, create RBAC policy mappings, deploy the Argo CD extension for AKS, and verify the integration. By leveraging Microsoft Entra ID, you can enhance the security of your Argo CD deployment and take advantage of Azure's identity and access management features.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="whats-next">What's next?<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#whats-next" class="hash-link" aria-label="Direct link to What's next?" title="Direct link to What's next?" translate="no">​</a></h3>
<ul>
<li class="">Expose Argo CD with an Ingress controller and a real TLS certificate</li>
<li class="">Deploy a sample GitOps application through Argo CD to see it in action</li>
<li class="">Add granular RBAC roles for different teams in your organization</li>
<li class="">Explore the <a href="https://learn.microsoft.com/azure/azure-arc/kubernetes/tutorial-use-gitops-argocd" target="_blank" rel="noopener noreferrer" class="">Argo CD extension docs</a> for more tutorials</li>
</ul>
<p>Tried this out? We'd love to hear how it went — drop us feedback in <a href="https://github.com/Azure/AKS/discussions" target="_blank" rel="noopener noreferrer" class="">GitHub Discussions</a> or open an <a href="https://github.com/Azure/AKS/issues/new" target="_blank" rel="noopener noreferrer" class="">issue</a> if you run into anything.</p>
<p>Happy deploying!</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="resources">Resources<a href="https://blog.aks.azure.com/2026/04/22/argocd-extension-with-microsoft-entra#resources" class="hash-link" aria-label="Direct link to Resources" title="Direct link to Resources" translate="no">​</a></h2>
<ul>
<li class=""><a href="https://gist.github.com/pauldotyu/8d54d73300669efa1296e56f13e9e27d" target="_blank" rel="noopener noreferrer" class="">Full Terraform code for this walkthrough</a></li>
<li class=""><a href="https://techcommunity.microsoft.com/blog/azurearcblog/announcing-public-preview-of-argo-cd-extension-on-aks-and-azure-arc-enabled-kube/4504497" target="_blank" rel="noopener noreferrer" class="">Announcing public preview of Argo CD extension on AKS and Azure Arc-enabled Kubernetes - Microsoft Tech Community</a></li>
<li class=""><a href="https://argo-cd.readthedocs.io/en/stable/" target="_blank" rel="noopener noreferrer" class="">Argo CD documentation</a></li>
<li class=""><a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/user-management/microsoft/" target="_blank" rel="noopener noreferrer" class="">Argo CD Microsoft Entra ID integration</a></li>
<li class=""><a href="https://argo-cd.readthedocs.io/en/stable/operator-manual/rbac/" target="_blank" rel="noopener noreferrer" class="">Argo CD RBAC documentation</a></li>
<li class=""><a href="https://learn.microsoft.com/azure/aks/workload-identity-overview" target="_blank" rel="noopener noreferrer" class="">Workload identity for AKS</a></li>
<li class=""><a href="https://learn.microsoft.com/azure/developer/terraform/overview" target="_blank" rel="noopener noreferrer" class="">Terraform on Azure</a></li>
</ul>]]></content:encoded>
            <category>Argo CD</category>
            <category>Entra</category>
            <category>Extensions</category>
            <category>Security</category>
            <category>Best Practices</category>
            <category>Terraform</category>
        </item>
        <item>
            <title><![CDATA[Control AI spend with per-application token rate limiting using Application Network and agentgateway]]></title>
            <link>https://blog.aks.azure.com/2026/04/17/appnet-agentgateway</link>
            <guid>https://blog.aks.azure.com/2026/04/17/appnet-agentgateway</guid>
            <pubDate>Fri, 17 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Use Application Network identity and agentgateway to enforce per-application token rate limiting and control shared AI service spend on AKS.]]></description>
            <content:encoded><![CDATA[<p>As organizations scale AI adoption, platform teams must balance two competing goals:</p>
<ul>
<li class="">Enable broad, low-friction access to AI services</li>
<li class="">Prevent a single application from exhausting shared quotas</li>
</ul>
<p>This article describes a <strong>platform-oriented approach</strong> to controlling AI spend using <strong>Azure Kubernetes Application Network</strong> (AppNet) and <strong>agentgateway</strong>. By leveraging <strong>workload identity already present in the network</strong>, you can enforce <strong>per-application, token-based rate limiting</strong> without issuing API keys to every application.</p>
<p>By adopting this platform-oriented approach, you gain centralized control over AI spending, eliminate secrets distribution, and improve operational efficiency.</p>
<p><strong>Azure Kubernetes Application Network</strong> (currently in Public Preview) is Azure's fully managed L7 network for AKS, providing security, observability, and control for your L7 network out of the box. This article focuses on AppNet's secure, automatic mTLS authentication.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="the-challenge-with-shared-ai-quotas">The challenge with shared AI quotas<a href="https://blog.aks.azure.com/2026/04/17/appnet-agentgateway#the-challenge-with-shared-ai-quotas" class="hash-link" aria-label="Direct link to The challenge with shared AI quotas" title="Direct link to The challenge with shared AI quotas" translate="no">​</a></h2>
<p>A typical AI inference workflow looks like this:</p>
<ol>
<li class="">Create an AI or model provider account.</li>
<li class="">Distribute a shared API key.</li>
<li class="">Allow applications to call the service.</li>
</ol>
<p>This approach works initially, but as usage grows, teams often encounter a shared failure mode:</p>
<blockquote>
<p><strong>403: Insufficient Quota</strong></p>
</blockquote>
<p>When the quota is exhausted:</p>
<ul>
<li class="">All applications are affected simultaneously.</li>
<li class="">It is difficult to identify which workload consumed the quota.</li>
<li class="">One misbehaving application can impact the entire platform.</li>
</ul>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-per-application-api-keys-dont-scale">Why per-application API keys don’t scale<a href="https://blog.aks.azure.com/2026/04/17/appnet-agentgateway#why-per-application-api-keys-dont-scale" class="hash-link" aria-label="Direct link to Why per-application API keys don’t scale" title="Direct link to Why per-application API keys don’t scale" translate="no">​</a></h2>
<p>A common mitigation is to issue one API key per application, each with an independent quota. While this improves attribution, it introduces new operational challenges:</p>
<ul>
<li class="">Manual key provisioning by platform engineers</li>
<li class="">Secret distribution and rotation</li>
<li class="">Slower onboarding for new applications</li>
<li class="">Increased operational overhead</li>
</ul>
<p>Instead of removing blockers, the platform becomes one.</p>
<hr>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="a-platform-oriented-solution">A platform-oriented solution<a href="https://blog.aks.azure.com/2026/04/17/appnet-agentgateway#a-platform-oriented-solution" class="hash-link" aria-label="Direct link to A platform-oriented solution" title="Direct link to A platform-oriented solution" translate="no">​</a></h2>
<p>A more scalable approach is to shift rate limiting out of application code and into the <strong>platform layer</strong>.</p>
<p><strong>The key idea is: Identify applications by identity, not by secrets.</strong></p>
<p>Azure Kubernetes Application Network automatically establishes workload identity through mutual TLS (mTLS) using Istio's ztunnel proxy. By enforcing policy based on this identity, the platform can apply per-application limits transparently.</p>
<p>This solution combines AppNet's automatic mTLS identity on all traffic with agentgateway's ability to define Token Rate Limiting buckets with CEL expressions to accomplish per-application Token budgets. By configuring agentgateway to terminate mTLS directly, we're able to have it participate in the network just like an Istio waypoint would, which gives it direct access to the TLS identity on the wire.</p>
<!-- -->
<p>In contrast with our initial scenario, the Azure Foundry API Key is only accessible to the
agentgateway, so application teams don't touch any secrets, while AppNet provides
per-application identity information on the wire.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="deep-dive-configure-application-network-and-agentgateway-for-token-based-rate-limiting">Deep Dive: Configure <strong>Application Network</strong> and <strong>agentgateway</strong> for token-based rate limiting<a href="https://blog.aks.azure.com/2026/04/17/appnet-agentgateway#deep-dive-configure-application-network-and-agentgateway-for-token-based-rate-limiting" class="hash-link" aria-label="Direct link to deep-dive-configure-application-network-and-agentgateway-for-token-based-rate-limiting" title="Direct link to deep-dive-configure-application-network-and-agentgateway-for-token-based-rate-limiting" translate="no">​</a></h2>
<p>Let's look at the key components that make up our rate limit.
For step-by-step setup details, see the <a href="https://gist.github.com/therealmitchconnors/b2776cea7a72e25f805b0228eef986cc#file-details-md" target="_blank" rel="noopener noreferrer" class="">detailed configuration gist</a>.
First, let's configure agentgateway to interoperate with AppNet, which exposes an
Istio-compliant control plane:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> agentgateway.dev/v1alpha1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> AgentgatewayParameters</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> agentgateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">deployment</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">template</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">labels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">istio.io/dataplane-mode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> none</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">networking.istio.io/tunnel</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> http</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">istio</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">{</span><span class="token punctuation" style="color:#393A34">}</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">rawConfig</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">binds</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">listeners</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">15008</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">tunnelProtocol</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> hboneGateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">service</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">ports</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> hbone</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">15008</span><br></div></code></pre></div></div>
<p>Next, let's create a policy to perform token-based rate limiting on this gateway's traffic, using the AppNet mTLS identity as the bucket key.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> agentgateway.dev/v1alpha1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> AgentgatewayPolicy</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> minute</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">token</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">budget</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">targetRefs</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">group</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> gateway.networking.k8s.io</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> gateway</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">traffic</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">rateLimit</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">global</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">backendRef</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">group</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">""</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Service</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ratelimit</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ratelimit</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8081</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">descriptors</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">entries</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">expression</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> source.identity.namespace</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> client_ns</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">unit</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Tokens</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">domain</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> token</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">budgets</span><br></div></code></pre></div></div>
<p>Finally, let's configure our Rate Limiting Server to deny traffic after 100 tokens per application per minute (in reality, we'd need a much bigger budget, but this low budget lets us easily demo exceeding the rate limiter).</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">data</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">config.yaml</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">    domain: token-budgets</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">    descriptors:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">      # Rate limit by client namespace</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">      - key: client_ns</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">        rate_limit:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          unit: minute</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          requests_per_unit: 100</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ConfigMap</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> ratelimit</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">config</span><br></div></code></pre></div></div>
<p>Now that we've configured our rate limiter, let's send some completion requests to Azure Foundry to see it in action. Full test instructions are available in the <a href="https://gist.github.com/therealmitchconnors/b2776cea7a72e25f805b0228eef986cc#file-details-md" target="_blank" rel="noopener noreferrer" class="">details gist</a>:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">curl gateway.default/v1/chat/completions -i -H content-type:application/json  -d '{</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   "model": "",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   "messages": [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       "role": "system",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       "content": "You are a helpful assistant."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     },</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       "role": "user",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       "content": "Write a short haiku about cloud computing."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"> }'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">HTTP/1.1 200 OK</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">...(succeeds several times)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">curl gateway.default/v1/chat/completions -i -H content-type:application/json  -d '{</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   "model": "",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   "messages": [</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       "role": "system",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       "content": "You are a helpful assistant."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     },</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       "role": "user",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">       "content": "Write a short haiku about cloud computing."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">     }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">   ]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"> }'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">HTTP/1.1 429 Too Many Requests</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">content-length: 0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">date: Fri, 03 Apr 2026 22:59:18 GMT</span><br></div></code></pre></div></div>
<p>Once we've exhausted our token budget, all requests from httpbin to Azure Foundry will be
blocked by agentgateway until our budget resets in 1 minute. Requests from other
applications can proceed without being blocked, because each application has its own rate
limit bucket, keyed on AppNet identity.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://blog.aks.azure.com/2026/04/17/appnet-agentgateway#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>By adopting this platform-oriented approach, you gain centralized control over AI spending,
eliminate secrets distribution, and improve operational efficiency. Applications gain
transparent rate limiting without code changes, while platform teams reduce overhead and
enforce fair resource allocation across the organization. This is just one of the many ways
you can benefit from Application Network, built on Istio's Ambient Mode, with readily
available open source tools like agentgateway. To learn more, see <a href="https://learn.microsoft.com/azure/application-network/overview" target="_blank" rel="noopener noreferrer" class="">Application Network
documentation</a> and
<a href="https://agentgateway.dev/" target="_blank" rel="noopener noreferrer" class="">agentgateway documentation</a>.</p>]]></content:encoded>
            <category>Application Network</category>
            <category>AI</category>
        </item>
        <item>
            <title><![CDATA[AI Inference on AKS enabled by Azure Arc: Generative AI using Triton and TensorRT‑LLM]]></title>
            <link>https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5</link>
            <guid>https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5</guid>
            <pubDate>Thu, 09 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Run local edge generative AI by deploying Triton with the TensorRT-LLM backend on AKS enabled by Azure Arc to serve Qwen2.5 7B Instruct with GPU acceleration.]]></description>
            <content:encoded><![CDATA[<p>In this post, you’ll deploy NVIDIA Triton Inference Server on your Azure Kubernetes Service (AKS) enabled by Azure Arc cluster to serve a Qwen‑based generative model using the TensorRT‑LLM backend. By the end, you’ll have a working generative AI inference pipeline running locally on your on‑premises GPU hardware.</p>
<p><img decoding="async" loading="lazy" alt="Deploying generative AI inference with open-source inference servers on AKS enabled by Azure Arc" src="https://blog.aks.azure.com/assets/images/hero-image-ccdd9a0aa85c1af434a106a2984267aa.png" width="650" height="467" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="introduction">Introduction<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#introduction" class="hash-link" aria-label="Direct link to Introduction" title="Direct link to Introduction" translate="no">​</a></h2>
<p>Earlier parts of this series used runtimes such as Ollama and vLLM to quickly enable local LLM inference and iterate on model behavior. In this post, you move to a more infrastructure‑centric deployment model using <strong>NVIDIA Triton Inference Server with the TensorRT‑LLM backend</strong> and an instruction‑tuned model. This approach prioritizes <strong>predictable performance, efficient GPU utilization, and long‑running inference services</strong>, making it well suited for edge and on‑prem environments where capacity is fixed and inference must operate reliably as part of the platform.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>PREREQUISITES AND SCOPE</div><div class="admonitionContent_BuS1"><p>Before you begin, ensure the <a class="" href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-2">Part 2 prerequisites</a> are met, including a GPU node configured for <strong>nvidia.com/gpu</strong>. Cluster nodes need <strong>internet access</strong> to download models. <strong>Expect a delay</strong> during initial deployment while the pod downloads and caches model files.</p><p>This tutorial is designed for experimentation and learning. The configurations shown are not production-ready and should not be deployed to production environments without additional security, reliability, performance hardening, and following standard practices.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="tensorrtllm-deployment-pipeline-on-triton">TensorRT‑LLM deployment pipeline on Triton<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#tensorrtllm-deployment-pipeline-on-triton" class="hash-link" aria-label="Direct link to TensorRT‑LLM deployment pipeline on Triton" title="Direct link to TensorRT‑LLM deployment pipeline on Triton" translate="no">​</a></h2>
<p>Unlike Ollama, vLLM, or ONNX-based models, where inference engines initialize at runtime, TensorRT-LLM follows an explicit build-then-serve pipeline. The model is compiled into a GPU-specific engine ahead of time, and Triton uses this engine for runtime inference. Once a TensorRT‑LLM engine is built, it can be deployed to any node as long as the hardware and software environment match the conditions it was built for. At a high level, the pipeline consists of three phases:</p>
<table><thead><tr><th>Phase</th><th>Purpose</th><th>Key activities</th></tr></thead><tbody><tr><td><strong>Preparation (Provisioner)</strong></td><td>Transform a standard model into a high‑performance, GPU‑specific executable</td><td>Model weights are converted from Hugging Face format into a TensorRT‑LLM checkpoint, optional quantization is applied to reduce memory footprint, and a GPU‑specific TensorRT engine is compiled with CUDA kernel fusion for maximum throughput.</td></tr><tr><td><strong>Configuration (Template)</strong></td><td>Align the model’s physical characteristics with Triton’s execution requirements</td><td>Template‑based Triton configurations are populated with concrete values such as engine paths, batching limits, and instance counts, ensuring preprocessing, engine, and postprocessing stages agree on shapes and runtime behavior.</td></tr><tr><td><strong>Inference (Runtime)</strong></td><td>Orchestrate live request execution and model serving</td><td>Triton Inference Server loads the prebuilt TensorRT‑LLM engine, orchestrates the ensemble pipeline end‑to‑end, manages concurrency and in‑flight batching, and exposes HTTP and gRPC endpoints for inference.</td></tr></tbody></table>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="for-reference">For reference<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#for-reference" class="hash-link" aria-label="Direct link to For reference" title="Direct link to For reference" translate="no">​</a></h3>
<ul>
<li class=""><a href="https://docs.nvidia.com/tensorrt-llm" target="_blank" rel="noopener noreferrer" class="">TensorRT‑LLM overview</a></li>
<li class=""><a href="https://github.com/NVIDIA/TensorRT-LLM" target="_blank" rel="noopener noreferrer" class="">TensorRT‑LLM engine build workflow</a></li>
<li class=""><a href="https://docs.nvidia.com/deeplearning/triton-inference-server" target="_blank" rel="noopener noreferrer" class="">Triton model repository and ensemble models</a></li>
<li class=""><a href="https://github.com/triton-inference-server/tensorrtllm_backend" target="_blank" rel="noopener noreferrer" class="">TensorRT‑LLM Triton backend</a></li>
<li class=""><a href="https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs" target="_blank" rel="noopener noreferrer" class="">Triton Inference Server architecture</a></li>
<li class=""><a href="https://docs.nvidia.com/deeplearning/triton-inference-server/user-guide/docs/user_guide/model_configuration.html" target="_blank" rel="noopener noreferrer" class="">Performance and batching concepts</a></li>
</ul>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-this-pipeline-matters">Why this pipeline matters<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#why-this-pipeline-matters" class="hash-link" aria-label="Direct link to Why this pipeline matters" title="Direct link to Why this pipeline matters" translate="no">​</a></h3>
<p>This compile-then-serve model differentiates TensorRT-LLM from earlier approaches in this series. By shifting optimization and hardware specialization to build time, TensorRT-LLM delivers deterministic performance and efficient GPU utilization, which are key requirements for edge and on-prem AI deployments.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tensorrt-llm-build-and-deployment-directory-structure">TensorRT-LLM build and deployment directory structure<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#tensorrt-llm-build-and-deployment-directory-structure" class="hash-link" aria-label="Direct link to TensorRT-LLM build and deployment directory structure" title="Direct link to TensorRT-LLM build and deployment directory structure" translate="no">​</a></h3>
<p>The following directory structure represents how artifacts flow through the build and deployment phases when using TensorRT-LLM with Triton. This structure helps visualize how model assets, hardware-specific artifacts, and runtime configuration are separated.</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">/models</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── qwen-raw</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   ├── model.safetensors</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   ├── tokenizer.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   ├── tokenizer_config.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   └── config.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── model_repository</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   ├── tllm_checkpoint_qwen_7b_int41</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── config.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   │   ├── *.ckpt</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   │   └── rank0.safetensors</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   └── tllm_engine_qwen_7b_int41</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│       ├── config.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│       └── rank0.engine</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── engines</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   └── qwen_7b_int4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│       ├── config.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│       └── rank0.engine</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">├── tokenizer</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│   └── qwen_7b_int4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│       ├── tokenizer.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│       └── tokenizer_config.json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">│</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">└── triton_model_repo</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ├── ensemble</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    │   ├── 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    │   └── config.pbtxt</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ├── preprocessing</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    │   ├── 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    │   └── config.pbtxt</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ├── tensorrt_llm</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    │   ├── 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    │   └── config.pbtxt</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    ├── postprocessing</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    │   ├── 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    │   └── config.pbtxt</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    └── tensorrt_llm_bls</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        ├── 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        └── config.pbtxt</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="phase-1-preparation">Phase 1: Preparation<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#phase-1-preparation" class="hash-link" aria-label="Direct link to Phase 1: Preparation" title="Direct link to Phase 1: Preparation" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="preparing-storage-for-the-model-repository">Preparing storage for the model repository<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#preparing-storage-for-the-model-repository" class="hash-link" aria-label="Direct link to Preparing storage for the model repository" title="Direct link to Preparing storage for the model repository" translate="no">​</a></h3>
<p>In addition to the prerequisites, you'll need persistent <strong>storage</strong> for model files. First, create a <strong>triton-inference</strong> namespace and a <strong>PersistentVolumeClaim</strong> (PVC) for the model repository.</p>
<p>Save the following YAML as <code>triton-pvc.yaml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># THE NAMESPACE</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Creates an isolated logical boundary for your Triton resources.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># All subsequent resources must reference this namespace to communicate.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Namespace</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># THE STORAGE (PVC)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Requests a persistent disk from the cluster to store your model weights.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># This ensures that if the Pod restarts, your downloaded models remain intact.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> PersistentVolumeClaim</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">repository</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">pvc</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference </span><span class="token comment" style="color:#999988;font-style:italic"># Ensures the storage is available within your namespace</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># ReadWriteOnce (RWO) allows the volume to be mounted as read-write by a single node.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">accessModes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> ReadWriteOnce</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Omit storageClassName to use the cluster's default StorageClass.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">resources</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">requests</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Allocates 100GB of space. Ensure your underlying disk provider</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># supports this size.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">storage</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 100Gi</span><br></div></code></pre></div></div>
<p>Apply the manifest and verify the PVC status is <strong>Bound</strong> (storage provisioned):</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f triton-pvc.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get pvc -n triton-inference</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploy-a-helper-pod-to-download-the-model">Deploy a helper pod to download the model<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#deploy-a-helper-pod-to-download-the-model" class="hash-link" aria-label="Direct link to Deploy a helper pod to download the model" title="Direct link to Deploy a helper pod to download the model" translate="no">​</a></h3>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>This tutorial uses a helper pod for downloading models, installing tools, and converting models to engines, making it easier to troubleshoot and iterate. For automated workflows, consider using a Kubernetes <a href="https://kubernetes.io/docs/concepts/workloads/controllers/job/" target="_blank" rel="noopener noreferrer" class="">Job</a> instead, which handles retries and completion tracking natively.</p></div></div>
<p>Next, you'll deploy a temporary helper pod that acts as a provisioner. You'll use this pod to download the model into persistent storage, convert the raw weights into TensorRT-LLM checkpoints, and build a GPU-specific TensorRT engine. This pod <strong>requires a GPU</strong> during the preparation stage. Save the following YAML as <code>triton-provisioner.yaml</code>.</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># THE PROVISIONER POD</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># This pod serves as your "workspace" to prepare the environment.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Its purpose is to download raw models, convert them into checkpoints,</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># and compile the optimized TensorRT engines for deployment.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Pod</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">provisioner</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">containers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> provisioner</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># TRT-LLM Image: Contains the necessary NVIDIA libraries (CUDA, TensorRT-LLM)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># and the Triton server binary required for engine building and testing.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> nvcr.io/nvidia/tritonserver</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">25.01</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">trtllm</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">python</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">py3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Keep the pod alive indefinitely so you can 'kubectl exec' into it</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># and run your download/build commands manually or via scripts.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">command</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"/bin/sh"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"-c"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"sleep infinity"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">resources</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">limits</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># A GPU is required for the 'trtllm-build' step to compile</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># the engine for the specific GPU architecture in your cluster.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">nvidia.com/gpu</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">volumeMounts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># Mount the PVC to /models inside the container.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># All downloads and engine files saved here will persist across pod restarts.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">storage</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">mountPath</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /models</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">storage</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Connects the internal /models path to the 100Gi PVC defined earlier.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">persistentVolumeClaim</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">claimName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">repository</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">pvc</span><br></div></code></pre></div></div>
<p>Apply the manifest and wait for the triton-provisioner pod to reach Running (you can stop watching with Ctrl+C once it’s running).</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>EXPECT A WAIT</div><div class="admonitionContent_BuS1"><p>The provisioning pod may take ten minutes or longer to reach the Running state because it pulls a large Triton image that includes CUDA and TensorRT-LLM libraries.</p></div></div>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f triton-provisioner.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get pods -n triton-inference -w</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="tensorrt-llm-model-preparation-and-triton-repository-assembly">TensorRT-LLM Model Preparation and Triton Repository Assembly<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#tensorrt-llm-model-preparation-and-triton-repository-assembly" class="hash-link" aria-label="Direct link to TensorRT-LLM Model Preparation and Triton Repository Assembly" title="Direct link to TensorRT-LLM Model Preparation and Triton Repository Assembly" translate="no">​</a></h3>
<p>Save the following script as a <code>prepare-trtllm.sh</code> file. This script prepares a Hugging Face LLM for inference with Triton by downloading the model, converting and quantizing it into a GPU‑specific TensorRT‑LLM engine, assembling the Triton model repository, and generating all required configuration files for end‑to‑end inference.</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">#!/usr/bin/env bash</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">set -euo pipefail</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Print a helpful message if any command fails</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">trap 'echo "FAILED at line $LINENO: $BASH_COMMAND" &gt;&amp;2' ERR</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "Starting TensorRT-LLM preparation pipeline..."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Paths</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">MODELS_ROOT="/models"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">RAW_MODEL_DIR="${MODELS_ROOT}/qwen-raw"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">CHECKPOINT_DIR="${MODELS_ROOT}/model_repository/tllm_checkpoint_qwen_7b_int41"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ENGINE_BUILD_DIR="${MODELS_ROOT}/model_repository/tllm_engine_qwen_7b_int41"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ENGINES_DIR="${MODELS_ROOT}/engines/qwen_7b_int4"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">TOKENIZER_DIR="${MODELS_ROOT}/tokenizer/qwen_7b_int4"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">TRITON_REPO_DIR="${MODELS_ROOT}/triton_model_repo"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">FILL_TEMPLATE_SCRIPT="/app/tools/fill_template.py"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">MODEL_ID="Qwen/Qwen2.5-7B-Instruct"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Small helper to print what is running</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run() {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  echo ""</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  echo "RUN: $*"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "$@"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Validate environment</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">if [[ ! -d "${MODELS_ROOT}" ]]; then</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  echo "FAILED: ${MODELS_ROOT} does not exist. Ensure the PVC is mounted at /models and run inside the provisioner pod." &gt;&amp;2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  exit 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">if ! command -v python3 &gt;/dev/null 2&gt;&amp;1; then</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  echo "FAILED: python3 not found. Run inside the TRT LLM provisioner image." &gt;&amp;2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  exit 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">if ! command -v trtllm-build &gt;/dev/null 2&gt;&amp;1; then</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  echo "FAILED: trtllm-build not found. Run inside the TRT LLM provisioner image." &gt;&amp;2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  exit 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Enter persistent directory</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run cd "${MODELS_ROOT}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Create required directories (idempotent)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run mkdir -p "${RAW_MODEL_DIR}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run mkdir -p "${CHECKPOINT_DIR}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run mkdir -p "${ENGINE_BUILD_DIR}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo ""</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "Downloading model ${MODEL_ID} into ${RAW_MODEL_DIR} ..."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Download raw model</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Prefer huggingface-cli when available, otherwise fall back to python module invocation</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">if command -v huggingface-cli &gt;/dev/null 2&gt;&amp;1; then</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  run huggingface-cli download "${MODEL_ID}" --local-dir "${RAW_MODEL_DIR}" --exclude "*.bin" "*.pth"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">else</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  run python3 -m huggingface_hub.cli download "${MODEL_ID}" --local-dir "${RAW_MODEL_DIR}" --exclude "*.bin" "*.pth"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Convert to quantized checkpoint</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run python3 /app/examples/qwen/convert_checkpoint.py \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --model_dir "${RAW_MODEL_DIR}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output_dir "${CHECKPOINT_DIR}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --dtype float16 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --use_weight_only \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --weight_only_precision int4</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Build the compressed engine</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run trtllm-build \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --checkpoint_dir "${CHECKPOINT_DIR}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --output_dir "${ENGINE_BUILD_DIR}" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --gemm_plugin float16 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --max_batch_size 4 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --max_input_len 2048 \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  --max_seq_len 3072</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo ""</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "Finalizing Triton model repository..."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Create folder layout + copy engine files</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run mkdir -p "${ENGINES_DIR}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run cp -f "${ENGINE_BUILD_DIR}/"* "${ENGINES_DIR}/"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Copy Triton model repository templates (overwrite allowed)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run mkdir -p "${TRITON_REPO_DIR}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run cp -rf /app/all_models/inflight_batcher_llm/* "${TRITON_REPO_DIR}/"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Verify files copied</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run ls -la "${TRITON_REPO_DIR}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Copy tokenizer files</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run mkdir -p "${TOKENIZER_DIR}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run cp -f "${RAW_MODEL_DIR}/"tokenizer* "${TOKENIZER_DIR}/"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Fill in model configs with fill_template.py</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">if [[ ! -f "${FILL_TEMPLATE_SCRIPT}" ]]; then</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  echo "FAILED: fill_template.py not found at ${FILL_TEMPLATE_SCRIPT}" &gt;&amp;2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  exit 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">fi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export ENGINE_DIR="${ENGINES_DIR}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export TOKENIZER_DIR="${TOKENIZER_DIR}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export MODEL_FOLDER="${TRITON_REPO_DIR}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export TRITON_MAX_BATCH_SIZE="1"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export INSTANCE_COUNT="1"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export MAX_QUEUE_DELAY_MS="0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export MAX_QUEUE_SIZE="0"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export FILL_TEMPLATE_SCRIPT="${FILL_TEMPLATE_SCRIPT}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export DECOUPLED_MODE="false"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">export LOGITS_DATATYPE="TYPE_FP32"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Basic validation that expected config files exist before patching</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">for f in \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "${MODEL_FOLDER}/ensemble/config.pbtxt" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "${MODEL_FOLDER}/preprocessing/config.pbtxt" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "${MODEL_FOLDER}/tensorrt_llm/config.pbtxt" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "${MODEL_FOLDER}/postprocessing/config.pbtxt" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "${MODEL_FOLDER}/tensorrt_llm_bls/config.pbtxt"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">do</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  if [[ ! -f "$f" ]]; then</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    echo "FAILED: expected config file missing: $f" &gt;&amp;2</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    exit 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  fi</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">done</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Update ensemble config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run python3 "${FILL_TEMPLATE_SCRIPT}" -i "${MODEL_FOLDER}/ensemble/config.pbtxt" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},logits_datatype:${LOGITS_DATATYPE}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Update preprocessing config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run python3 "${FILL_TEMPLATE_SCRIPT}" -i "${MODEL_FOLDER}/preprocessing/config.pbtxt" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "tokenizer_dir:${TOKENIZER_DIR},triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},preprocessing_instance_count:${INSTANCE_COUNT}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Update tensorrt_llm config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run python3 "${FILL_TEMPLATE_SCRIPT}" -i "${MODEL_FOLDER}/tensorrt_llm/config.pbtxt" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "triton_backend:tensorrtllm,triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},decoupled_mode:${DECOUPLED_MODE},engine_dir:${ENGINE_DIR},max_queue_delay_microseconds:${MAX_QUEUE_DELAY_MS},batching_strategy:inflight_fused_batching,max_queue_size:${MAX_QUEUE_SIZE},encoder_input_features_data_type:TYPE_FP16,logits_datatype:${LOGITS_DATATYPE}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Update postprocessing config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run python3 "${FILL_TEMPLATE_SCRIPT}" -i "${MODEL_FOLDER}/postprocessing/config.pbtxt" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "tokenizer_dir:${TOKENIZER_DIR},triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},postprocessing_instance_count:${INSTANCE_COUNT}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Update BLS config</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">run python3 "${FILL_TEMPLATE_SCRIPT}" -i "${MODEL_FOLDER}/tensorrt_llm_bls/config.pbtxt" \</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  "triton_max_batch_size:${TRITON_MAX_BATCH_SIZE},decoupled_mode:${DECOUPLED_MODE},bls_instance_count:${INSTANCE_COUNT},logits_datatype:${LOGITS_DATATYPE}"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo ""</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "Triton model repository templates have been successfully updated."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "TensorRT-LLM preparation completed successfully."</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">echo "Stop the provisioner pod after this to release the GPU for the Triton server."</span><br></div></code></pre></div></div>
<p>Run the <code>prepare-trtllm.sh</code> script inside provisioner POD using the following sequence of commands:</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>LONG BUILD TIME</div><div class="admonitionContent_BuS1"><p>The engine build step may take 30 minutes or more depending on your GPU. Do not interrupt the process.</p></div></div>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Copy the prepare-trtllm.sh script to the provisioner pod</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl cp ./prepare-trtllm.sh triton-inference/triton-provisioner:/models/prepare-trtllm.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Start a bash session inside the pod</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl exec -it triton-provisioner -n triton-inference -- /bin/bash</span><br></div></code></pre></div></div>
<p>Run the following commands inside bash session:</p>
<div class="language-bash codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-bash codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Fix potential Windows line-ending issues (safe no-op on Linux files)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">sed -i 's/\r$//' /models/prepare-trtllm.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Make the script executable</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">chmod +x /models/prepare-trtllm.sh</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Run the script</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">/models/prepare-trtllm.sh</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploying-triton-inference-server">Deploying Triton inference server<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#deploying-triton-inference-server" class="hash-link" aria-label="Direct link to Deploying Triton inference server" title="Direct link to Deploying Triton inference server" translate="no">​</a></h3>
<p>With the engine in place, you'll deploy Triton. Save this as <code>triton-deployment.yaml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># THE SERVICE (The "Phone Number" for your Model)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># This exposes the Triton Inference Server to users outside or inside the cluster.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Namespace</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Service</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># LoadBalancer provides a public IP (on cloud providers like AKS/EKS/GKE).</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Change to 'ClusterIP' if you only want internal access.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">type</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> LoadBalancer</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">ports</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic"># HTTP Endpoint: Standard REST queries (standard for most web apps)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> http</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">targetPort</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic"># gRPC Endpoint: High-performance, low-latency streaming (best for LLMs)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> grpc</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8001</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">targetPort</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8001</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic"># Metrics Endpoint: For Prometheus/Grafana monitoring (GPU usage, throughput)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> metrics</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8002</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">targetPort</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8002</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">selector</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic"># Connects this Service to any Pod labeled 'app: triton-server'</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># 5. THE DEPLOYMENT (The Inference Server)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># This is the actual "Running Process" that serves the model to users.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> apps/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Deployment</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># This can be flipped 1 or 0 to start and stop the Triton server during troubleshooting.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">replicas</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">selector</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">matchLabels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">template</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">labels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">containers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># Must match the version used in your Provisioner to ensure Engine compatibility.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> nvcr.io/nvidia/tritonserver</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">25.01</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">trtllm</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">python</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">py3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">args</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tritonserver"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic"># Point to the specific folder where your 'ensemble', 'tensorrt_llm', etc., are stored.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--model-repository=/models/triton_model_repo"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic"># Prevents Triton from guessing configurations; ensures it uses your exact .pbtxt files.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--disable-auto-complete-config"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic"># Increases timeout for the Python backend (needed for heavy LLM tokenizers/preprocessing).</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--backend-config=python,stub-timeout-seconds=120"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic"># Verbose logging (Level 3) is great for debugging but very "noisy."</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic"># In production, change --log-verbose to 0 or 1.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--log-info=true"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--log-warning=true"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--log-error=true"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--log-verbose=3"</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">ports</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">containerPort</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">containerPort</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8001</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">containerPort</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8002</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">resources</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">limits</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token comment" style="color:#999988;font-style:italic"># Triton requires at least one GPU to load the TensorRT-LLM backend.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token key atrule" style="color:#00a4db">nvidia.com/gpu</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">volumeMounts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic"># Mount the SAME PVC that the Provisioner used to access the built engine files.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">storage</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">              </span><span class="token key atrule" style="color:#00a4db">mountPath</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /models</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">storage</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">persistentVolumeClaim</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">claimName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">repository</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">pvc</span><br></div></code></pre></div></div>
<p>Apply the Triton deployment manifest and verify Triton server is running:</p>
<div class="theme-admonition theme-admonition-warning admonition_xJq3 alert alert--warning"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 16 16"><path fill-rule="evenodd" d="M8.893 1.5c-.183-.31-.52-.5-.887-.5s-.703.19-.886.5L.138 13.499a.98.98 0 0 0 0 1.001c.193.31.53.501.886.501h13.964c.367 0 .704-.19.877-.5a1.03 1.03 0 0 0 .01-1.002L8.893 1.5zm.133 11.497H6.987v-2.003h2.039v2.003zm0-3.004H6.987V5.987h2.039v4.006z"></path></svg></span>GPU RESOURCE CONFLICT</div><div class="admonitionContent_BuS1"><p>The provisioner pod holds the GPU. You must delete it before deploying the Triton server, otherwise the Triton pod will stay in Pending state due to insufficient GPU resources.</p></div></div>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Stop Provisioner POD</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl delete pod triton-provisioner -n triton-inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># wait until provisioner pod is stopped.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get pods -n triton-inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Deploy Triton Server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f triton-deployment.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># verify triton server is running.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get pods -n triton-inference -w</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Optionally check the Triton Server logs to ensure it starts correctly</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl logs -f deployment/triton-server -n triton-inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Replace "triton-server-5cb57f9bf-fjk6x" with the pod name from "kubectl get pods -n triton-inference"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl logs triton-server-5cb57f9bf-fjk6x -n triton-inference --previous</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="validating-the-tensorrt-llm-inference">Validating the TensorRT-LLM inference<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#validating-the-tensorrt-llm-inference" class="hash-link" aria-label="Direct link to Validating the TensorRT-LLM inference" title="Direct link to Validating the TensorRT-LLM inference" translate="no">​</a></h3>
<p>To validate the setup end-to-end, send a request to Triton and verify that TensorRT-LLM returns a valid response.</p>
<ol>
<li class="">
<p>Expose the Triton service for testing: you'll use port-forwarding. In a separate terminal window (or a new background process), run:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl port-forward -n triton-inference deploy/triton-server 8000:8000</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Run an inference request</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># Send a query to Triton from your client PowerShell</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$ModelName = "ensemble"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$Uri = "http://localhost:8000/v2/models/$ModelName/generate"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$Payload = @{</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  text_input = "What is Azure?"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  max_tokens = 64</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  bad_words  = ""</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  stop_words = ""</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$response = Invoke-RestMethod -Uri $Uri -Method Post -Body ($Payload | ConvertTo-Json -Compress) -ContentType "application/json"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$response</span><br></div></code></pre></div></div>
</li>
</ol>
<p>Example output:</p>
<div class="language-output codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-output codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">model_name     : ensemble</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">model_version  : 1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">sequence_end   : False</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">sequence_id    : 0</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">sequence_start : False</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">text_output    : What is Azure?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                 Azure is Microsoft's cloud computing platform that enables businesses to build, deploy, and manage</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                 applications and services through a global network of data centers. It offers a wide range of cloud</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                 services including Infrastructure as a Service (IaaS), Platform as a Service (PaaS), and Software as a</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                 Service (SaaS). Azure</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cleanup">Cleanup<a href="https://blog.aks.azure.com/2026/04/09/ai-inference-on-aks-arc-part-5#cleanup" class="hash-link" aria-label="Direct link to Cleanup" title="Direct link to Cleanup" translate="no">​</a></h3>
<p>To free resources, delete the triton-inference namespace and all its contents:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl delete namespace triton-inference</span><br></div></code></pre></div></div>
<p>If you installed the GPU Operator specifically for this test, you can also uninstall it via Helm to release cluster resources.</p>]]></content:encoded>
            <category>AKS Arc</category>
            <category>AI</category>
            <category>AI Inference</category>
        </item>
        <item>
            <title><![CDATA[Azure Container Storage v2.1.0: Now GA with Elastic SAN]]></title>
            <link>https://blog.aks.azure.com/2026/04/08/acstor-v2.1-ga</link>
            <guid>https://blog.aks.azure.com/2026/04/08/acstor-v2.1-ga</guid>
            <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Explore what’s new in Azure Container Storage v2.1.0 with Elastic SAN integration, modular on-demand installation, and enhanced node selector support.]]></description>
            <content:encoded><![CDATA[<p>Stateful workloads on Kubernetes continue to demand not only faster performance but also larger scale and more streamlined operational simplicity. <a href="https://learn.microsoft.com/azure/storage/container-storage/container-storage-introduction" target="_blank" rel="noopener noreferrer" class="">Azure Container Storage v2.1.0</a> is now generally available with three headline improvements:</p>
<p><strong>Elastic SAN (ESAN) integration</strong>: consolidate hundreds of persistent volumes under a single managed ESAN, bypassing VM disk-attachment limits</p>
<p><strong>Modular on-demand installation</strong>: deploy only the components your chosen storage type requires, reducing install time and cluster footprint</p>
<p><strong>Node selector support</strong>: control where Azure Container Storage components run so you can optimize resource usage across node pools</p>
<p><img decoding="async" loading="lazy" alt="Azure Container Storage v2.1.0 architecture overview" src="https://blog.aks.azure.com/assets/images/acstorv2-architecture-35d291af0227467faa9ccb1411bee1b4.png" width="3060" height="2193" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="why-elastic-san-for-kubernetes-storage">Why Elastic SAN for Kubernetes storage<a href="https://blog.aks.azure.com/2026/04/08/acstor-v2.1-ga#why-elastic-san-for-kubernetes-storage" class="hash-link" aria-label="Direct link to Why Elastic SAN for Kubernetes storage" title="Direct link to Why Elastic SAN for Kubernetes storage" translate="no">​</a></h2>
<p><a href="https://learn.microsoft.com/azure/storage/elastic-san/" target="_blank" rel="noopener noreferrer" class="">Elastic SAN</a> is a managed, shared block storage service that provides a central pool of capacity and performance including IOPS and throughput. From this pool, you create multiple volumes and attach them to many compute resources.</p>
<p>Below are example ESAN “base capacity” sizes and the ESAN-level provisioned performance you get from that base size (because ESAN scales SAN IOPS + throughput linearly with base TiB). Specifically, each 1 TiB of base capacity adds 5,000 IOPS and 200 MB/s throughput; additional/capacity-only TiB does not increase IOPS or throughput.</p>
<table><thead><tr><th>Base capacity (TiB)</th><th>ESAN provisioned IOPS</th><th>ESAN provisioned throughput</th></tr></thead><tbody><tr><td>1</td><td>5,000</td><td>200 MB/s</td></tr><tr><td>2</td><td>10,000</td><td>400 MB/s</td></tr><tr><td>5</td><td>25,000</td><td>1,000 MB/s</td></tr><tr><td>50</td><td>250,000</td><td>10,000 MB/s</td></tr><tr><td>200</td><td>1,000,000</td><td>40,000 MB/s</td></tr></tbody></table>
<p>Here are the key advantages we see customers looking for:</p>
<p><strong>Scalability beyond VM disk limits</strong>: ESAN uses iSCSI over TCP/IP for volume connectivity, which bypasses traditional VM disk attachment constraints (for example, limits such as 64 disks per VM). On AKS Linux nodes, iSCSI sessions aren't constrained by the same disk-attachment limits, so you can attach significantly more PVs per node. This is a key differentiator for high-volume PV scenarios.</p>
<p><strong>Cost efficiency through storage consolidation</strong>: Elastic SAN provisions capacity at the TiB level, enabling you to consolidate hundreds (or thousands) of smaller GiB-scale PVs under a single SAN. This reduces overprovisioning and lowers management overhead. Use the <a href="https://azure.microsoft.com/pricing/calculator/?msockid=386fb3dcdad3644830f9a576dbe16569" target="_blank" rel="noopener noreferrer" class="">ESAN pricing calculator</a> for a cost estimation of your storage requirements.</p>
<p><strong>Fast attach/detach for burst scale scenarios</strong>: iSCSI session establishment is fast and avoids disk-centric attachment throttling patterns, which matters during burst scale when dozens to hundreds of pods come and go quickly.</p>
<p><strong>Simplified management with an on-ramp to open-source flexibility</strong>: Azure Container Storage v2 is designed so you install only the components needed for the selected storage type. The SAN CSI Driver is also planned to be open sourced to give customers flexibility in how they orchestrate storage.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="which-workloads-benefit-most">Which workloads benefit most<a href="https://blog.aks.azure.com/2026/04/08/acstor-v2.1-ga#which-workloads-benefit-most" class="hash-link" aria-label="Direct link to Which workloads benefit most" title="Direct link to Which workloads benefit most" translate="no">​</a></h2>
<p>Elastic SAN with Azure Container Storage is a strong fit for stateful workloads that combine many volumes with elastic provisioning needs. Here are a few workload patterns we’ve seen align well:</p>
<p><strong>Database as a service (DBaaS) and multi-tenant database platforms</strong>: Many customers run containerized databases such as PostgreSQL and beyond on AKS, either as DBaaS offerings or shared, multi-tenant platforms. In these scenarios, storage choices have a direct impact on scalability, performance, durability, and day‑to‑day operability.
Common requirements include:</p>
<ul>
<li class="">Scaling database instances efficiently while keeping infrastructure overhead under control</li>
<li class="">Fast provisioning and attachment of many PVs, especially in multi-tenant or bursty environments</li>
<li class="">Avoiding monolithic installs when only a single storage backend is needed</li>
</ul>
<p><strong>Large-scale developer environments and per-user volumes</strong>: Another pattern is per-user or per-project environments where each user instance maps to a PV. One example described in planning is a large environment with many pods across multiple clusters, where daily burst creation can stress volume provisioning and attachment if implemented as thousands of independent disks. Elastic SAN enables consolidating volumes under a SAN and accelerating provisioning/attachment flows compared to disk-per-PV approaches.</p>
<p>For using ESAN with Azure Container Storage, see the <a href="https://learn.microsoft.com/azure/storage/container-storage/use-container-storage-with-elastic-san?tabs=cli" target="_blank" rel="noopener noreferrer" class="">configure Elastic SAN documentation</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="on-demand-installation-model">On-demand installation model<a href="https://blog.aks.azure.com/2026/04/08/acstor-v2.1-ga#on-demand-installation-model" class="hash-link" aria-label="Direct link to On-demand installation model" title="Direct link to On-demand installation model" translate="no">​</a></h2>
<p>Azure Container Storage v2.1.0 supports a lightweight, modular installation model. You can enable Azure Container Storage first, then choose a backing storage type when you need it, deploying only the components required for that storage type. You can also add, update, or remove storage components after the initial setup. It supports two installation flows:</p>
<p><strong>Flow A</strong>: Enable Azure Container Storage and choose storage type upfront
If you know which storage type (local NVMe/Elastic SAN) you want to use upfront, enable Azure Container Storage with your preferred storage type so the relevant driver and a default StorageClass can be configured.</p>
<p><strong>Flow B</strong>: Enable Azure Container Storage first, then add storage type later
If you want to keep the initial install lightweight, you can enable Azure Container Storage first and then create a StorageClass of your preferred storage type which triggers installation of the respective CSI driver when you need it.</p>
<p>For detailed steps, see the <a href="https://learn.microsoft.com/azure/storage/container-storage/install-container-storage-aks?pivots=azurecli" target="_blank" rel="noopener noreferrer" class="">on-demand installation documentation</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="node-selector-support-for-component-placement">Node selector support for component placement<a href="https://blog.aks.azure.com/2026/04/08/acstor-v2.1-ga#node-selector-support-for-component-placement" class="hash-link" aria-label="Direct link to Node selector support for component placement" title="Direct link to Node selector support for component placement" translate="no">​</a></h2>
<p>In production clusters, running every component on every node usually isn’t the goal. You might have:</p>
<ul>
<li class="">a dedicated node pool optimized for storage-heavy workloads</li>
<li class="">GPU pools where you want to minimize background DaemonSet footprint</li>
<li class="">mixed compute/storage topologies where placement matters</li>
</ul>
<p>Azure Container Storage v2.1.0 adds node selector support so you can control where Azure Container Storage components run, helping optimize performance and resource usage. The local CSI drivers can be deployed only in selected node pools by configuring node affinity based on the agentpool label. The following example shows a StorageClass configuration:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> storage.k8s.io/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> StorageClass</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> local</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">nvme</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">annotations</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">storageoperator.acstor.io/nodeAffinity</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">|</span><span class="token scalar string" style="color:#e3116c"></span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">      requiredDuringSchedulingIgnoredDuringExecution:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">        nodeSelectorTerms:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">        - matchExpressions:</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">          - key: kubernetes.azure.com/agentpool</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">            operator: In</span><br></div><div class="token-line" style="color:#393A34"><span class="token scalar string" style="color:#e3116c">            values: [mygpu,mygpu2]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">provisioner</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> localdisk.csi.acstor.io</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">reclaimPolicy</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Delete</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">volumeBindingMode</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> WaitForFirstConsumer</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">allowVolumeExpansion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token boolean important" style="color:#36acaa">true</span><br></div></code></pre></div></div>
<p>For more details on configuring component placement, see the <a href="https://learn.microsoft.com/en-us/azure/storage/container-storage/manage-local-container-storage-interface-driver-placement" target="_blank" rel="noopener noreferrer" class="">node selector documentation</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="practical-guidance">Practical guidance<a href="https://blog.aks.azure.com/2026/04/08/acstor-v2.1-ga#practical-guidance" class="hash-link" aria-label="Direct link to Practical guidance" title="Direct link to Practical guidance" translate="no">​</a></h2>
<ul>
<li class="">
<p>Validate scale targets in your environment: very high PV density scenarios should be tested with your workload’s I/O patterns and cluster topology.</p>
</li>
<li class="">
<p>Use node selectors to align component placement with your node pool strategy (for example, dedicated storage pools or mixed pools).</p>
</li>
<li class="">
<p>Consolidate where it makes sense: Elastic SAN is most compelling when you have many PVs and want centralized capacity/performance management.</p>
</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="getting-started-with-azure-container-storage-v210">Getting started with Azure Container Storage v2.1.0<a href="https://blog.aks.azure.com/2026/04/08/acstor-v2.1-ga#getting-started-with-azure-container-storage-v210" class="hash-link" aria-label="Direct link to Getting started with Azure Container Storage v2.1.0" title="Direct link to Getting started with Azure Container Storage v2.1.0" translate="no">​</a></h2>
<p>Ready to run your stateful workloads using Azure Container Storage v2.1.0? Here are your next steps:</p>
<ul>
<li class="">
<p>New to Azure Container Storage? Start with our <a href="https://learn.microsoft.com/azure/storage/container-storage/container-storage-introduction" target="_blank" rel="noopener noreferrer" class="">comprehensive documentation</a></p>
</li>
<li class="">
<p>Want to use Azure Elastic SAN as storage type? Follow <a href="https://learn.microsoft.com/azure/storage/container-storage/use-container-storage-with-elastic-san?tabs=cli" target="_blank" rel="noopener noreferrer" class="">the step-by-step guide</a></p>
</li>
<li class="">
<p>Want to use local NVMe as storage type? Follow <a href="https://learn.microsoft.com/azure/storage/container-storage/use-container-storage-with-local-disk" target="_blank" rel="noopener noreferrer" class="">the step-by-step guide</a></p>
</li>
<li class="">
<p>Deploying specific workloads? Check out our updated deployment guide for <a href="https://learn.microsoft.com/azure/aks/postgresql-ha-overview" target="_blank" rel="noopener noreferrer" class="">PostgreSQL</a></p>
</li>
<li class="">
<p>Want the open-source local CSI driver? Visit our <a href="https://github.com/Azure/local-csi-driver" target="_blank" rel="noopener noreferrer" class="">GitHub repository</a> for installation instructions</p>
</li>
<li class="">
<p>Have questions or feedback? Reach out to our team at <a href="mailto:AskContainerStorage@microsoft.com" target="_blank" rel="noopener noreferrer" class="">AskContainerStorage@microsoft.com</a></p>
</li>
</ul>]]></content:encoded>
            <category>General</category>
            <category>Developer</category>
            <category>Storage</category>
            <category>Databases</category>
        </item>
        <item>
            <title><![CDATA[Turn your agents into AKS experts: Agent Skills for AKS]]></title>
            <link>https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks</link>
            <guid>https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks</guid>
            <pubDate>Wed, 08 Apr 2026 00:00:00 GMT</pubDate>
            <description><![CDATA[Announcing agent skills for AKS, easily pluggable into any agent to enhance them with built-in AKS expertise, safer workflows, and reduced developer overhead.]]></description>
            <content:encoded><![CDATA[<p><strong>Agent skills for Azure Kubernetes Service (AKS)</strong> bring production-grade AKS guidance, troubleshooting checklists, and guardrails directly into any compatible AI agent. The first set of skills are now available through the <strong>GitHub Copilot for Azure</strong> extension, with support for VS Code, Visual Studio, Copilot CLI, and Claude.</p>
<p>While AI agents already carry a good baseline of Kubernetes and AKS knowledge, that knowledge is only as current as their training data and varies across models. Skills enhance agents with prescriptive, up-to-date guidance on the tools and processes our AKS engineers use today to make the right AKS decisions across cluster creation, operations, and issue resolution.</p>
<p><img decoding="async" loading="lazy" alt="Diagram showing artistic rendition of the components" src="https://blog.aks.azure.com/assets/images/hero-image-1b853c7a122263d91cc356d67de04b18.png" width="1536" height="1024" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="what-are-agent-skills">What are agent skills?<a href="https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks#what-are-agent-skills" class="hash-link" aria-label="Direct link to What are agent skills?" title="Direct link to What are agent skills?" translate="no">​</a></h2>
<p><a href="https://learn.microsoft.com/agent-framework/agents/skills" target="_blank" rel="noopener noreferrer" class="">Agent skills</a> are an open standard pioneered by Anthropic for enhancing AI agents with domain-specific expertise in a token-efficient way. You install a skill once, and any compatible agent (such as GitHub Copilot, Claude, Gemini, or others) picks it up automatically for relevant prompts. They provide essential expertise to your agents while staying context efficient, as each skill loads only when your prompt is relevant to the skill's content:</p>
<ul>
<li class="">If you're not asking about AKS, the skill <strong>stays out of the way</strong> and doesn't add to your token usage.</li>
<li class="">When you do ask an AKS-related question, the skill <strong>activates automatically</strong> and brings in the right guidance, commands, and context.</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="available-skills">Available skills<a href="https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks#available-skills" class="hash-link" aria-label="Direct link to Available skills" title="Direct link to Available skills" translate="no">​</a></h2>
<p>The first release includes the following skills:</p>
<ol>
<li class="">A high-level <a href="https://github.com/microsoft/GitHub-Copilot-for-Azure/blob/main/plugins/azure-skills/skills/azure-kubernetes/SKILL.md" target="_blank" rel="noopener noreferrer" class=""><strong>AKS best practices</strong> skill</a></li>
<li class="">Sub-skills for <a href="https://github.com/microsoft/GitHub-Copilot-for-Azure/blob/main/plugins/azure-skills/skills/azure-diagnostics/troubleshooting/aks/aks-troubleshooting.md" target="_blank" rel="noopener noreferrer" class=""><strong>AKS troubleshooting</strong></a></li>
</ol>
<p>The <strong>AKS best practices</strong> skill guides agents through cluster configuration recommendations across networking, upgrade strategy, security, reliability, scale, and more. The guidance reflects what the AKS engineering team recommends for production clusters for optimal performance and uptime, along with specific defaults and critical decisions that apply to AKS. After installing the skill, try the following commands to invoke the skill and receive targeted recommendations for your AKS cluster:</p>
<ul>
<li class="">"What are the best practice recommendations for a highly reliable and performant AKS cluster?"</li>
<li class="">"Help me determine the Day-0 decisions I need to make when creating a new AKS cluster."</li>
<li class="">"What networking setup is best for my AKS cluster?"</li>
</ul>
<p><strong>AKS troubleshooting</strong> covers the most common incident scenarios: node health failures and networking issues. It includes the exact CLI commands and diagnostic sequences that AKS engineers use internally when working on customer incidents. Commands are permission-gated: the skill only suggests and executes commands that your current credentials allow, so there's no risk of unintentional changes. Here's what a typical troubleshooting session looks like in VS Code with the skill active:</p>
<div class="language-text codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-text codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">User: What are the best practices for deploying a secure, reliable, and cost-efficient AKS cluster?</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Copilot: I’ll pull AKS-specific best-practice guidance first so I can give you a concise, production-ready checklist for security, reliability, and cost.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Action: Read skill `azure-kubernetes`</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">For AKS, the best results come from making strong Day-0 choices, then layering autoscaling, policy, and operational guardrails.</span><br></div></code></pre></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="how-to-get-started">How to get started<a href="https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks#how-to-get-started" class="hash-link" aria-label="Direct link to How to get started" title="Direct link to How to get started" translate="no">​</a></h2>
<p>You have two options: install individual skills directly, or get the full bundle through the GitHub Copilot for Azure plugin.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="option-1-install-through-github-copilot-for-azure-plugin">Option 1: Install through GitHub Copilot for Azure plugin<a href="https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks#option-1-install-through-github-copilot-for-azure-plugin" class="hash-link" aria-label="Direct link to Option 1: Install through GitHub Copilot for Azure plugin" title="Direct link to Option 1: Install through GitHub Copilot for Azure plugin" translate="no">​</a></h3>
<p>The GitHub Copilot for Azure plugin is available through VS Code, Claude, and Copilot CLI and bundles AKS skills alongside 20+ skills across cost optimization, other Azure resources, and deployment workflows. Learn more about <a href="https://learn.microsoft.com/azure/developer/github-copilot-azure/introduction?tabs=vscode" target="_blank" rel="noopener noreferrer" class="">GitHub Copilot for Azure</a>, and get the plugin:</p>
<p><strong>Install the Azure extension for VS Code:</strong></p>
<ol>
<li class="">Open VS Code and go to the Extensions marketplace (<code>Ctrl+Shift+X</code> / <code>Cmd+Shift+X</code>).</li>
<li class="">Search for <strong>GitHub Copilot for Azure</strong> and select <strong>Install</strong>. (<a href="https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azure-github-copilot" target="_blank" rel="noopener noreferrer" class="">Direct link to the marketplace</a>)</li>
<li class="">Open GitHub Copilot Chat in VS Code (the chat icon in the sidebar or <code>Ctrl+Alt+I</code> / <code>Cmd+Alt+I</code>).</li>
<li class="">Run a prompt that references AKS, such as <em>"What's the recommended upgrade strategy for my AKS cluster?"</em> or <em>"What are the best practices for AKS clusters?"</em>. The AKS skill will install and load automatically.</li>
</ol>
<blockquote>
<p><strong>Note</strong>: If skills don't activate after installing the extension, open the VS Code Command Palette (<code>Ctrl+Shift+P</code> / <code>Cmd+Shift+P</code>) and run <strong>GitHub Copilot for Azure: Refresh Skills</strong>. Alternatively, running any AKS-related prompt will trigger the extension to initialize and load available skills.</p>
</blockquote>
<p><strong>Install the Azure plugin to Claude or Copilot CLI:</strong></p>
<ol>
<li class="">Add the marketplace with <code>/plugin marketplace add microsoft/azure-skills</code></li>
<li class="">Install the plugin with <code>/plugin install azure@azure-skills</code></li>
<li class="">Update the plugin with <code>/plugin update azure@azure-skills</code></li>
<li class="">Verify that the azure-kubernetes skill has successfully installed with <code>/skills</code></li>
<li class="">Run a prompt that references AKS, which will invoke the relevant skill.</li>
</ol>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="option-2-install-aks-skills-directly">Option 2: Install AKS skills directly<a href="https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks#option-2-install-aks-skills-directly" class="hash-link" aria-label="Direct link to Option 2: Install AKS skills directly" title="Direct link to Option 2: Install AKS skills directly" translate="no">​</a></h3>
<ol>
<li class="">To install specific skills directly, use <code>npx skills add https://github.com/microsoft/github-copilot-for-azure --skill [skill name]</code>:<!-- -->
<ol>
<li class=""><code>npx skills add https://github.com/microsoft/github-copilot-for-azure --skill azure-kubernetes</code></li>
<li class=""><code>npx skills add https://github.com/microsoft/github-copilot-for-azure --skill azure-diagnostics</code></li>
</ol>
</li>
<li class="">Alternatively, download the skill file directly from the repo from the reference links below and move it to your skills directory (e.g. <code>~/.copilot/skills</code> or <code>~/.claude/skills</code>).</li>
<li class="">Run any AKS-related prompt such as <em>"Review my AKS cluster for best practices"</em>, and the skill will activate automatically.</li>
</ol>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai-powered-capabilities-for-aks">AI-powered capabilities for AKS<a href="https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks#ai-powered-capabilities-for-aks" class="hash-link" aria-label="Direct link to AI-powered capabilities for AKS" title="Direct link to AI-powered capabilities for AKS" translate="no">​</a></h2>
<p>AKS now has several AI-powered experiences: skills, the <a class="" href="https://blog.aks.azure.com/2025/08/06/aks-mcp-server">AKS MCP server</a>, and the <a href="https://aka.ms/aks/agentic-cli" target="_blank" rel="noopener noreferrer" class="">agentic CLI for AKS</a>. Understanding the differences helps you choose the right combination for your workflow.</p>
<p>The <strong><a class="" href="https://blog.aks.azure.com/2025/08/06/aks-mcp-server">AKS MCP server</a></strong> is a tool layer that pairs directly with skills. Where skills tell the agent <em>what</em> to do, the MCP server gives it the ability to <em>act</em>: securely access your cluster details, run scoped diagnostic commands, and interact with Kubernetes and Azure APIs. Without the AKS MCP server, agents fall back to running direct CLI commands, which lack the structured, permission-aware interface the MCP server provides.</p>
<p>Skills enhance the base knowledge of any agent, including the <strong><a href="https://aka.ms/aks/agentic-cli" target="_blank" rel="noopener noreferrer" class="">agentic CLI for AKS</a></strong> (<code>az aks agent</code>). We're working on built-in support for all AKS skills in the agentic CLI, making it the right choice when you want a purpose-built terminal experience without assembling the individual pieces across tooling and AKS expertise yourself.</p>
<p>The three layers are designed to complement each other:</p>
<table><thead><tr><th style="text-align:left">Capability</th><th style="text-align:left">Role</th><th style="text-align:left">Requires cluster</th><th style="text-align:left">Best for</th></tr></thead><tbody><tr><td style="text-align:left">AKS skills</td><td style="text-align:left">Knowledge</td><td style="text-align:left">No</td><td style="text-align:left">Wide range of scenarios from cluster configuration to troubleshooting/operations</td></tr><tr><td style="text-align:left">AKS MCP server</td><td style="text-align:left">Tools</td><td style="text-align:left">Yes</td><td style="text-align:left">Live diagnostics, cluster state, Azure and Kubernetes API access</td></tr><tr><td style="text-align:left">Agentic CLI for AKS</td><td style="text-align:left">End-to-end experience</td><td style="text-align:left">Yes</td><td style="text-align:left">AI-powered cluster operations and workflows</td></tr></tbody></table>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="creating-your-own-skills">Creating your own skills<a href="https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks#creating-your-own-skills" class="hash-link" aria-label="Direct link to Creating your own skills" title="Direct link to Creating your own skills" translate="no">​</a></h2>
<p>Your team can get the most value from skills by combining AKS-authored skills with skills specific to your organization. If you have existing AKS workflows tailored to your specific workloads, you can encode them into reusable skills using the same format and run them together.</p>
<p>Good candidates for internal skills include:</p>
<ul>
<li class="">Governance guardrails (required tags, approved regions, allowed SKUs, naming policies)</li>
<li class="">Security controls (network isolation checks, image and registry policies, workload identity requirements)</li>
<li class="">Platform standards (ingress patterns, DNS conventions, observability defaults, escalation paths)</li>
<li class="">Troubleshooting workbooks (common issues that your team faces, specifics on how your clusters are configured, monitoring setup)</li>
</ul>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="conclusion">Conclusion<a href="https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks#conclusion" class="hash-link" aria-label="Direct link to Conclusion" title="Direct link to Conclusion" translate="no">​</a></h2>
<p>AKS skills give your agents a baseline of production AKS knowledge using the same guidance, commands, and diagnostic approaches that AKS engineers use. The first release covers best practices and troubleshooting, and we're planning to cover more scenarios based on customer feedback. If you run into issues or have scenarios you'd like to see covered, submit your feedback in the <a href="https://github.com/Azure/AKS/issues/5705" target="_blank" rel="noopener noreferrer" class="">AKS agent skills forum on GitHub</a>.</p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="resources">Resources<a href="https://blog.aks.azure.com/2026/04/08/agent-skills-for-aks#resources" class="hash-link" aria-label="Direct link to Resources" title="Direct link to Resources" translate="no">​</a></h2>
<ul>
<li class="">Link to the <a href="https://github.com/microsoft/GitHub-Copilot-for-Azure/blob/main/plugins/azure-skills/skills/azure-kubernetes/SKILL.md" target="_blank" rel="noopener noreferrer" class="">azure-kubernetes skill</a></li>
<li class="">Link to the <a href="https://github.com/microsoft/GitHub-Copilot-for-Azure/blob/main/plugins/azure-skills/skills/azure-diagnostics/SKILL.md" target="_blank" rel="noopener noreferrer" class="">azure-diagnostics skill</a></li>
<li class="">Find all skills in the <a href="https://github.com/microsoft/skills/tree/main/.github/plugins/azure-skills" target="_blank" rel="noopener noreferrer" class="">Microsoft/skills repo</a></li>
</ul>]]></content:encoded>
            <category>Agent</category>
            <category>Skills</category>
            <category>MCP</category>
            <category>Developer</category>
        </item>
        <item>
            <title><![CDATA[AI Inference on AKS enabled by Azure Arc: Predictive AI using Triton and ResNet-50]]></title>
            <link>https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4</link>
            <guid>https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4</guid>
            <pubDate>Tue, 07 Apr 2026 00:01:03 GMT</pubDate>
            <description><![CDATA[Run predictive AI at the edge by deploying Triton with the ONNX backend on AKS enabled by Azure Arc for local ResNet 50 GPU inference without cloud dependency.]]></description>
            <content:encoded><![CDATA[<p>In this post, you'll deploy NVIDIA Triton Inference Server on your Azure Kubernetes Service (AKS) enabled by Azure Arc cluster to serve a ResNet-50 image classification model in ONNX format. By the end, you'll have a working predictive AI inference pipeline running on your on-premises GPU hardware.</p>
<p><img decoding="async" loading="lazy" alt="Deploying predictive AI inference with open-source inference servers on AKS enabled by Azure Arc" src="https://blog.aks.azure.com/assets/images/hero-image-4741bd803b20b3d2c686f1d095dc70ce.png" width="750" height="508" class="img_ev3q"></p>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="introduction">Introduction<a href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4#introduction" class="hash-link" aria-label="Direct link to Introduction" title="Direct link to Introduction" translate="no">​</a></h2>
<p>The previous post covered deploying LLM servers for text generation. This post shifts to predictive AI, specifically computer vision. You'll use NVIDIA Triton Inference Server with the ONNX runtime backend to serve ResNet-50, a classic "hello world" for image classification. Triton is an enterprise-grade, multi-framework model server, and deploying it here gives you a starting pattern for serving any ONNX model on your AKS enabled by Azure Arc managed cluster.</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>PREREQUISITES AND SCOPE</div><div class="admonitionContent_BuS1"><p>Before you begin, ensure the <a class="" href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-2">Part 2 prerequisites</a> are met, including a GPU node configured for <strong>nvidia.com/gpu</strong>. Cluster nodes need <strong>internet access</strong> to download models. <strong>Expect a delay</strong> during initial deployment while the pod downloads and caches model files.</p><p>This tutorial is designed for experimentation and learning. The configurations shown are not production-ready and should not be deployed to production environments without additional security, reliability, performance hardening, and following standard practices.</p></div></div>
<h2 class="anchor anchorTargetStickyNavbar_Vzrq" id="ai-inference-with-triton-onnx">AI Inference with Triton (ONNX)<a href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4#ai-inference-with-triton-onnx" class="hash-link" aria-label="Direct link to AI Inference with Triton (ONNX)" title="Direct link to AI Inference with Triton (ONNX)" translate="no">​</a></h2>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="preparing-storage-for-the-model-repository">Preparing storage for the model repository<a href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4#preparing-storage-for-the-model-repository" class="hash-link" aria-label="Direct link to Preparing storage for the model repository" title="Direct link to Preparing storage for the model repository" translate="no">​</a></h3>
<p>In addition to the prerequisites, you'll need persistent <strong>storage</strong> for model files. First, create a <strong>triton-inference</strong> namespace and a <strong>PersistentVolumeClaim</strong> (PVC) for the model repository.</p>
<p>Save the following YAML as <code>triton-pvc.yaml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token comment" style="color:#999988;font-style:italic"># 1. THE NAMESPACE</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Creates an isolated logical boundary for your Triton resources.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># All subsequent resources must reference this namespace to communicate.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Namespace</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token punctuation" style="color:#393A34">---</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># 2. THE STORAGE (PVC)</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># Requests a persistent disk from the cluster to store your model weights.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token comment" style="color:#999988;font-style:italic"># This ensures that if the Pod restarts, your downloaded models remain intact.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> PersistentVolumeClaim</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">repository</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">pvc</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference </span><span class="token comment" style="color:#999988;font-style:italic"># Ensures the storage is available within your namespace</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># ReadWriteOnce (RWO) allows the volume to be mounted as read-write by a single node.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">accessModes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> ReadWriteOnce</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Omit storageClassName to use the cluster's default StorageClass. </span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">resources</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">requests</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Allocates 10GB of space. Ensure your underlying disk provider supports this size.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">storage</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> 10Gi</span><br></div></code></pre></div></div>
<p>Apply the manifest and verify the PVC status is <strong>Bound</strong> (storage provisioned):</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f triton-pvc.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get pvc -n triton-inference</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploy-a-helper-pod-to-download-the-model">Deploy a helper pod to download the model<a href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4#deploy-a-helper-pod-to-download-the-model" class="hash-link" aria-label="Direct link to Deploy a helper pod to download the model" title="Direct link to Deploy a helper pod to download the model" translate="no">​</a></h3>
<div class="theme-admonition theme-admonition-tip admonition_xJq3 alert alert--success"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 12 16"><path fill-rule="evenodd" d="M6.5 0C3.48 0 1 2.19 1 5c0 .92.55 2.25 1 3 1.34 2.25 1.78 2.78 2 4v1h5v-1c.22-1.22.66-1.75 2-4 .45-.75 1-2.08 1-3 0-2.81-2.48-5-5.5-5zm3.64 7.48c-.25.44-.47.8-.67 1.11-.86 1.41-1.25 2.06-1.45 3.23-.02.05-.02.11-.02.17H5c0-.06 0-.13-.02-.17-.2-1.17-.59-1.83-1.45-3.23-.2-.31-.42-.67-.67-1.11C2.44 6.78 2 5.65 2 5c0-2.2 2.02-4 4.5-4 1.22 0 2.36.42 3.22 1.19C10.55 2.94 11 3.94 11 5c0 .66-.44 1.78-.86 2.48zM4 14h5c-.23 1.14-1.3 2-2.5 2s-2.27-.86-2.5-2z"></path></svg></span>tip</div><div class="admonitionContent_BuS1"><p>This tutorial uses a helper pod for interactive model downloading, which makes it easy to troubleshoot and retry. For automated workflows, consider using a Kubernetes <a href="https://kubernetes.io/docs/concepts/workloads/controllers/job/" target="_blank" rel="noopener noreferrer" class="">Job</a> instead, which handles retries and completion tracking natively.</p></div></div>
<p>Next, you'll spin up a temporary pod to download the model into storage. Save the following YAML as <code>model-download-pod.yaml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Pod</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Name of the helper pod used to stage or download models</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">downloader</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Namespace where Triton and related components are deployed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">labels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token comment" style="color:#999988;font-style:italic"># Label to identify this pod for management or cleanup</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">downloader</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">storage</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Persistent volume claim backing the Triton model repository</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Models downloaded by this pod will be visible to Triton</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">persistentVolumeClaim</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">claimName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">repository</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">pvc</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">containers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> helper</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Lightweight Python image used as a utility container</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> python</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">3.11</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">slim</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Keep the container running so you can exec into it</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># and download or manage model files interactively</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">command</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"/bin/sh"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"-c"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"tail -f /dev/null"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">volumeMounts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">storage</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># Mount path where models will be placed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># This should match Triton's model repository path</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">mountPath</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /models</span><br></div></code></pre></div></div>
<p>Apply the manifest and wait for the model-downloader pod to reach Running (you can stop watching with Ctrl+C once it’s running).</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f model-download-pod.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get pods -n triton-inference -w</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="download-the-onnx-model-into-the-repository">Download the ONNX model into the repository<a href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4#download-the-onnx-model-into-the-repository" class="hash-link" aria-label="Direct link to Download the ONNX model into the repository" title="Direct link to Download the ONNX model into the repository" translate="no">​</a></h3>
<p>Now <strong>download the ResNet-50 ONNX model</strong> from Hugging&nbsp;Face:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl exec -n triton-inference model-downloader -- python3 -c "</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">import urllib.request;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">import os;</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">os.makedirs('/models/repository/resnet50/1', exist_ok=True);</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">urllib.request.urlretrieve(</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  'https://huggingface.co/onnxmodelzoo/resnet50-v2-7/resolve/main/resnet50-v2-7.onnx',</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  '/models/repository/resnet50/1/model.onnx'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">);</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">print('Download Complete')</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">"</span><br></div></code></pre></div></div>
<h4 class="anchor anchorTargetStickyNavbar_Vzrq" id="confirm-the-model-file-98-mb-exists">Confirm the model file (~98 MB) exists<a href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4#confirm-the-model-file-98-mb-exists" class="hash-link" aria-label="Direct link to Confirm the model file (~98 MB) exists" title="Direct link to Confirm the model file (~98 MB) exists" translate="no">​</a></h4>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl exec -n triton-inference model-downloader -- ls -lh /models/repository/resnet50/1/model.onnx</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="deploying-triton-inference-server-onnx">Deploying Triton inference server (ONNX)<a href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4#deploying-triton-inference-server-onnx" class="hash-link" aria-label="Direct link to Deploying Triton inference server (ONNX)" title="Direct link to Deploying Triton inference server (ONNX)" translate="no">​</a></h3>
<p>With the model in place, you'll deploy Triton. Save this as <code>triton-deployment.yaml</code>:</p>
<div class="language-yaml codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-yaml codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token key atrule" style="color:#00a4db">apiVersion</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> apps/v1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">kind</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> Deployment</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Name of the Triton Inference Server deployment</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Namespace where Triton and related resources are deployed</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">namespace</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">inference</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"></span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token comment" style="color:#999988;font-style:italic"># Number of Triton pods to run</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">replicas</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">selector</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">matchLabels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token comment" style="color:#999988;font-style:italic"># Label selector used by the Deployment to manage pods</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">  </span><span class="token key atrule" style="color:#00a4db">template</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">metadata</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">labels</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># Labels applied to the Triton pod</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">app</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">server</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    </span><span class="token key atrule" style="color:#00a4db">spec</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">containers</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># Official NVIDIA Triton Inference Server image</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">image</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> nvcr.io/nvidia/tritonserver</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain">24.08</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">py3</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># Start Triton with the model repository mounted at /models/repository</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># --strict-model-config=false allows Triton to infer model configs</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">args</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token punctuation" style="color:#393A34">[</span><span class="token string" style="color:#e3116c">"tritonserver"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--model-repository=/models/repository"</span><span class="token punctuation" style="color:#393A34">,</span><span class="token plain"> </span><span class="token string" style="color:#e3116c">"--strict-model-config=false"</span><span class="token punctuation" style="color:#393A34">]</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">ports</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># HTTP endpoint for inference and health checks</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">containerPort</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> http</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># gRPC endpoint for inference</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">containerPort</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8001</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> grpc</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># READINESS PROBE:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># Checks whether Triton is ready to accept inference requests.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># Triton exposes /v2/health/ready and returns HTTP 200 only after</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># the server is initialized and all models are loaded.</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">readinessProbe</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">httpGet</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">path</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /v2/health/ready</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">port</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">8000</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">initialDelaySeconds</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">60</span><span class="token plain"> </span><span class="token comment" style="color:#999988;font-style:italic"># Wait 60 seconds before the first probe</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">periodSeconds</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">10</span><span class="token plain">       </span><span class="token comment" style="color:#999988;font-style:italic"># Probe every 10 seconds</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">failureThreshold</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">6</span><span class="token plain">     </span><span class="token comment" style="color:#999988;font-style:italic"># Mark pod unready after 6 failures</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">resources</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">limits</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token comment" style="color:#999988;font-style:italic"># Request exclusive access to one NVIDIA GPU</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            </span><span class="token key atrule" style="color:#00a4db">nvidia.com/gpu</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> </span><span class="token number" style="color:#36acaa">1</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">volumeMounts</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">vol</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token comment" style="color:#999988;font-style:italic"># Mount the persistent volume containing the Triton model repository</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">mountPath</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> /models</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token key atrule" style="color:#00a4db">volumes</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">      </span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain"> </span><span class="token key atrule" style="color:#00a4db">name</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">vol</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token comment" style="color:#999988;font-style:italic"># Persistent volume claim backing the Triton model repository</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        </span><span class="token key atrule" style="color:#00a4db">persistentVolumeClaim</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">          </span><span class="token key atrule" style="color:#00a4db">claimName</span><span class="token punctuation" style="color:#393A34">:</span><span class="token plain"> triton</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">model</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">repository</span><span class="token punctuation" style="color:#393A34">-</span><span class="token plain">pvc</span><br></div></code></pre></div></div>
<p>Apply the Triton deployment manifest and verify Triton server is running</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl apply -f triton-deployment.yaml</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">kubectl get pods -n triton-inference -w   # watch Triton pod startup</span><br></div></code></pre></div></div>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="validating-the-resnet-model-inference">Validating the ResNet model inference<a href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4#validating-the-resnet-model-inference" class="hash-link" aria-label="Direct link to Validating the ResNet model inference" title="Direct link to Validating the ResNet model inference" translate="no">​</a></h3>
<p>Now you'll test it end-to-end. You'll send an image to Triton and confirm you get a valid prediction back from ResNet-50.</p>
<ol>
<li class="">
<p>Expose the Triton service for testing: Since there's no LoadBalancer service, you'll use port-forwarding. In a separate terminal window (or a new background process), run:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl port-forward -n triton-inference deploy/triton-server 8000:8000</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Prepare a test image: Obtain a sample image file (e.g., a JPEG picture of an animal, since ResNet-50 is a general image classifier). Save the image to a known path on your local machine (for example, <code>C:\temp\test-image.jpg</code>).</p>
</li>
<li class="">
<p>Install the Python dependencies the script needs (if you haven't already):</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">pip install numpy pillow requests</span><br></div></code></pre></div></div>
</li>
<li class="">
<p>Run an inference request using a script. Save the following script as <code>ImageClassificationSample.ps1</code>, then execute it to submit a request to Triton:</p>
<div class="theme-admonition theme-admonition-note admonition_xJq3 alert alert--secondary"><div class="admonitionHeading_Gvgb"><span class="admonitionIcon_Rf37"><svg viewBox="0 0 14 16"><path fill-rule="evenodd" d="M6.3 5.69a.942.942 0 0 1-.28-.7c0-.28.09-.52.28-.7.19-.18.42-.28.7-.28.28 0 .52.09.7.28.18.19.28.42.28.7 0 .28-.09.52-.28.7a1 1 0 0 1-.7.3c-.28 0-.52-.11-.7-.3zM8 7.99c-.02-.25-.11-.48-.31-.69-.2-.19-.42-.3-.69-.31H6c-.27.02-.48.13-.69.31-.2.2-.3.44-.31.69h1v3c.02.27.11.5.31.69.2.2.42.31.69.31h1c.27 0 .48-.11.69-.31.2-.19.3-.42.31-.69H8V7.98v.01zM7 2.3c-3.14 0-5.7 2.54-5.7 5.68 0 3.14 2.56 5.7 5.7 5.7s5.7-2.55 5.7-5.7c0-3.15-2.56-5.69-5.7-5.69v.01zM7 .98c3.86 0 7 3.14 7 7s-3.14 7-7 7-7-3.12-7-7 3.14-7 7-7z"></path></svg></span>note</div><div class="admonitionContent_BuS1"><p>ResNet‑50 predicts one of 1,000 ImageNet classes. This sample applies a demo‑only mapping that groups some animal‑related labels for simplicity. You can extend the sample to handle additional classes or custom mappings. This is provided for demonstration purposes only.</p></div></div>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain"># ImageClassificationSample.ps1 - sends an image to Triton Inference Server and prints the result.</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 1. Prompt for the local image path to classify:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$imagePath = Read-Host "Enter the full path to your local image (e.g., C:\pics\animal.jpg)"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$tritonUrl = "http://localhost:8000/v2/models/resnet50/infer"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 2. Define a Python script that will send the image to Triton:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$scriptPath = "$env:TEMP\triton_silent_exec.py"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$scriptContent = @'</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">import numpy as np</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">from PIL import Image</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">import requests</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">import json</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">import sys</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># Determine broad animal type from predicted label (for demonstration)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">def get_animal_type(label):</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    l = label.lower()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    mapping = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        "DOG": ['dog', 'terrier', 'retriever', 'hound', 'collie', 'beagle', 'malamute', 'husky', 'dalmatian', 'pug'],</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        "CAT": ['cat', 'tabby', 'siamese', 'persian', 'lynx', 'leopard', 'tiger', 'lion', 'cougar', 'cheetah'],</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        "HORSE": ['horse', 'stallion', 'foal', 'zebra', 'sorrel'],</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        "DONKEY": ['donkey', 'mule'],</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        "BUFFALO": ['buffalo', 'bison', 'water buffalo'],</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        "DEER": ['deer', 'impala', 'gazelle', 'elk', 'moose', 'antelope']</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    for category, keywords in mapping.items():</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        if any(x in l for x in keywords):</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            return category</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    return "OTHER"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">def main():</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    try:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        img_path, url = sys.argv[1], sys.argv[2]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        # Fetch labels for ImageNet classes</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        labels_resp = requests.get("https://raw.githubusercontent.com/pytorch/hub/master/imagenet_classes.txt", timeout=10)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        labels = [line.strip() for line in labels_resp.text.strip().splitlines() if line.strip()]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        # Load and preprocess the image</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        img = Image.open(img_path).convert('RGB').resize((224, 224))</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        img_data = (np.array(img).astype(np.float32) / 255.0).transpose(2, 0, 1)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        img_data = np.expand_dims(img_data, axis=0)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        # Prepare binary data and header for Triton HTTP inference request</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        binary_data = img_data.tobytes()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        header = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            "inputs": [{</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                "name": "data",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                "shape": [1, 3, 224, 224],</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                "datatype": "FP32",</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">                "parameters": {"binary_data_size": len(binary_data)}</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            }]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        header_str = json.dumps(header)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        body = header_str.encode('utf-8') + binary_data</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        headers = {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            "Inference-Header-Content-Length": str(len(header_str)),</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            "Content-Type": "application/octet-stream"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        }</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        # Send the request to Triton's HTTP inference endpoint</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        response = requests.post(url, data=body, headers=headers, timeout=15)</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        res = response.json()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        if "error" in res:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            print(f"Error: {res['error']}")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        else:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            scores = res["outputs"][0]["data"]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            idx = scores.index(max(scores))</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            breed = labels[idx]</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            # Print results</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            print("\n--- AI INFERENCE RESULT ---")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            print(f"ANIMAL TYPE    : {get_animal_type(breed)}")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            print(f"SPECIFIC BREED : {breed}")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">            print(f"SCORE     : {max(scores):.4f}")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    except Exception as e:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">        print(f"Execution Failed: {e}")</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">if __name__ == '__main__':</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    main()</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">'@</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 3. Execute the Python inference script with the provided image:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Write-Host "Processing inference..." -ForegroundColor Cyan</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">$scriptContent | Out-File -FilePath $scriptPath -Encoding utf8 -Force</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">python $scriptPath "$imagePath" "$tritonUrl"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain"># 4. Clean up the temporary script file:</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">if (Test-Path $scriptPath) {</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">    Remove-Item $scriptPath</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">}</span><br></div></code></pre></div></div>
</li>
</ol>
<p>Example output:</p>
<div class="language-output codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-output codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">PS D:\Dynamo-Triton&gt; powershell.exe -ExecutionPolicy Bypass -File .\ImageClassificationSample.ps1</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Enter the full path to your local image (e.g., C:\pics\animal.jpg): "D:\Dynamo-Triton\susanneedele-quarter-horse.jpg"</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">Processing Inference...</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain" style="display:inline-block"></span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">--- AI INFERENCE RESULT ---</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">ANIMAL TYPE    : HORSE</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">SPECIFIC BREED : sorrel</span><br></div><div class="token-line" style="color:#393A34"><span class="token plain">SCORE          : 12.5520</span><br></div></code></pre></div></div>
<p>The script maps ImageNet labels to broad animal categories for readability; your actual output will include the full class name.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="cleanup">Cleanup<a href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4#cleanup" class="hash-link" aria-label="Direct link to Cleanup" title="Direct link to Cleanup" translate="no">​</a></h3>
<p>To free resources, delete the triton-inference namespace and all its contents:</p>
<div class="language-powershell codeBlockContainer_Ckt0 theme-code-block" style="--prism-color:#393A34;--prism-background-color:#f6f8fa"><div class="codeBlockContent_QJqH"><pre tabindex="0" class="prism-code language-powershell codeBlock_bY9V thin-scrollbar" style="color:#393A34;background-color:#f6f8fa"><code class="codeBlockLines_e6Vv"><div class="token-line" style="color:#393A34"><span class="token plain">kubectl delete namespace triton-inference</span><br></div></code></pre></div></div>
<p>If you installed the GPU Operator specifically for this test, you can also uninstall it via Helm to release cluster resources.</p>
<h3 class="anchor anchorTargetStickyNavbar_Vzrq" id="troubleshooting">Troubleshooting<a href="https://blog.aks.azure.com/2026/04/07/ai-inference-on-aks-arc-part-4#troubleshooting" class="hash-link" aria-label="Direct link to Troubleshooting" title="Direct link to Troubleshooting" translate="no">​</a></h3>
<p>Here are common issues and how to fix them:</p>
<ul>
<li class="">Triton pod stuck in Pending state: This typically means no suitable node was found with the required GPU resource. Ensure that your cluster has a GPU node available and that the NVIDIA device plugin is running on it. If you haven’t installed the GPU operator or device plugin, revisit the Prepare GPU nodes step above or see the <a href="https://learn.microsoft.com/azure/aks/aksarc/use-gpu-workloads" target="_blank" rel="noopener noreferrer" class="">GPU workloads on AKS Arc</a> documentation.</li>
<li class="">Model fails to download: If the model-downloader pod cannot retrieve the model from the internet (for example, kubectl exec times out or the model file is not present), check that the cluster’s network can access external endpoints such as Hugging Face. If necessary, manually download the model file to your local machine and then transfer it to the cluster (for instance, by attaching a file to the PVC or using a different container image with the model baked in).</li>
<li class="">Triton server not loading the model: If the Triton pod is running but the model isn’t being served (e.g., inference requests return errors), verify the Triton container logs for any errors. Use kubectl logs -n triton-inference deploy/triton-server to see Triton’s output. Check for messages about loading the model; if there are errors, ensure the model repository path is correct (it should be /models/repository inside the container) and that the model file is correctly placed under the folder structure resnet50/1/model.onnx. You can also exec into the Triton pod to confirm the model file exists in the expected location.</li>
<li class="">Inference request errors: If the PowerShell test script returns a JSON parsing error or other failure, ensure you are using PowerShell 7+ (as older Windows PowerShell versions may not handle the JSON body formatting correctly). Additionally, check that the port-forward is active and that you are using the correct URL (<code>http://localhost:8000/v2/models/resnet50/infer</code>). If using a different model or if you modified the deployment, adjust the model name and endpoint accordingly.</li>
</ul>
<p>You now have Triton running on your Arc-enabled AKS cluster, serving predictions from a ResNet-50 model. From here, you can swap in your own ONNX models or try different Triton backends for other AI workloads.</p>]]></content:encoded>
            <category>AKS Arc</category>
            <category>AI</category>
            <category>AI Inference</category>
        </item>
    </channel>
</rss>