Service Groups Architecture Overview

Service Groups Deep Dive
Infrastructure Perspective
Definition and Purpose
Azure Service Groups offer a flexible way to organize and manage resources across subscriptions and resource groups, parallel to any existing Azure resource hierarchy. They’re ideal for scenarios requiring cross-boundary grouping, minimal permissions, and aggregations of data across resources.
Key Characteristics:
- Tenant-Level Hierarchy: Service Groups are a parallel tenant level hierarchy that allows the grouping of resources. The separation from Management Groups, Subscriptions, and Resource Groups allows Service Groups to be connected many times to different resources and resource containers without impacting the existing structures.
- Multi-Membership: Resources can belong to multiple Service Groups simultaneously
- Cross-Subscription Grouping: Group resources across any subscriptions within the same tenant
- Nested Structure: Service Groups allow self nesting to create up to 10 “levels” of grouping depth
Resource Provider and API
Service Groups are managed through the Microsoft.Management resource provider with REST API endpoints:
https://management.azure.com/providers/Microsoft.Management/serviceGroups/[groupID]?api-version=2024-04-01-preview
Infrastructure Benefits:
- Flexible Organization: Create multiple logical views of your cloud estate without altering actual structure
- Cross-Boundary Management: Group resources across different subscriptions and resource groups
- Operational Views: Aggregate resources for operational, organizational, or persona-based needs
- Minimal Impact: Parallel hierarchy doesn’t affect existing ARM deployment structures
Security Perspective
Access Control Model
Service Groups don’t inherit permissions to the members, customers can apply least privileges to assign permissions on the Service Groups that allow viewing of metrics. This capability provides a solution where two users can be assigned access to the same Service Group, but only one is allowed to see certain resources.
Built-in Roles:
- Service Group Contributor: This built-in role should be given to users when they need to create or manage the lifecycle of a Service Group. This role allows for all actions except for Role Assignment capabilities.
- Service Group Reader: This built-in role provides read-only access to service group information and can be assigned to other resources in order to view the connected relationships.
Security Features:
- Low Privilege Management: Service Groups are designed to operate with minimal permissions, ensuring that users can manage resources without needing excessive access rights.
- No Inheritance: Role assignments on Service Groups don’t inherit through memberships to resources
- Isolated Permissions: Different permission levels for Service Group management vs. member resource access
- Origin Tracking: Service Group Member relationships have a property set called “Origin Information” within the response of a GET call. The information provided identifies how the relationship is created.
Governance Integration:
- Azure Monitor: Centralized metrics and monitoring across Service Group members
- Tagging Strategy: Use with Azure tags for enhanced resource organization
- Policy Support: While not deployment targets, Service Groups support governance scenarios
- Compliance Views: Aggregate compliance status across grouped resources
Current Limitations (Public Preview):
- Service Groups are not deployment targets
- RBAC inheritance doesn’t flow through Service Groups to members
- Policy inheritance doesn’t apply through Service Groups
Getting Your Hands Dirty – A Real Lab Experience
Alright, enough theory. Let’s actually build something with Service Groups so you can see how this works in practice. I’m going to walk you through creating a realistic scenario that mirrors what you might actually implement in your organization.
What We’re Building
We’re going to simulate a common enterprise scenario: a web application whose components are scattered across multiple Azure subscriptions for valid business reasons. The web servers live in the application subscription, the database is in the compliance managed data subscription, and shared services like networking are in yet another subscription.
Sound familiar? If you’ve worked in enterprise Azure, you’ve definitely seen this pattern.
Before We Start
Prerequisites (The Boring But Necessary Stuff)
- Azure subscription where you have Contributor access
- Azure PowerShell module installed (trust me, it’s easier than fighting with REST calls)
- Access to Azure Service Groups preview (you’ll need to request this at aka.ms/servicegroups)
- PowerShell 5.1 or PowerShell 7+ (either works fine)
- About 30 minutes and a coffee
A Quick Note on Costs
The resources we’re creating are deliberately small (Standard_B1s VMs, basic storage), so you’re looking at maybe a few dollars for the duration of this lab. Just remember to run the cleanup section when you’re done!
Step 1: Getting Our Environment Ready
First, let’s get connected and figure out what we’re working with:
# The usual Azure login dance
Connect-AzAccount
Set-AzContext -SubscriptionId "your-subscription-id"
# Let's grab some key information we'll need
$TenantId = (Get-AzContext).Tenant.Id
$SubscriptionId = (Get-AzContext).Subscription.Id
$Location = "East US" # Change this if you prefer a different region
$UserObjectId = (Get-AzADUser -SignedIn).Id
# Just so we know what we're working with
Write-Host "Tenant ID: $TenantId" -ForegroundColor Green
Write-Host "Subscription ID: $SubscriptionId" -ForegroundColor Green
Write-Host "User Object ID: $UserObjectId" -ForegroundColor Green
Step 2: Building Our Scattered Infrastructure
Here’s where we simulate the real world messiness. We’re going to create resources in different resource groups to mimic how they might be distributed across subscriptions in a real environment:
# Create our resource groups (imagine these are in different subscriptions)
Write-Host "Creating resource groups..." -ForegroundColor Yellow
$WebAppRG = New-AzResourceGroup -Name "rg-webapp-prod" -Location $Location -Tag @{Environment="Production"; ServiceType="WebApp"}
$FinanceRG = New-AzResourceGroup -Name "rg-finance-prod" -Location $Location -Tag @{Environment="Production"; Department="Finance"}
$SharedRG = New-AzResourceGroup -Name "rg-shared-services" -Location $Location -Tag @{Environment="Production"; ServiceType="Shared"}
# Set up networking (this would typically be in your shared services subscription)
$SubnetConfig = New-AzVirtualNetworkSubnetConfig -Name "subnet-web" -AddressPrefix "10.0.1.0/24"
$VNet = New-AzVirtualNetwork -ResourceGroupName "rg-shared-services" -Location $Location -Name "vnet-shared" -AddressPrefix "10.0.0.0/16" -Subnet $SubnetConfig
# Create a basic network security group
$NSG = New-AzNetworkSecurityGroup -ResourceGroupName "rg-shared-services" -Location $Location -Name "nsg-web"
# Create our web server VM (this is where the magic happens later)
Write-Host "Creating VM... this might take a few minutes" -ForegroundColor Yellow
$PublicIP = New-AzPublicIpAddress -ResourceGroupName "rg-webapp-prod" -Location $Location -Name "pip-web-01" -AllocationMethod Static -Sku Standard
$NIC = New-AzNetworkInterface -ResourceGroupName "rg-webapp-prod" -Location $Location -Name "nic-web-01" -SubnetId $VNet.Subnets[0].Id -PublicIpAddressId $PublicIP.Id -NetworkSecurityGroupId $NSG.Id
# VM Configuration - keep it simple
$VMConfig = New-AzVMConfig -VMName "vm-web-01" -VMSize "Standard_B1s"
$VMConfig = Set-AzVMOperatingSystem -VM $VMConfig -Linux -ComputerName "vm-web-01" -Credential (Get-Credential -Message "Enter VM admin credentials")
$VMConfig = Set-AzVMSourceImage -VM $VMConfig -PublisherName "Canonical" -Offer "0001-com-ubuntu-server-jammy" -Skus "22_04-lts-gen2" -Version "latest"
$VMConfig = Add-AzVMNetworkInterface -VM $VMConfig -Id $NIC.Id
$VM = New-AzVM -ResourceGroupName "rg-webapp-prod" -Location $Location -VM $VMConfig -Tag @{Environment="Production"; Tier="Web"; ServiceType="WebApp"}
# Create a storage account (imagine this holds our application data)
$StorageAccountName = "stfinanceprod$(Get-Date -Format 'yyyyMMddHHmmss')"
$StorageAccount = New-AzStorageAccount -ResourceGroupName "rg-finance-prod" -Name $StorageAccountName -Location $Location -SkuName "Standard_LRS" -Tag @{Environment="Production"; Department="Finance"}
Write-Host "Resources created successfully" -ForegroundColor Green
Step 3: Now for the Service Groups Magic
This is where things get interesting. We’re going to create Service Groups that organize our scattered resources in ways that actually make business sense:
# Helper function to create Service Groups (because I hate repeating REST calls)
function New-ServiceGroup {
param(
[string]$GroupId,
[string]$DisplayName,
[string]$ParentResourceId
)
Write-Host "Creating Service Group: $DisplayName" -ForegroundColor Yellow
$Headers = @{
'Authorization' = "Bearer $((Get-AzAccessToken).Token)"
'Content-Type' = 'application/json'
}
$Body = @{
properties = @{
displayName = $DisplayName
parent = @{
resourceId = $ParentResourceId
}
}
} | ConvertTo-Json -Depth 3
$Uri = "https://management.azure.com/providers/Microsoft.Management/serviceGroups/$GroupId" + "?api-version=2024-04-01-preview"
try {
Invoke-RestMethod -Uri $Uri -Method PUT -Headers $Headers -Body $Body
Write-Host "Service Group '$DisplayName' created successfully" -ForegroundColor Green
}
catch {
Write-Warning "Failed to create Service Group '$DisplayName': $($_.Exception.Message)"
}
}
# Create our Service Group hierarchy
New-ServiceGroup -GroupId "Production" -DisplayName "Production Resources" -ParentResourceId "/providers/Microsoft.Management/serviceGroups/$TenantId"
New-ServiceGroup -GroupId "Finance-Dept" -DisplayName "Finance Department" -ParentResourceId "/providers/Microsoft.Management/serviceGroups/$TenantId"
# This one is nested under Production - showing hierarchical organization
New-ServiceGroup -GroupId "WebApp-Workload" -DisplayName "Web Application Workload" -ParentResourceId "/providers/Microsoft.Management/serviceGroups/Production"
Step 4: The Waiting Game (Because Azure Takes Time)
One thing Microsoft doesn’t tell you in the docs: Service Groups are created asynchronously. You need to wait for them to actually exist before you can use them:
# Function to check if a Service Group is ready
function Test-ServiceGroup {
param([string]$GroupId)
$Headers = @{
'Authorization' = "Bearer $((Get-AzAccessToken).Token)"
}
$Uri = "https://management.azure.com/providers/Microsoft.Management/serviceGroups/$GroupId" + "?api-version=2024-04-01-preview"
try {
$Response = Invoke-RestMethod -Uri $Uri -Method GET -Headers $Headers
Write-Host "$GroupId is ready" -ForegroundColor Green
return $true
}
catch {
Write-Host "⏳ $GroupId is still being created..." -ForegroundColor Yellow
return $false
}
}
# Wait for our Service Groups to be ready (grab another coffee ☕)
$ServiceGroups = @("Production", "Finance-Dept", "WebApp-Workload")
foreach ($SG in $ServiceGroups) {
do {
Start-Sleep -Seconds 10
} while (!(Test-ServiceGroup -GroupId $SG))
}
Write-Host "All Service Groups are ready!" -ForegroundColor Green
Step 5: Building the Relationships (The Fun Part)
Now we get to the heart of what makes Service Groups useful creating these flexible relationships between Service Groups and resources:
# Another helper function because life's too short for repetitive REST calls
function Add-ServiceGroupMember {
param(
[string]$ResourceId,
[string]$MembershipName,
[string]$TargetServiceGroupId
)
Write-Host "Adding resource to Service Group '$TargetServiceGroupId'" -ForegroundColor Yellow
$Headers = @{
'Authorization' = "Bearer $((Get-AzAccessToken).Token)"
'Content-Type' = 'application/json'
}
$Body = @{
properties = @{
targetResourceId = "/providers/Microsoft.Management/serviceGroups/$TargetServiceGroupId"
}
} | ConvertTo-Json -Depth 2
$Uri = "https://management.azure.com$ResourceId/providers/Microsoft.Relationship/ServiceGroupMember/$MembershipName" + "?api-version=2024-04-01-preview"
try {
Invoke-RestMethod -Uri $Uri -Method PUT -Headers $Headers -Body $Body
Write-Host "Added resource to Service Group '$TargetServiceGroupId'" -ForegroundColor Green
}
catch {
Write-Warning "Failed to add resource to Service Group: $($_.Exception.Message)"
}
}
# Add entire resource groups to Service Groups
$WebAppRGId = "/subscriptions/$SubscriptionId/resourceGroups/rg-webapp-prod"
Add-ServiceGroupMember -ResourceId $WebAppRGId -MembershipName "production-membership" -TargetServiceGroupId "Production"
$FinanceRGId = "/subscriptions/$SubscriptionId/resourceGroups/rg-finance-prod"
Add-ServiceGroupMember -ResourceId $FinanceRGId -MembershipName "finance-membership" -TargetServiceGroupId "Finance-Dept"
# Here's where it gets interesting - add the same VM to multiple Service Groups
$VMResourceId = "/subscriptions/$SubscriptionId/resourceGroups/rg-webapp-prod/providers/Microsoft.Compute/virtualMachines/vm-web-01"
Add-ServiceGroupMember -ResourceId $VMResourceId -MembershipName "production-vm-membership" -TargetServiceGroupId "Production"
Add-ServiceGroupMember -ResourceId $VMResourceId -MembershipName "webapp-vm-membership" -TargetServiceGroupId "WebApp-Workload"
Write-Host "Resource relationships created!" -ForegroundColor Green
Step 6: Setting Up Access Control (The Security Bit)
This is where Service Groups really shine you can give people visibility without giving them dangerous permissions:
Write-Host "Configuring RBAC for Service Groups..." -ForegroundColor Yellow
# Give yourself reader access to the Finance Department Service Group
New-AzRoleAssignment -ObjectId $UserObjectId -RoleDefinitionName "Service Group Reader" -Scope "/providers/Microsoft.Management/serviceGroups/Finance-Dept"
# Give yourself contributor access to manage the Production Service Group
New-AzRoleAssignment -ObjectId $UserObjectId -RoleDefinitionName "Service Group Contributor" -Scope "/providers/Microsoft.Management/serviceGroups/Production"
Write-Host "RBAC configured" -ForegroundColor Green
Step 7: See What You’ve Built (The Payoff)
Let’s explore what we’ve created and see how Service Groups give us new ways to view our resources:
# Helper functions for querying (because the output formatting matters)
function Get-ServiceGroups {
$Headers = @{ 'Authorization' = "Bearer $((Get-AzAccessToken).Token)" }
$Uri = "https://management.azure.com/providers/Microsoft.Management/serviceGroups?api-version=2024-04-01-preview"
try {
$Response = Invoke-RestMethod -Uri $Uri -Method GET -Headers $Headers
return $Response.value
}
catch {
Write-Warning "Failed to retrieve Service Groups: $($_.Exception.Message)"
return $null
}
}
function Get-ResourceMemberships {
param([string]$ResourceId)
$Headers = @{ 'Authorization' = "Bearer $((Get-AzAccessToken).Token)" }
$Uri = "https://management.azure.com$ResourceId/providers/Microsoft.Relationship/ServiceGroupMember?api-version=2024-04-01-preview"
try {
$Response = Invoke-RestMethod -Uri $Uri -Method GET -Headers $Headers
return $Response.value
}
catch {
Write-Warning "Failed to retrieve resource memberships: $($_.Exception.Message)"
return $null
}
}
# Show off what we've built
Write-Host "`n=== All Service Groups ===" -ForegroundColor Cyan
$ServiceGroups = Get-ServiceGroups
if ($ServiceGroups) {
$ServiceGroups | Select-Object name, @{Name="DisplayName";Expression={$_.properties.displayName}}, @{Name="Parent";Expression={$_.properties.parent.resourceId}} | Format-Table -AutoSize
}
Write-Host "=== VM Service Group Memberships ===" -ForegroundColor Cyan
$VMMemberships = Get-ResourceMemberships -ResourceId $VMResourceId
if ($VMMemberships) {
$VMMemberships | Select-Object name, @{Name="ServiceGroup";Expression={$_.properties.targetResourceId}} | Format-Table -AutoSize
}
Write-Host "=== Service Group Role Assignments ===" -ForegroundColor Cyan
Get-AzRoleAssignment -Scope "/providers/Microsoft.Management/serviceGroups/Production" | Select-Object DisplayName, RoleDefinitionName, Scope | Format-Table -AutoSize
Step 8: Understanding What You’ve Accomplished
Let’s take a step back and see the bigger picture of what we just built:
Write-Host "`n=== The Big Picture ===" -ForegroundColor Magenta
Write-Host "`nTraditional Azure Hierarchy:" -ForegroundColor Yellow
Write-Host "├── Subscription: $($SubscriptionId.Substring(0,8))..."
Write-Host "│ ├── rg-webapp-prod"
Write-Host "│ │ └── vm-web-01"
Write-Host "│ ├── rg-finance-prod"
Write-Host "│ │ └── $StorageAccountName"
Write-Host "│ └── rg-shared-services"
Write-Host "│ ├── vnet-shared"
Write-Host "│ └── nsg-web"
Write-Host "`nService Group Organization:" -ForegroundColor Yellow
Write-Host "├── Production Service Group"
Write-Host "│ ├── rg-webapp-prod (entire resource group)"
Write-Host "│ ├── vm-web-01 (individual resource)"
Write-Host "│ └── WebApp-Workload (nested service group)"
Write-Host "│ └── vm-web-01 (same resource, multi-membership)"
Write-Host "└── Finance-Dept Service Group"
Write-Host " └── rg-finance-prod (entire resource group)"
Write-Host "`n Key Insights:" -ForegroundColor Green
Write-Host "• Your VM appears in BOTH Production and WebApp-Workload Service Groups"
Write-Host "• Resource groups can be members of Service Groups as complete units"
Write-Host "• Individual resources can also be members independently"
Write-Host "• None of this affects your original Azure hierarchy"
Write-Host "• You can now create dashboards and reports based on Service Group membership"
Step 9: Clean Up (Don’t Skip This!)
When you’re done experimenting, clean up to avoid unexpected charges:
Write-Host "`n🧹 Cleaning up resources..." -ForegroundColor Yellow
# Helper functions for cleanup
function Remove-ServiceGroupMember {
param([string]$ResourceId, [string]$MembershipName)
$Headers = @{ 'Authorization' = "Bearer $((Get-AzAccessToken).Token)" }
$Uri = "https://management.azure.com$ResourceId/providers/Microsoft.Relationship/ServiceGroupMember/$MembershipName" + "?api-version=2024-04-01-preview"
try {
Invoke-RestMethod -Uri $Uri -Method DELETE -Headers $Headers
Write-Host "Removed Service Group membership: $MembershipName" -ForegroundColor Green
}
catch {
Write-Warning "Failed to remove membership: $($_.Exception.Message)"
}
}
function Remove-ServiceGroup {
param([string]$GroupId)
$Headers = @{ 'Authorization' = "Bearer $((Get-AzAccessToken).Token)" }
$Uri = "https://management.azure.com/providers/Microsoft.Management/serviceGroups/$GroupId" + "?api-version=2024-04-01-preview"
try {
Invoke-RestMethod -Uri $Uri -Method DELETE -Headers $Headers
Write-Host "Service Group '$GroupId' deleted" -ForegroundColor Green
}
catch {
Write-Warning "Failed to delete Service Group '$GroupId': $($_.Exception.Message)"
}
}
# Uncomment the lines below when you're ready to clean up
<#
# Remove Service Group memberships first
Remove-ServiceGroupMember -ResourceId $VMResourceId -MembershipName "production-vm-membership"
Remove-ServiceGroupMember -ResourceId $VMResourceId -MembershipName "webapp-vm-membership"
Remove-ServiceGroupMember -ResourceId $WebAppRGId -MembershipName "production-membership"
Remove-ServiceGroupMember -ResourceId $FinanceRGId -MembershipName "finance-membership"
# Delete Service Groups (children first due to hierarchy)
Remove-ServiceGroup -GroupId "WebApp-Workload"
Remove-ServiceGroup -GroupId "Finance-Dept"
Remove-ServiceGroup -GroupId "Production"
# Delete Azure resources
Remove-AzResourceGroup -Name "rg-webapp-prod" -Force -AsJob
Remove-AzResourceGroup -Name "rg-finance-prod" -Force -AsJob
Remove-AzResourceGroup -Name "rg-shared-services" -Force -AsJob
Write-Host "🗑️ Cleanup initiated - resources are being deleted in the background"
#>
Write-Host "Uncomment the cleanup section and run it when you're done experimenting!" -ForegroundColor Red
What You’ve Actually Accomplished
By the end of this lab, you’ve built something that solves real problems:
Flexible Resource Organization: Your VM now appears in multiple Service Groups based on different organizational needs
Cross-Boundary Grouping: Resources from different resource groups are unified under logical Service Groups
Hierarchical Structure: You’ve got nested Service Groups that mirror business relationships
Security Integration: Different permission levels for viewing vs. managing Service Groups
Non-Disruptive Implementation: Your original Azure hierarchy remains untouched
This isn’t just a lab exercise it’s a pattern you can apply to organize your actual Azure resources in ways that finally make business sense.
The best part? Your resources stay exactly where they are. Service Groups just add a new organizational layer that works the way your team actually thinks about your infrastructure.
One Last Tip: Start simple. Don’t try to reorganize your entire Azure estate with Service Groups on day one. Pick one specific problem maybe grouping all the components of a critical application and solve that first. Once you see the value, you can expand from there.
Service Groups aren’t magic, but they might just be the organizational tool Azure has been missing.