Skip to content

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:

  1. 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 init and terraform plan run with the variables file of the addon from the configuration repository, and the plan is published as a pipeline artifact.
  2. 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.
  3. 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.