Skip to content

Commit

Permalink
feat: Added Azure.PostgreSQL.ZoneRedundantHA (#2933)
Browse files Browse the repository at this point in the history
* feat: Added Azure.PostgreSQL.ZoneRedundantHA

* Minor updates

---------

Co-authored-by: Bernie White <[email protected]>
  • Loading branch information
BenjaminEngeset and BernieWhite authored Jun 13, 2024
1 parent 69ef58a commit d8d0972
Show file tree
Hide file tree
Showing 5 changed files with 263 additions and 18 deletions.
3 changes: 2 additions & 1 deletion docs/CHANGELOG-v1.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ What's changed since v1.37.0:
[#2916](https://github.com/Azure/PSRule.Rules.Azure/issues/2916)
- Verify that servers have zone-redundant high availability (HA) configured by @BenjaminEngeset.
[#2914](https://github.com/Azure/PSRule.Rules.Azure/issues/2914)

- Azure Database for PostgreSQL:
- Verify that Azure Database for PostgreSQL servers have a customer-controlled maintenance window configured by @BenjaminEngeset.
[#2927](https://github.com/Azure/PSRule.Rules.Azure/issues/2927)
- Verify that servers have zone-redundant high availability (HA) configured by @BenjaminEngeset.
[#2932](https://github.com/Azure/PSRule.Rules.Azure/issues/2932)
- Azure Firewall:
- Verify that firewalls have availability zones configured by @BenjaminEngeset.
[#2909](https://github.com/Azure/PSRule.Rules.Azure/issues/2909)
Expand Down
116 changes: 116 additions & 0 deletions docs/en/rules/Azure.PostgreSQL.ZoneRedundantHA.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
severity: Important
pillar: Reliability
category: RE:05 Regions and availability zones
resource: Azure Database for PostgreSQL
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.PostgreSQL.ZoneRedundantHA/
---

# Zone-Redundant High Availability

## SYNOPSIS

Deploy Azure Database for PostgreSQL servers using zone-redundant high availability (HA) in supported regions to ensure high availability and resilience.

## DESCRIPTION

Azure Database for PostgreSQL flexible servers allows configuration high availability (HA) across availability zones in supported regions.
Using availability zones improves resiliency of your solution to failures or disruptions isolated to a zone or data center.

Zone-redundant HA works by:

- Deploying two servers; a primary in one zone, and a secondary in a physically separate zone.
- Daily Data Backups: Performed from the primary server and stored using zone-redundant backup storage.
- Transaction Logs: Continuously archived in zone-redundant backup storage from the standby replica.

The failover process ensures continuous operation by switching from the primary server to the standby replica server.
This process can be:

- Manual (Planned) Failover: Initiated by the user for maintenance or other operational reasons.
- Automatic (Unplanned) Failover: Triggered by Azure in response to failures such as hardware or network issues affecting the primary server.

Before opting for the zone-redundant HA model, review the documentation for additional limitations and critical information.
This includes understanding the latency impact between zones, cost implications, and any specific regional support constraints.

## RECOMMENDATION

Consider deploying flexible servers using zone-redundant high-availability to improve the resiliency of your databases.

## EXAMPLES

### Configure with Azure template

To configure servers that pass this rule:

- Set the `properties.highAvailability.mode` property to `ZoneRedundant`.

For example:

```json
{
"type": "Microsoft.DBforPostgreSQL/flexibleServers",
"apiVersion": "2023-03-01-preview",
"name": "[parameters('serverName')]",
"location": "[parameters('location')]",
"sku": {
"name": "Standard_D16as",
"tier": "GeneralPurpose"
},
"properties": {
"administratorLogin": "[parameters('administratorLogin')]",
"administratorLoginPassword": "[parameters('administratorLoginPassword')]",
"createMode": "Default",
"version": "[parameters('postgresqlVersion')]",
"availabilityZone": "1",
"highAvailability": {
"mode": "ZoneRedundant",
"standbyAvailabilityZone": "2"
}
}
}
```

### Configure with Bicep

To configure servers that pass this rule:

- Set the `properties.highAvailability.mode` property to `ZoneRedundant`.

For example:

```bicep
resource postgresqlDbServer 'Microsoft.DBforPostgreSQL/flexibleServers@2023-03-01-preview' = {
name: serverName
location: location
sku: {
name: 'Standard_D16as'
tier: 'GeneralPurpose'
}
properties: {
administratorLogin: administratorLogin
administratorLoginPassword: administratorLoginPassword
createMode: 'Default'
version: postgresqlVersion
availabilityZone: 1
highAvailability: {
mode: 'ZoneRedundant'
standbyAvailabilityZone: 2
}
}
}
```

<!-- external:avm avm/res/db-for-postgre-sql/flexible-server highAvailability -->

## NOTES

The `Burstable` SKU tier is not supported.

The zone-redundancy HA model is only available in regions that support availability zones.

## LINKS

- [RE:05 Regions and availability zones](https://learn.microsoft.com/azure/well-architected/reliability/regions-availability-zones)
- [High availability concepts in Azure Database for PostgreSQL](https://learn.microsoft.com/azure/reliability/reliability-postgresql-flexible-server)
- [Zone-redundant HA architecture](https://learn.microsoft.com/azure/reliability/reliability-postgresql-flexible-server#availability-zone-support)
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.dbforpostgresql/flexibleservers)
19 changes: 19 additions & 0 deletions src/PSRule.Rules.Azure/rules/Azure.PostgreSQL.Rule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,25 @@ Rule 'Azure.PostgreSQL.AAD' -Ref 'AZR-000389' -Type 'Microsoft.DBforPostgreSQL/f
}
}

# Synopsis: Deploy Azure Database for PostgreSQL servers using zone-redundant high availability (HA) in supported regions to ensure high availability and resilience.
Rule 'Azure.PostgreSQL.ZoneRedundantHA' -Ref 'AZR-000434' -Type 'Microsoft.DBforPostgreSQL/flexibleServers' -Tag @{ release = 'GA'; ruleSet = '2024_06 '; 'Azure.WAF/pillar' = 'Reliability'; } {
# Check if the region supports availability zones.
$provider = [PSRule.Rules.Azure.Runtime.Helper]::GetResourceType('Microsoft.DBforPostgreSQL', 'flexibleServers')
$availabilityZones = GetAvailabilityZone -Location $TargetObject.Location -Zone $provider.ZoneMappings

# Don't flag if the region does not support availability zones.
if (-not $availabilityZones) {
return $Assert.Pass()
}

$supportedSku = @('GeneralPurpose', 'MemoryOptimized')
if ($TargetObject.sku.tier -notin $supportedSku) {
return $Assert.In($TargetObject, 'sku.tier', $supportedSku) # Zone-redundant HA is only supported for the GeneralPurpose and MemoryOptimized SKU tiers.
}

$Assert.HasFieldValue($TargetObject, 'properties.highAvailability.mode', 'ZoneRedundant')
}

#endregion SQL Managed Instance

#region Helper functions
Expand Down
35 changes: 27 additions & 8 deletions tests/PSRule.Rules.Azure.Tests/Azure.PostgreSQL.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -128,8 +128,8 @@ Describe 'Azure.PostgreSQL' -Tag 'PostgreSQL' {
# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -BeIn 'server-A', 'server-B', 'server-E', 'server-F', 'server-G';
$ruleResult.Length | Should -Be 7;
$ruleResult.TargetName | Should -BeIn 'server-A', 'server-B', 'server-E', 'server-F', 'server-G', 'server-H', 'server-I';

$ruleResult[0].Reason | Should -BeExactly "The Azure Database for PostgreSQL 'server-B' should have geo-redundant backup configured.";
$ruleResult[1].Reason | Should -BeExactly "The Azure Database for PostgreSQL 'server-A' should have geo-redundant backup configured.";
Expand Down Expand Up @@ -168,8 +168,8 @@ Describe 'Azure.PostgreSQL' -Tag 'PostgreSQL' {
# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 7;
$ruleResult.TargetName | Should -BeIn 'server-A', 'server-B', 'server-D', 'server-E', 'server-G', 'ActiveDirectoryAdmin-A', 'ActiveDirectoryAdmin-C';
$ruleResult.Length | Should -Be 9;
$ruleResult.TargetName | Should -BeIn 'server-A', 'server-B', 'server-D', 'server-E', 'server-G', 'server-H', 'server-I', 'ActiveDirectoryAdmin-A', 'ActiveDirectoryAdmin-C';

$ruleResult[0].Reason | Should -BeIn 'Path properties.administratorType: Is null or empty.', 'Path properties.login: Is null or empty.', 'Path properties.sid: Is null or empty.';
$ruleResult[1].Reason | Should -BeIn "A sub-resource of type 'Microsoft.DBforPostgreSQL/servers/administrators' has not been specified.";
Expand All @@ -196,17 +196,17 @@ Describe 'Azure.PostgreSQL' -Tag 'PostgreSQL' {
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'server-F', 'server-G';
$ruleResult.Length | Should -Be 4;
$ruleResult.TargetName | Should -BeIn 'server-F', 'server-G', 'server-H', 'server-I';
}

It 'Azure.PostgreSQL.MaintenanceWindow' {
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.PostgreSQL.MaintenanceWindow' };

# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -BeIn 'server-D', 'server-E', 'server-F';
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -BeIn 'server-D', 'server-E', 'server-F', 'server-H', 'server-I';

$ruleResult[0].Reason | Should -BeExactly "Path properties.maintenanceWindow.customWindow: The field 'properties.maintenanceWindow.customWindow' does not exist.";
$ruleResult[1].Reason | Should -BeExactly "Path properties.maintenanceWindow.customWindow: Is set to 'notset'.";
Expand All @@ -217,6 +217,25 @@ Describe 'Azure.PostgreSQL' -Tag 'PostgreSQL' {
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'server-G';
}

It 'Azure.PostgreSQL.ZoneRedundantHA' {
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.PostgreSQL.ZoneRedundantHA' };

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

$ruleResult[0].Reason | Should -BeExactly "Path sku.tier: The field value 'Burstable' was not included in the set.";
$ruleResult[1].Reason | Should -BeExactly "Path properties.highAvailability.mode: Is set to 'Disabled'."
$ruleResult[2].Reason | Should -BeExactly "Path properties.highAvailability.mode: Is set to 'SameZone'.";
$ruleResult[3].Reason | Should -BeExactly "Path sku.tier: The field value 'Burstable' was not included in the set.";

# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult.Length | Should -Be 2;
$ruleResult.TargetName | Should -BeIn 'server-H', 'server-I';
}
}

Context 'Resource name - Azure.PostgreSQL.ServerName' {
Expand Down
108 changes: 99 additions & 9 deletions tests/PSRule.Rules.Azure.Tests/Resources.PostgreSQL.json
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-D",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-D",
"Location": "region",
"Location": "westeurope",
"ResourceName": "server-D",
"Name": "server-D",
"Properties": {
Expand All @@ -337,9 +337,6 @@
"backupRetentionDays": 35,
"geoRedundantBackup": "Enabled"
},
"highAvailability": {
"mode": "Disabled"
},
"version": "14",
"createMode": "Default"
},
Expand All @@ -356,7 +353,7 @@
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-E",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-E",
"Location": "region",
"Location": "westeurope",
"ResourceName": "server-E",
"Name": "server-E",
"Properties": {
Expand All @@ -378,6 +375,7 @@
"startHour": "notset",
"startMinute": "notset"
},
"availabilityZone": "1",
"highAvailability": {
"mode": "Disabled"
},
Expand Down Expand Up @@ -412,7 +410,7 @@
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-F",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-F",
"Location": "region",
"Location": "westeurope",
"ResourceName": "server-F",
"Name": "server-F",
"Properties": {
Expand All @@ -435,8 +433,10 @@
"startHour": 1,
"startMinute": 0
},
"availabilityZone": "1",
"highAvailability": {
"mode": "Disabled"
"mode": "SameZone",
"standbyAvailabilityZone": "1"
},
"version": "14",
"createMode": "Default"
Expand Down Expand Up @@ -469,7 +469,7 @@
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-G",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-G",
"Location": "region",
"Location": "westeurope",
"ResourceName": "server-G",
"Name": "server-G",
"Properties": {
Expand All @@ -492,8 +492,98 @@
"startHour": 1,
"startMinute": 0
},
"availabilityZone": "1",
"highAvailability": {
"mode": "Disabled"
"mode": "ZoneRedundant",
"standbyAvailabilityZone": "2"
},
"version": "14",
"createMode": "Default"
},
"ResourceGroupName": "test-rg",
"Type": "Microsoft.DBforPostgreSQL/flexibleServers",
"ResourceType": "Microsoft.DBforPostgreSQL/flexibleServers",
"Sku": {
"Name": "Standard_B2s",
"Tier": "Burstable"
},
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000"
},
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-H",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-H",
"Location": "notregion",
"ResourceName": "server-H",
"Name": "server-H",
"Properties": {
"administratorLogin": "db-admin",
"authConfig": {
"activeDirectoryAuth": "Enabled",
"passwordAuth": "Disabled",
"tenantId": "00000000-0000-0000-0000-000000000000"
},
"storage": {
"storageSizeGB": 20
},
"backup": {
"backupRetentionDays": 7,
"geoRedundantBackup": "Disabled"
},
"maintenanceWindow": {
"customWindow": "Disabled",
"dayOfWeek": 0,
"startHour": 1,
"startMinute": 0
},
"availabilityZone": "1",
"highAvailability": {
"mode": "ZoneRedundant",
"standbyAvailabilityZone": "2"
},
"version": "14",
"createMode": "Default"
},
"ResourceGroupName": "test-rg",
"Type": "Microsoft.DBforPostgreSQL/flexibleServers",
"ResourceType": "Microsoft.DBforPostgreSQL/flexibleServers",
"Sku": {
"Name": "E64",
"Tier": "MemoryOptimized"
},
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000"
},
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-I",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.DBforPostgreSQL/flexibleServers/server-I",
"Location": "westeurope",
"ResourceName": "server-I",
"Name": "server-I",
"Properties": {
"administratorLogin": "db-admin",
"authConfig": {
"activeDirectoryAuth": "Enabled",
"passwordAuth": "Disabled",
"tenantId": "00000000-0000-0000-0000-000000000000"
},
"storage": {
"storageSizeGB": 20
},
"backup": {
"backupRetentionDays": 7,
"geoRedundantBackup": "Disabled"
},
"maintenanceWindow": {
"customWindow": "Disabled",
"dayOfWeek": 0,
"startHour": 1,
"startMinute": 0
},
"availabilityZone": "1",
"highAvailability": {
"mode": "ZoneRedundant",
"standbyAvailabilityZone": "2"
},
"version": "14",
"createMode": "Default"
Expand Down

0 comments on commit d8d0972

Please sign in to comment.