3. Architecture
Core Concepts named the parts. This page traces what actually happens when you press go — on a training job and on a scoring request — because almost every hard bug on this platform lives in the gap between those two paths.
What happens when you submit a job
You type az ml job create -f job.yml. Roughly this follows:
[Image Prompt: 2D minimalistic numbered sequence diagram tracing an Azure Machine Learning training job from CLI submission through Entra ID token acquisition, workspace discovery, code snapshot upload to the default storage account, environment image build in the container registry, cluster scale-out, container start on a node, data read from a datastore using the compute managed identity, MLflow metric logging back to the workspace, model artifact write to storage, and cluster scale-in to zero, flat design, clean vector art style, white background]
- Authenticate. The CLI acquires an Entra ID token for your identity (or the pipeline's federated credential) and calls ARM to resolve the workspace.
- Discover the data plane. The workspace returns its regional data-plane address
(
<region>.api.azureml.ms). Job submission is a data-plane call, not an ARM deployment. This is why a job never appears in the resource group's deployment history. - Snapshot the code. The contents of the job's
codedirectory are uploaded to the workspace's default storage account and given a snapshot ID..amlignore(or.gitignore) controls what goes. Everything not excluded is uploaded — this is why the first job from a repo with a 3 GBdata/folder takes twenty minutes. - Resolve the environment. If the image already exists in ACR, it's reused. If not, Azure ML builds it — a build that runs on the platform's own build compute (or inside your managed VNet if you've configured one) and can take five to fifteen minutes. Cached thereafter.
- Queue against compute. The cluster's autoscaler sees pending work and requests nodes. If quota is
available, nodes allocate in a couple of minutes; if not, the job sits in
Queued— not failed — possibly indefinitely. On Spot nodes, availability, not just quota, gates this. - Run the container. The image starts on the node, the snapshot is mounted, inputs are mounted or downloaded from their datastores using the compute's managed identity, and your command executes.
- Stream telemetry. stdout/stderr and MLflow metrics flow back to the workspace continuously. This is how the studio shows you a live loss curve.
- Write outputs. Anything under the job's output paths lands in storage; a registered model becomes a versioned asset with the job ID recorded as its lineage.
- Scale in. After the idle window, the cluster releases nodes back to zero.
The three latency traps in that list, because "why did my two-minute job take twenty minutes" is the most common first-week question: the code snapshot upload, the environment image build, and the node allocation. All three are one-off per change; none of them are your training loop.
What happens when you score a request
Different path, different address, different identity model.
client ──HTTPS──▶ <endpoint>.<region>.inference.ml.azure.com
│ auth: key | aml_token | aad_token
▼
traffic router ── splits by deployment percentage
│
┌─────────┴─────────┐
▼ ▼
deployment "blue" deployment "green"
(N instances) (M instances)
│
▼
scoring container
init() ← runs once at instance start; load the model here
run(raw) ← runs per request
│
▼
response + logs/metrics → Application Insights
Points that matter operationally:
init()runs once per instance,run()runs per request. Loading a model insiderun()is the classic latency bug: every request pays the load cost.- The traffic split is enforced at the router, per request, not per client. A 90/10 split does not mean 10% of users — it means roughly 10% of calls, distributed however they arrive.
- Health probes gate a deployment going live. A deployment whose container fails its readiness probe
never receives traffic, which is a feature: a broken
greencannot take downblue. - Instances are billed whether or not requests arrive. There is no scale-to-zero.
- Autoscale is Azure Monitor autoscale on the deployment, driven by CPU or by a request metric — the same autoscale engine as VM scale sets, not an ML-specific one. It scales within a deployment; it does not shift traffic between deployments.
- Logs go to Application Insights, and — separately — you can enable data collection to capture request and response payloads to storage for drift analysis. Those are two different switches.
Control plane vs. data plane, drawn properly
[Image Prompt: 2D minimalistic diagram of three separate doors into one Azure Machine Learning workspace — a control plane door at management dot azure dot com governed by Owner, Contributor and AzureML Compute Operator roles, a workspace data plane door at region dot api dot azureml dot ms governed by the AzureML Data Scientist role, and an inference data plane door at endpoint dot region dot inference dot ml dot azure dot com governed by a scoring permission, key, or token, flat design, clean vector art style, white background]
| Control plane | Workspace data plane | Inference data plane | |
|---|---|---|---|
| Address | management.azure.com |
<region>.api.azureml.ms |
<endpoint>.<region>.inference.ml.azure.com |
| Creates | Workspace, compute, endpoints, deployments (as ARM resources), role assignments, private endpoints | Jobs, data assets, environments, model registrations, metrics | Nothing — it serves |
| Auth | Entra ID, Azure RBAC | Entra ID, Azure RBAC | Entra ID token, Azure ML token, or a key |
| Key roles | Owner, Contributor, AzureML Compute Operator |
AzureML Data Scientist, Reader |
The endpoint /score/action permission |
| Shows in | Activity Log, deployment history, what-if |
Job history, MLflow | Application Insights |
Three failure patterns fall directly out of this table:
- "I'm Owner and I get 403 scoring." Control-plane ownership is not a data-plane grant. Assign the scoring permission to the caller's identity, or use a key while you debug.
- "It works in the studio and fails in the job." You are authorised on the storage account; the
compute's managed identity is not. Grant
Storage Blob Data Readerto that identity. - "The pipeline can submit jobs but not create the cluster." Correct, and usually intentional:
AzureML Data Scientistis a data-plane role. AddAzureML Compute Operatorif the pipeline must manage compute, and think hard before you do.
⚠️ Role names and their exact permission sets have changed over releases — verify against current Azure docs before encoding them in policy.
Quota, and how throttling actually manifests
There is no autoscale that rescues you from quota. Three separate pools, all counted per subscription, per region:
| Pool | Counted per | Symptom when exhausted |
|---|---|---|
| Dedicated vCPU, per VM family | subscription × region × family | Cluster stays at its current node count; jobs sit in Queued |
| Low-priority (Spot) vCPU | subscription × region (a separate pool) | Same, plus mid-run eviction when capacity is reclaimed |
| Managed online endpoint quota | subscription × region | Deployment creation fails outright with a quota error |
Plus structural caps: endpoints per workspace, deployments per endpoint, and workspaces per resource
group. ⚠️ All of these numbers vary by subscription type and region — read them from
az ml compute list-usage and the portal's Usage + quotas blade rather than trusting any figure written
down, including here.
The mental model to keep: Azure ML never buys you capacity you weren't granted. A cluster with
max_instances = 40 and quota for 12 is a cluster with 12 nodes and a misleading config file.
Networking paths
Three connections to reason about, and people routinely secure one and forget the others.
- You → workspace. Locked down with a private endpoint on the workspace
(sub-resource
amlworkspace) pluspublic_network_access = Disabled. Now the studio only works from inside the network or over VPN/ExpressRoute. - Compute → everything it needs. This is what the managed virtual network handles. In
AllowInternetOutboundmode, compute reaches the internet for package installs and inbound is closed. InAllowOnlyApprovedOutbound, egress is limited to FQDN rules and private endpoints you declare — which meanspip installfrom PyPI fails until you allow it, and the managed firewall doing the filtering is a billed resource. - Callers → online endpoint. Governed separately by the endpoint's own public network access setting and, for private access, a private endpoint.
The nearly universal mistake: workspace private, storage account still public. The workspace's front door is locked and the filing cabinet is on the pavement. Every dependent resource needs its own private endpoint. See Production.
Identity, in one picture
Four identities are in play and confusing them is the root of most access bugs:
| Identity | Used for |
|---|---|
| Your user identity | Studio, CLI, submitting jobs |
| The workspace's managed identity | Workspace-level access to the dependent resources |
| The compute's identity (system- or user-assigned, per compute) | Reading datastores and Key Vault from inside a job |
| The online deployment's identity | Reading the model, Key Vault, and any resource the scoring script touches |
A user-assigned managed identity used consistently across compute and deployments is the configuration that makes this tractable: one principal to grant, one to audit, one to rotate nothing on. Set it up in Deployment.
Common failure modes, and what they usually mean
| Symptom | Usual cause |
|---|---|
Job stuck in Queued for a long time |
No quota in that VM family/region, or Spot capacity unavailable. Not a failure — it will wait |
| Job fails with a storage 403 | The compute's identity lacks a data role on the storage account; yours has one |
| First job on a new environment takes 15 minutes | Image build. Subsequent jobs reuse the cached image |
| First job from a repo takes forever to start | Code snapshot upload — add an .amlignore |
| Spot job dies at 80% | Eviction. Checkpoint, or use dedicated for long single runs |
| Deployment creation fails on quota | Online endpoint quota is a separate pool from training vCPU |
| Endpoint returns 424 | The scoring container returned an error — read the deployment logs, not the endpoint logs |
| Endpoint healthy, latency terrible | Model loading inside run() instead of init(), or too few instances |
| 403 scoring while being subscription Owner | Control-plane role, data-plane call |
pip install fails only in the managed VNet |
AllowOnlyApprovedOutbound with no PyPI FQDN rule |
| Deleted the workspace, still being billed | Storage, ACR, App Insights and Key Vault survive workspace deletion |
| Can't recreate a workspace with the same name | Soft delete. It's in the recycle bin; recover it or purge it |
The theme: on this service the interesting failures are about identity, quota, and caching, not about the model. Knowing which of the three you are looking at is most of the debugging.
Next: Getting Started →
← Back to the Azure Machine Learning overview · ← Previous: Core Concepts