Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(new): Added Azure.APIM.AvailabilityZone.Units #2866

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions docs/CHANGELOG-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
[#2846](https://github.com/Azure/PSRule.Rules.Azure/issues/2846)
- Check that database accounts have public network access disabled by @BenjaminEngeset.
[#2702](https://github.com/Azure/PSRule.Rules.Azure/issues/2702)
- API Management:
- Check that scale units are distributed evenly across the configured availability zones by @BenjaminEngeset.
[#2788](https://github.com/Azure/PSRule.Rules.Azure/issues/2788)

## v1.37.0-B0009 (pre-release)

Expand Down
147 changes: 147 additions & 0 deletions docs/en/rules/Azure.APIM.AvailabilityZone.Units.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
---
severity: Important
pillar: Reliability
category: RE:05 Regions and availability zones
resource: API Management
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.APIM.AvailabilityZone.Units/
---

# API management services should use the same number of units as the number of availability zones or greater in a region.

## SYNOPSIS

Configure the same number of units as the number of availability zones or greater in a region.

## DESCRIPTION

Enabling zone redundancy for an API Management instance in a supported region provides redundancy for all service components: gateway, management plane, and developer portal. Azure automatically replicates all service components across the zones that you select. Zone redundancy is only available in the Premium service tier.

When you enable zone redundancy in a region, both in primary and additional regions, consider the number of API Management scale units that need to be distributed. Minimally, configure the same number of units as the number of availability zones, or a multiple so that the units are distributed evenly across the zones. For example, if you select 3 availability zones in a region, you could have 3 units so that each zone hosts one unit.

## RECOMMENDATION

Consider configuring the same number of units as the number of availability zones or greater in a region.

## EXAMPLES

### Configure with Azure template

To deploy API management instances that pass this rule:

- Set `zones` to a minimum of two zones from `["1", "2", "3"]`, ensuring the number of zones match `sku.capacity` and/or `properties.additionalLocations[*].zones` to a minimum of two zones from `["1", "2", "3"]`, ensuring the number of zones match `properties.additionalLocations[*].sku.capacity`.
- Set `sku.name` and/or `properties.additionalLocations[*].sku.name` to `Premium`.

For example:

```json
{
"type": "Microsoft.ApiManagement/service",
"apiVersion": "2023-05-01-preview",
"name": "[parameters('service_api_mgmt_name')]",
"location": "Australia East",
"sku": {
"name": "Premium",
"capacity": 3
},
"zones": [
"1",
"2",
"3"
],
"properties": {
"publisherEmail": "[email protected]",
"publisherName": "contoso",
"notificationSenderEmail": "[email protected]",
"hostnameConfigurations": [
{
"type": "Proxy",
"hostName": "[concat(parameters('service_api_mgmt_name'), '.azure-api.net')]",
"negotiateClientCertificate": false,
"defaultSslBinding": true,
"certificateSource": "BuiltIn"
}
],
"additionalLocations": [
{
"location": "East US",
"sku": {
"name": "Premium",
"capacity": 3
},
"zones": [
"1",
"2",
"3"
],
"disableGateway": false
}
],
"virtualNetworkType": "None",
"disableGateway": false,
"apiVersionConstraint": {}
}
}
```

### Configure with Bicep

To set availability zones for a API management service

- Set `zones` to a minimum of two zones from `["1", "2", "3"]`, ensuring the number of zones match `sku.capacity` and/or `properties.additionalLocations[*].zones` to a minimum of two zones from `["1", "2", "3"]`, ensuring the number of zones match `properties.additionalLocations[*].sku.capacity`.
- Set `sku.name` and/or `properties.additionalLocations[*].sku.name` to `Premium`.

For example:

```bicep
resource apim 'Microsoft.ApiManagement/service@2023-05-01-preview' = {
name: service_api_mgmt_name
location: 'Australia East'
sku: {
name: 'Premium'
capacity: 3
}
zones: [
'1',
'2',
'3'
]
properties: {
publisherEmail: '[email protected]'
publisherName: 'contoso'
notificationSenderEmail: '[email protected]'
hostnameConfigurations: [
{
type: 'Proxy'
hostName: '${service_api_mgmt_test2_name}.azure-api.net'
negotiateClientCertificate: false
defaultSslBinding: true
certificateSource: 'BuiltIn'
}
]
additionalLocations: [
{
location: 'East US'
sku: {
name: 'Premium'
capacity: 3
}
zones: [
'1'
'2'
'3'
]
disableGateway: false
}
]
virtualNetworkType: 'None'
disableGateway: false
apiVersionConstraint: {}
}
}
```

## LINKS

- [RE:05 Regions and availability zones](https://learn.microsoft.com/azure/well-architected/reliability/regions-availability-zones)
- [Units distribution evenly across the zones](https://learn.microsoft.com/azure/api-management/high-availability#availability-zones)
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.apimanagement/service)
54 changes: 54 additions & 0 deletions src/PSRule.Rules.Azure/rules/Azure.APIM.Rule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -334,6 +334,36 @@ Rule 'Azure.APIM.DefenderCloud' -Ref 'AZR-000387' -Type 'Microsoft.ApiManagement
}
}

# Synopsis: Configure the same number of units as the number of availability zones or greater in a region.
Rule 'Azure.APIM.AvailabilityZone.Units' -Ref 'AZR-000422' -Type 'Microsoft.ApiManagement/service' -If { (IsPremiumAPIM) -and (Test-IsZoneRedundant) } -Tag @{ release = 'GA'; ruleSet = '2024_06'; 'Azure.WAF/pillar' = 'Reliability'; } {
# Configure the same number of units as the number of availability zones or greater in the primary region. If you select 3 availability zones in the region > 3 units so that each zone hosts one unit.
$zones = @($TargetObject.zones)
if ($zones.Count -eq '2' -and -not (Compare-Object -DifferenceObject $zones -ReferenceObject '1', '2')) {
$Assert.GreaterOrEqual($TargetObject, 'sku.capacity', 2)
}

if ($zones.Count -eq '3' -and -not (Compare-Object -DifferenceObject $zones -ReferenceObject '1', '2', '3')) {
$Assert.GreaterOrEqual($TargetObject, 'sku.capacity', 3)
}

# Configure the same number of units as the number of availability zones or greater in additional regions. If you select 3 availability zones in a region > 3 units so that each zone hosts one unit.
$additionalLocations = @($TargetObject.properties.additionalLocations)
if ($additionalLocations.Count -gt 0) {
foreach ($location in $additionalLocations) {
$sku = $location.sku.name
$zones = @($location.zones)
}
if ($sku -eq 'Premium' -and $zones.Count -eq '2' -and -not (Compare-Object -DifferenceObject $zones -ReferenceObject '1', '2')) {
$Assert.GreaterOrEqual($location, 'sku.capacity', 2)
}

if ($sku -eq 'Premium' -and $zones.Count -eq '3' -and -not (Compare-Object -DifferenceObject $zones -ReferenceObject '1', '2', '3')) {
$Assert.GreaterOrEqual($location, 'sku.capacity', 3)

}
}
}

#endregion Rules

#region Helper functions
Expand Down Expand Up @@ -389,4 +419,28 @@ function global:HasRestApi {
}
}

function global:Test-IsZoneRedundant {
[CmdletBinding()]
param ( )
# Check if more than 1 zone is selected in the primary region.
if ($TargetObject.zones.Count -gt 1) {
return $true
}

# Check if more than 1 zone is selected in additional regions.
if ($additionalLocations.Count -gt 0) {
foreach ($location in $additionalLocations) {
$sku = $location.sku.name
$zones = @($location.zones)
}
if ($sku -eq 'Premium' -and $zones.Count -eq '2' -and -not (Compare-Object -DifferenceObject $zones -ReferenceObject '1', '2')) {
return $true
}

if ($sku -eq 'Premium' -and $zones.Count -eq '3' -and -not (Compare-Object -DifferenceObject $zones -ReferenceObject '1', '2', '3')) {
return $true
}
}
}

#endregion Helper functions
20 changes: 20 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/Azure.APIM.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -465,6 +465,26 @@ Describe 'Azure.APIM' -Tag 'APIM' {
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'apim-D';
}

It 'Azure.APIM.AvailabilityZone.Units' {
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.APIM.AvailabilityZone.Units' };

# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult.Length | Should -Be 4;
$ruleResult.TargetName | Should -BeIn 'apim-E', 'apim-F', 'apim-G', 'apim-I';

$ruleResult[0].Reason | Should -BeIn "Path sku.capacity: The value '1' was not >= '2'.";
$ruleResult[1].Reason | Should -BeIn "Path sku.capacity: The value '2' was not >= '3'.";
$ruleResult[2].Reason | Should -BeIn "Path sku.capacity: The value '1' was not >= '3'.";
$ruleResult[3].Reason | Should -BeIn "Path sku.capacity: The value '2' was not >= '3'.";


# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -BeIn 'apim-J', 'apim-M', 'apim-N', 'apim-O', 'apim-P';
}
}

Context 'With Template' {
Expand Down
Loading
Loading