Skip to content

13.5 — Azure in Practice

Every Azure resource that uses keys gives you two of them, called Key 1 and Key 2, and both work. New users assume one is a backup.

They exist so that rotation has no downtime. Applications use Key 1; you regenerate Key 2, deploy configuration pointing at it, confirm nothing uses Key 1, then regenerate Key 1 and it becomes the spare. That is expand-and-contract (Chapter 8.6.1) built into the product, and it is the answer to "why two".

And the better answer is to use neither, which section 4 covers. Azure's design is identity-first in a way the other providers are not, and understanding that shapes everything else.

1. The hierarchy, and what each level is for

Tenant (one Entra ID directory — the identity boundary)
 └── Management group        policy and access, applied to many subscriptions
      └── Subscription       billing boundary and quota boundary
           └── Resource group   lifecycle boundary
                └── Resource

Each level exists for a different reason, and using the wrong one is a common design error.

The tenant is the identity boundary — one Entra ID directory (Chapter 8.4.9). Everything in it shares users, groups and applications.

A subscription is a billing and quota boundary. Quotas are per subscription per region, so separating production from non-production into different subscriptions stops a development spike consuming production's quota — which is a better reason than the invoicing one.

A resource group is a lifecycle boundary, and this is the one that matters daily: deleting a resource group deletes everything in it. So group resources that live and die together — one application's web app, database and storage — and never put shared infrastructure in an application's group. Resource locks (CanNotDelete, ReadOnly) are the guard against the accidental cascade.

Management groups apply policy and access across many subscriptions, which is how a large organisation enforces a rule once.

2. Control plane and data plane

A distinction that causes real confusion, and it is worth being exact.

The control plane is Azure Resource Manager: creating, configuring and deleting resources. Everything goes through one API, with role assignments and activity logs.

The data plane is using the resource: reading a blob, querying a database, sending a message. Different endpoint, different permission model, different logs.

The classic mistake follows directly. The Contributor role is a control-plane role — it can manage a storage account but has no data-plane permission to read a blob. Except that it can list the account keys, and with a key it can do anything to the data. So Contributor is effectively a data-plane role by a side door, and the correct grant for someone who should read blobs is Storage Blob Data Reader, a data-plane role, with key access disabled on the account.

Disable key-based access where you can (allowSharedKeyAccess: false), which forces identity-based access and makes the permission model honest.

Two more control-plane facts. Every resource type is provided by a resource provider that must be registered in the subscription — a "resource provider not registered" error on a first deployment is this, not a permissions problem. And the control plane throttles: a pipeline creating hundreds of resources in a loop will hit request limits, so batch and back off.

3. Access control

Role assignments are (principal, role, scope) — exactly Chapter 8.4.10's scoped RBAC — and they inherit downward: a role at the subscription applies to every resource group inside it.

Prefer built-in roles; write a custom role only when no built-in one fits, and keep it narrow.

Assign to groups, not to people. Joiners and leavers then change group membership rather than dozens of assignments, and Chapter 8.4.6's provisioning handles it automatically.

Privileged Identity Management provides just-in-time elevation: a user is eligible for a role and activates it for a few hours with a justification and optionally an approval. This is the practical implementation of removing standing privilege (Chapter 8.4.10), and it is the single most valuable access control in an Azure estate.

Azure Policy is guardrails as code, and it is the mechanism that scales governance:

json
{ "if":   { "field": "location", "notIn": ["uksouth", "ukwest"] },
  "then": { "effect": "deny" } }

Effects worth knowing: deny blocks the deployment, audit records non-compliance without blocking, deployIfNotExists remediates automatically (enabling diagnostics on every new resource, for example), and modify adds or changes properties such as required tags.

The pattern that works: start every policy in audit, look at what it would have blocked, then switch to deny. Going straight to deny on a live estate breaks deployments nobody expected.

Landing zones are the packaged version of all this — a subscription structure, network topology, policy set and logging configuration applied consistently. Worth adopting the shape even if you build it yourself.

4. Identity instead of secrets

The Azure-specific version of Chapter 8.6.1's argument.

A managed identity gives a resource an identity in Entra ID with no credential anywhere (Chapter 8.4.9). Prefer user-assigned so role assignments survive the resource being recreated by a pipeline.

