Shared Services: What Is Deployed and How¶
This page is for the platform team. It describes what the shared services code deploys, how it is configured, and how the deployment pipeline works. The workload team's view is on Shared services. The extensions that connect a spoke to these services are described on Spoke extensions.
Who is "the platform team" depends on your operating model: XMMS in the managed model, your own team in the self-managed model.
Reference design, your deployment may differ
Shared services are not part of the core MCP hub and spoke (Service types). The facts below come from the Terraform code and pipeline of one reference implementation. Resource names, sizes and the set of addons that you run can differ. Values in examples are placeholders.
How it fits together¶
flowchart LR
code["Shared services code<br/>(Terraform, one folder per addon)"] --> pipe["Deployment pipeline<br/>(plan, then apply)"]
cfg["Shared services configuration<br/>(one variables file per addon and environment)"] --> pipe
pipe -->|"service connection"| spoke["Shared services spoke<br/>(spoke canvas: VNet, Key Vault, Log Analytics)"]
spoke --> svc["Addons: gateway, registry, API Management,<br/>messaging, communication, build agents"]
ws["Workload spokes"] -->|"spoke extensions<br/>(roles, groups, identities)"| svc
Two repositories work together:
| Repository | Content |
|---|---|
| Shared services code | Reusable Terraform per addon in infra/<addon>/, and the pipeline templates in pipelines/templates/ |
| Shared services configuration | The environment-specific values (variables.tfvars) per addon |
The code does not contain environment values. A deployment combines one addon folder of the code with the matching variables
file of the configuration. The code repository has a workflow that triggers a downstream synchronisation on every push to main and on tags that start
with v. For the mechanism, see Code synchronisation.
What the code deploys¶
The shared services are built into an existing spoke. Every addon reads the resource group, virtual network, subnet,
Key Vault and Log Analytics workspace of that spoke by name (spoke_config variable), so the spoke must exist before you deploy
an addon (Spoke deployment).
Addon (folder in infra/) |
What it deploys | Access it creates |
|---|---|---|
appgw |
Application Gateway with WAF policies, public IP, user-assigned identity, Key Vault for certificates | Gateway identity gets Key Vault roles. See below |
acr |
Container registry from modules (Premium, private endpoint, customer-managed key) | Optional role assignments for groups you pass in by object ID |
acr-alt |
Container registry, self-contained (Premium, private endpoint, customer-managed key) | Creates an AcrPull and an AcrPush group |
apim |
API Management, Application Insights with its own Log Analytics workspace, optional developer portal | Monitoring Reader group, optional portal content editor group |
event-hub-namespace |
Event Hubs namespace and event hubs, private endpoint, customer-managed key | A sender group and a receiver group per event hub |
service-bus |
Service Bus namespace (Premium), private endpoint, optional customer-managed key | A contributors group on the namespace |
acs |
Azure Communication Services and Email Communication Services, with domains | A contributors group |
cae-agents |
Self-hosted Azure Pipelines agent pool on Container Apps jobs | Managed identity with AcrPull and administrator on the pool. See Container agent pool |
Groups are created in the administrative unit that you pass in (administrative_unit_id), the unit of the landing zone.
Common security settings¶
| Setting | Where |
|---|---|
| Public network access disabled, access through a private endpoint | Registry, Service Bus, Event Hubs, Key Vault of the gateway. API Management in the StandardV2 SKU (see below) |
| Local authentication (keys, SAS) disabled | Service Bus (local_auth_enabled = false), Event Hubs (local_authentication_enabled = false), Application Insights and its workspace |
| Registry: admin user off, anonymous pull off, export policy off | acr and acr-alt |
| Customer-managed key in the spoke Key Vault | Registry, Event Hubs, Service Bus (when customer_managed_key_enabled is true) |
| RBAC authorisation, purge protection on | Key Vault of the gateway |
The customer-managed keys are RSA 2048 keys with an expiry one year after creation, an automatic rotation policy and a
notification before expiry. The private DNS zone group of the private endpoints is not managed by the code
(ignore_changes on the private endpoint, or private_endpoints_manage_dns_zone_group = false): DNS registration comes from
the platform (Private endpoint DNS coverage). Check that your platform covers the
services you deploy.
Naming and environments¶
Each addon supports two naming modes:
| Mode | Variables | Example result |
|---|---|---|
| Environment-based (recommended for new deployments) | environment (at most 7 characters) and location_short |
apim-nonprod-weu-001, resource group rg-apim-nonprod-weu-001 |
| Legacy | resource_postfix |
apim-<postfix>-001 |
The environment-based names are used only when both environment and location_short are set. Existing deployments that
use resource_postfix plan with no changes. Moving an existing deployment from one mode to the other changes the names, so
Terraform creates new resources: plan a migration (new deployment, data migration, clean-up) and do not switch by editing
the variables. The Application Gateway addon has no legacy mode: environment and location_short are required.
| Service | Resource name pattern (environment-based) |
|---|---|
| Resource group | rg-<service>-<environment>-<location_short>-001 |
| API Management | apim-<environment>-<location_short>-001 |
| Service Bus namespace | sbns-<environment>-<location_short>-001 |
| Event Hubs namespace | ehns-<environment>-<location_short>-001 |
Container registry (acr-alt) |
acr<environment><location_short>001 (hyphens removed) |
| Communication Services | acs-<environment>-<location_short>-001, email service ecs-... |
| Application Gateway | agw-<environment>-<location_short>-001 |
Run non-production and production as separate deployments: separate subscriptions, separate Terraform state files (for example
apim-nonprod.tfstate and apim-prod.tfstate) and separate groups. The example values in the code repository differ per
environment, for example:
| Setting | Non-production example | Production example |
|---|---|---|
| API Management SKU and units | StandardV2, 1 |
Premium, 2, zones 1, 2, 3 |
| Event Hubs | Standard, capacity 1, 2 partitions, retention 1 | Standard, capacity 2, auto-inflate to 10 throughput units, 4 partitions, retention 7 |
| Service Bus | capacity 1, 1 messaging partition | capacity 2, 4 messaging partitions |
Registry (acr-alt) |
No data endpoint, quarantine, zone redundancy or trust policy, retention 7 | Data endpoint, quarantine, zone redundancy and trust policy on, retention 30 |
Treat these as examples from the repository's deployment guide, not as defaults.
Addon notes¶
Application Gateway (appgw)¶
Reference design: the gateway is deployed in a spoke, in the subnet AppGwSubnet of the spoke virtual network. The code
creates:
| Resource | Notes |
|---|---|
Resource group rg-agw-<environment>-<location_short>-001 |
Holds all resources below |
| Public IP | Standard SKU, static, zones 1, 2 and 3. Optional DNS label (public_ip_dns_name). DDoS protection is on by default (use_ddos_protection_plan), which needs the plan name, resource group and subscription (ddos_protection_plan, ddos_protection_plan_subscription_id). |
| Application Gateway | Zones 1, 2 and 3. A user-assigned identity. Everything else comes from JSON files. |
| User-assigned identity | Gets Key Vault Certificates Officer and Key Vault Secrets User on the gateway Key Vault |
Key Vault kv-agw-<environment>-<location_short>-<random> |
RBAC, purge protection, 7 days soft delete, public access disabled, private endpoint in the subnet from spoke_config.subnet_pe_name |
| WAF policies | One per JSON file. See WAF exceptions |
An optional private frontend IP is enabled with enable_private_ip and private_ip_address.
The gateway configuration is JSON, in the folder set by application_gateway_json_file_relative_path (default
gateway-definitions/, pattern *.json). Files have a top-level application_gateway object. The sections of all files are
merged section by section (backend pools, HTTP settings, frontend ports, listeners, routing rules, probes, certificates, URL
path maps, redirects, rewrite rules, SKU and autoscale, TLS policies and profiles). Plan with every section present, as the
module reads each section directly. The structure of a workload entry is the same as described in
Application Gateway (add or change a workload).
The gateway uses the WAF policy that comes from the file web_application_firewall_policy_hub.json (the file name decides
the policy key). Other WAF policy files create policies that a listener or path rule can reference by resource ID.
This addon is not in the deployment pipeline of the code repository (see Pipeline). How it is deployed in your platform is not documented in the sources.
Two gateway implementations
The MCP hub can also deploy an Application Gateway from the connectivity layer
(Platform resources). The shared services addon above is a
separate implementation with other resource names and settings. Know which one your platform uses before you follow a runbook.
Differences are listed on Application Gateway.
Container registry (acr, acr-alt)¶
Both create a Premium registry with the admin user, anonymous pull and export disabled, public network access disabled, a private endpoint and encryption with a customer-managed key (with a user-assigned identity that has Key Vault Crypto Service Encryption User on the spoke Key Vault).
acr |
acr-alt |
|
|---|---|---|
| Structure | Modules (container-registry, private_endpoint, role_assignments) called from deploy/ |
One self-contained folder |
| Groups | You pass in the object IDs of up to seven groups (Owner, Contributor, Reader, AcrPush, AcrPull, AcrDelete, AcrImageSigner). Each role is assigned only for a group you pass in. | Creates two groups (names from acr_pull_group_name and acr_push_group_name) with AcrPull and AcrPush |
| Registry settings | data_endpoint_enabled and quarantine_policy_enabled are fixed to false in deploy/ |
container_registry_config: data endpoint, quarantine, zone redundancy, retention days (default 7), trust policy |
| Terraform root | infra/acr/deploy |
infra/acr-alt |
The acr-alt pull and push groups are the main groups that the ACR spoke extension expects (acr_pull_group_object_id and
acr_push_group_object_id in the shared_services block, see Spoke extensions).
The sources do not say which variant is the current one. The code contains no pull-through cache rules for public registries:
if the registry caches Microsoft Container Registry or Docker Hub on your platform, that is configured elsewhere.
API Management (apim)¶
| Topic | Behaviour |
|---|---|
| SKU | StandardV2 or Premium. Units 1 to 10 for StandardV2, 1 to 99 for Premium. Availability zones only with Premium. |
| Network | Premium: virtual network type Internal, no private endpoint. StandardV2: type External with a private endpoint, and public network access is switched off after the first creation (the property cannot be set when the service is created, so the code checks whether the service exists). |
| Identity | A system-assigned and a user-assigned identity |
| Telemetry | Application Insights (local authentication disabled) on its own Log Analytics workspace, with an API Management logger that uses the managed identity. Optional diagnostic settings to the spoke workspace (diagnostic_settings). The Application Insights retention defaults to 90 days. |
| Developer portal | Off by default (developer_portal.enabled). When on: basic authentication off, sign-in required, optional terms of service, optional content editors group. |
| Access | A group with Monitoring Reader on Application Insights (application_insights_monitoring_readers_group_name) |
| Custom domain | Not deployed. The file custom_domain.tf is commented out. |
The developer-facing API definitions and products, and the use of API Management as an integration building block, are described in the Integration platform.
Event Hubs and Service Bus¶
| Topic | Event Hubs (event-hub-namespace) |
Service Bus (service-bus) |
|---|---|---|
| SKU | Set by event_hub_namespace_config (sku, capacity, auto-inflate, maximum throughput units) |
Premium (needed for customer-managed keys and private endpoints). capacity 1, 2, 4, 8 or 16. messaging_partitions 1, 2 or 4. |
| Content | One event hub per entry in event_hub_config (name, partition count, retention in days, status) |
The namespace. Queues and topics are not created by this code. |
| Network | Private endpoint, network rule set default action Deny, trusted services off | Private endpoint, public access off, trusted services off |
| Groups | Per event hub: a sender group (Azure Event Hubs Data Sender) and a receiver group (Azure Event Hubs Data Receiver and Contributor) on that event hub | A contributors group with Contributor on the namespace |
| Encryption | Customer-managed key always | Customer-managed key when customer_managed_key_enabled is true (default false) |
The spoke extensions add the roles for the spokes on top of these (Spoke extensions). The messaging services as integration building blocks are covered in the Integration platform.
Communication Services (acs)¶
Creates a Communication Services resource and an Email Communication Services resource in the data location you choose
(data_location), an Azure-managed email domain (deploy_managed_domain, default false) and one customer-managed domain for
each name in customer_managed_domains. Diagnostic settings send the email logs (send mail, status update, user engagement) to
the spoke workspace. A group with Contributor gives access. The code comments say that the group is needed until spoke
extensions are available for this service, which matches Spoke extensions.
Container agent pool (cae-agents)¶
See Container agent pool.
Pipeline¶
The reference pipeline pipelines/deployment.yaml deploys selected addons. It checks out the code and the configuration
repository side by side and has one boolean parameter per addon:
| Parameter | Addon folder | State key |
|---|---|---|
deployACR |
infra/acr |
acr-deploy.tfstate |
deployAPIM |
infra/apim |
apim-deploy.tfstate |
deployACS |
infra/acs |
acs-deploy.tfstate |
deployEVHN |
infra/event-hub-namespace |
evhn-deploy.tfstate |
deployASB |
infra/service-bus |
asb-deploy.tfstate |
deployCAEAgents |
infra/cae-agents |
cae-agents-deploy.tfstate |
There is also a branch parameter, to deploy a branch or tag of the code repository. There is no parameter for appgw and
acr-alt.
How a run works:
- Plan stage: one job per selected addon. Terraform is installed (the reference pins version 1.9.3), the state backend
(resource group, storage account, container) is created if it does not exist,
terraform initandterraform planrun with the variables file of the addon from the configuration repository, and the plan is published as a pipeline artifact. - Apply stage: one stage per addon, which depends on the plan stage. The stage runs as a deployment job against an Azure DevOps environment, so approvals and checks on that environment apply. It downloads the plan artifact and applies exactly that plan, and publishes the Terraform output as an artifact.
- For
cae-agents, an extra job starts the placeholder job of the agent pool after the apply.
Authentication is through the Azure DevOps service connection. The template uses a federated token (OIDC) when the service connection provides one, otherwise the client secret of the service principal. The state is in an Azure Storage account with Microsoft Entra ID authentication.
The pipeline reads these values from a variable group: the service connection name, the target subscription, the state resource group, storage account and container, and, for the agent pool, the workload name and resource postfix. The agent pool also needs a personal access token for the Azure DevOps Terraform provider (Container agent pool).
The README of the code repository also shows how to run two pipelines for one addon, with a different environment, subscription,
state key and variables file (-var-file=nonprod.tfvars, -var-file=prod.tfvars). The reference pipeline deploys one
environment, named prod in Azure DevOps.
Check the pipeline before you rely on it
Parts of the reference pipeline look inconsistent with the code layout: the registry job points at infra/acr, while the
Terraform root is infra/acr/deploy, and the variables file paths differ per job (../config/... against
../../../config/...). The template that creates the state backend also allows public blob access on a storage account that it
creates. Test a plan for every addon and review the state storage account settings before production use.
Operate¶
| Task | How |
|---|---|
| Change a setting | Change the variables file in the configuration repository (or the code), run the pipeline for the addon and review the plan. |
| Add an addon or a second instance | Create a variables file and a state key. Follow the naming pattern with environment and location_short, keep the variables optional with null defaults, and keep legacy naming working. |
| Connect a workload | Switch on the spoke extension (Spoke extensions). It needs the resource ID of the service in shared_services. |
| Grant a service-specific role to a group | Add the member to the group the addon created. Members that you add to these groups by hand are not removed by the extensions. |
| Investigate a failed apply | Read the plan artifact and the Terraform output of the stage. |
| Check the customer-managed keys | They are in the spoke Key Vault and are named after the service (for example <resource name>-encryption-key; the acr module uses <registry name>-managedkey-001). They have a rotation policy. Check their expiry dates. |