Key Vault holds what genuinely must be a secret — third-party API keys, certificates, encryption keys — with a few operational details that matter:

Soft delete is on and cannot be disabled, and purge protection prevents even a permanent delete during the retention window. This protects against a malicious or accidental deletion of the keys that decrypt your data. It also means a deleted vault's name is reserved, so recreating with the same name in a pipeline fails until it is purged — a genuinely common and confusing deployment error.

Use the RBAC permission model, not the legacy access policies. Access policies are per-vault, hard to audit and cannot be assigned to a management group.

Certificate management is the underused feature: Key Vault can hold a certificate, renew it automatically with an integrated authority, and App Service or Application Gateway can reference it — which removes the expiry outage from Chapter 8.3.1.

5. Microsoft Graph

One API for the whole Microsoft 365 estate — users, groups, mail, calendar, files, Teams, devices, security signals — at graph.microsoft.com.

Permissions are delegated or application (Chapter 8.4.9), and the distinction is the same and just as consequential: Mail.Read delegated reads the signed-in user's mail; as an application permission it reads every mailbox in the organisation.

The cost model has three parts, and only one of them is money.

Throttling is the first and most common constraint. Limits are per application per tenant, and per service — Outlook, SharePoint and Teams each have their own. Exceeding them returns 429 with a Retry-After header. Honour that header rather than implementing your own backoff, spread work rather than bursting, and expect a tenant-wide budget shared with every other application in that tenant, including ones you do not control.

Licensing is the second. Some data requires the tenant's users to hold particular licences. An API that works in your test tenant can fail in a customer's because of licensing, which is a support conversation rather than a code fix.

And some APIs are genuinely metered. Teams change notifications and the message export APIs are billed per message against an Azure subscription, so a compliance or archival product built on them has a real per-message cost. Check the metering status of any Teams data API before designing around it — this is the part of "Graph's cost model" that surprises people.

Three practices that avoid most of the trouble:

Delta queries — ask for what changed since a token, instead of listing everything. Dramatically fewer requests.

Change notifications (webhooks) — be told when something changes rather than polling, with the subscription renewed before expiry and the notification validated (Chapter 5.8's webhook checklist).

Batching — up to 20 requests in one call, which helps with both latency and throttling.

6. Azure AI services

The menu: speech, vision, language, translation, document intelligence, content safety, and the Azure OpenAI Service.

What is genuinely different from calling a public API:

Deployments, not models. You create a named deployment of a model in a region and call the deployment name. Model availability varies by region, and moving regions can mean a different set of models.

Quota is measured in tokens per minute and allocated per subscription, per region, per model. It is the binding constraint in practice, not price: a launch fails because quota was not requested weeks ahead, and increases go through a request process. Plan quota like capacity.

Provisioned throughput is the alternative — reserved capacity with predictable latency, priced per unit rather than per token, and worth it only at sustained volume.

Data handling commitments are the usual reason enterprises choose it: prompts and completions are not used to train models, data stays in the chosen region, and private endpoints keep traffic off the internet. For a regulated customer this is often the deciding factor, not capability.

Content filters run on input and output at configurable severities, and they can be adjusted or, with approval, reduced. They will occasionally block legitimate content — the over-refusal problem from Chapter 12.10 — so handle a filtered response as a distinct, user-visible case rather than an error.

Prompt Flow is the orchestration and evaluation surface (Chapter 12.6.1).

Content safety as a standalone service is worth knowing separately: it classifies text and images for harm categories, which is the guardrail layer of Chapter 12.10 available as an API.

7. Operating an Azure estate

Azure Monitor and Log Analytics collect metrics and logs into a workspace queried with KQL:

kusto
AppRequests
| where TimeGenerated > ago(1h) and Success == false
| summarize failures = count() by Name, ResultCode
| order by failures desc

Diagnostic settings must be enabled per resource to send logs anywhere — they are off by default, and the moment you need logs for an incident is the moment you discover it. A deployIfNotExists policy that enables them on every new resource is the fix, and it is the best example of that policy effect.

Cost Management with budgets and anomaly alerts, and tags applied by policy so cost is attributable (Chapter 13.1).

Naming and constraints will bite you: storage account names are globally unique, lower-case, 3–24 characters with no hyphens; many resources cannot be renamed; and some names are reserved after deletion by soft delete. Decide a naming convention before the first deployment, because renaming later means recreating.

Region capacity is real. A specific virtual machine size or GPU family can be unavailable in a region, and the error appears at deployment. Check availability before designing around a size, and have a second region in mind.

What the interviewer will push on

"Why does Azure give you two keys?" Zero-downtime rotation: use one, regenerate the other, move configuration, confirm nothing uses the old one, then regenerate it as the new spare. Then give the better answer — use a managed identity and disable key-based access entirely, so there is no secret to rotate.

"Someone has Contributor on a storage account. Can they read the data?" Yes — indirectly. Contributor is a control-plane role with no data permission, and it can list the account keys, which grant full data access. The correct grant is a data-plane role such as Storage Blob Data Reader, with shared-key access disabled so the side door is closed.

"What is a resource group for?" A lifecycle boundary — deleting it deletes everything inside. Group what lives and dies together, never put shared infrastructure in an application's group, and use resource locks against accidental cascade. Subscriptions are the billing and quota boundary, which is the better reason to split production from development.

"How would you enforce that all resources are deployed only in the UK?" Azure Policy with a deny effect at a management group. Then the operational detail that shows experience: start in audit, review what it would have blocked, then switch to deny — going straight to deny on a live estate breaks deployments nobody predicted.

"What limits a Microsoft Graph integration?" Throttling first — per application per tenant per service, shared with other applications you do not control, and Retry-After must be honoured. Then licensing, which makes an API work in your tenant and fail in a customer's. And some Teams APIs are metered per message, which is a real per-unit cost in an archival product.

"What is the constraint on Azure OpenAI in practice?" Tokens-per-minute quota per subscription, region and model — requested ahead, not provisioned on demand. Plan it like capacity. Then mention the reasons enterprises pick it: regional data residency, no training on your data, private endpoints, and configurable content filters.

One thing to volunteer: point out that Key Vault soft delete reserves the vault name during the retention window, so a pipeline that deletes and recreates a vault with the same name fails until it is purged. It is a small operational detail that produces a genuinely baffling deployment error, and knowing it signals you have run this rather than read about it.

Recall

  • Tenant (identity) → management group (policy) → subscription (billing and quota) → resource group (lifecycle) → resource. Deleting a resource group deletes everything in it — group what lives and dies together, and use resource locks.
  • Control plane (Resource Manager) and data plane are separate permission models. Contributor can list account keys and therefore read data — grant a data-plane role and disable shared-key access.
  • Role assignments are (principal, role, scope) and inherit downward. Assign to groups; use Privileged Identity Management for just-in-time elevation — the practical way to remove standing privilege.
  • Azure Policy is guardrails as code: deny, audit, deployIfNotExists, modify. Always start in audit and switch to deny after reviewing what it would have blocked.
  • Two keys exist for zero-downtime rotation — expand and contract. The better answer is a managed identity with no key at all. Key Vault has non-optional soft delete plus purge protection, which reserves the name and breaks recreate-with-the-same-name pipelines.
  • Graph's cost model is throttling first (per application per tenant per service, shared, honour Retry-After), licensing second (works in your tenant, fails in a customer's), and metered Teams APIs third. Use delta queries, change notifications and batching.
  • Azure OpenAI: deployments, not models; quota is tokens per minute per subscription, region and model, and is the binding constraint. Enterprises choose it for residency, no-training commitments, private endpoints and content filters — and a filtered response is a distinct case, not an error.
  • Diagnostic settings are off by default — enable them everywhere with deployIfNotExists. Decide naming conventions first: many resources cannot be renamed and some names are reserved after deletion.

Self-test: What is the actual purpose of Key 1 and Key 2? · How does Contributor end up reading blob data? · Which boundary is billing and quota, and which is lifecycle? · What is the safe rollout order for a new policy? · Name the three parts of Graph's cost model · What is the practical limit on an Azure OpenAI launch?

Next: 13.6.1 goes down a layer to what actually ships — where containers came from, what problem they solved that virtual machines did not, and why that answer changed how software is delivered.