Skip to content
3 changes: 3 additions & 0 deletions docs/changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,9 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
- App Configuration:
- Check that replica locations are in allowed regions by @BernieWhite.
[#3441](https://github.com/Azure/PSRule.Rules.Azure/issues/3441)
- Azure Cache for Redis:
- Check for legacy Azure Cache for Redis instances by @BenjaminEngeset.
[#3605](https://github.com/Azure/PSRule.Rules.Azure/issues/3605)
- Managed Instance for Apache Cassandra:
- Check that Managed Instance for Apache Cassandra clusters have availability zones enabled by @BenjaminEngeset.
[#3592](https://github.com/Azure/PSRule.Rules.Azure/issues/3592)
Expand Down
100 changes: 100 additions & 0 deletions docs/en/rules/Azure.Redis.MigrateAMR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
---
reviewed: 2025-11-23
severity: Important
pillar: Operational Excellence
category: OE:05 Infrastructure as code
resource: Azure Cache for Redis
resourceType: Microsoft.Cache/redis
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Redis.MigrateAMR/
---

# Migrate to Azure Managed Redis

## SYNOPSIS

Azure Cache for Redis is being retired. Migrate to Azure Managed Redis.

## DESCRIPTION

Microsoft has announced the retirement timeline for Azure Cache for Redis across all SKUs.
The recommended replacement going forward is Azure Managed Redis.

Azure Cache for Redis (Basic, Standard, Premium) will be retired according to the following timeline:

- Creation blocked for new customers: April 1, 2026.
- Creation blocked for existing customers: October 1, 2026.
- Retirement Date: September 30, 2028.
- Instances will be disabled starting October 1, 2028.

To avoid service disruption, migrate your workloads to Azure Managed Redis.

## RECOMMENDATION

Plan and execute migration from Azure Cache for Redis to Azure Managed Redis before the retirement dates to avoid service disruption.

## EXAMPLES

### Configure with Bicep

To deploy resource that pass this rule:

- Create resources of type `Microsoft.Cache/redisEnterprise` and an Azure Managed Redis SKU, such as:
- `Balanced_*`
- `MemoryOptimized_*`
- `ComputeOptimized_*`

For example:

```bicep
resource primary 'Microsoft.Cache/redisEnterprise@2025-07-01' = {
name: name
location: location
properties: {
highAvailability: 'Enabled'
publicNetworkAccess: 'Disabled'
}
sku: {
name: 'Balanced_B10'
}
}
```

### Configure with Azure template

To deploy resource that pass this rule:

- Create resources of type `Microsoft.Cache/redisEnterprise` and an Azure Managed Redis SKU, such as:
- `Balanced_*`
- `MemoryOptimized_*`
- `ComputeOptimized_*`

For example:

```json
{
"type": "Microsoft.Cache/redisEnterprise",
"apiVersion": "2025-07-01",
"name": "[parameters('name')]",
"location": "[parameters('location')]",
"properties": {
"highAvailability": "Enabled",
"publicNetworkAccess": "Disabled"
},
"sku": {
"name": "Balanced_B10"
}
}
```

## NOTES

Redis Enterprise and Enterprise Flash used the `Microsoft.Cache/redisEnterprise` resource type.
Redis Enterprise and Enterprise Flash SKUs `Enterprise_*` and `EnterpriseFlash_*` are also deprecated.

## LINKS

- [OE:05 Infrastructure as code](https://learn.microsoft.com/azure/architecture/framework/devops/automation-infrastructure)
- [Azure Cache for Redis retirement: What to know and how to prepare](https://techcommunity.microsoft.com/blog/azure-managed-redis/azure-cache-for-redis-retirement-what-to-know-and-how-to-prepare/4458721)
- [Azure Cache for Redis retirement FAQ](https://learn.microsoft.com/azure/azure-cache-for-redis/retirement-faq)
- [Azure Managed Redis documentation](https://learn.microsoft.com/azure/azure-cache-for-redis/managed-redis/managed-redis-overview)
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.cache/redisenterprise)
72 changes: 72 additions & 0 deletions docs/examples/resources/amr.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.

// Bicep documentation examples

@minLength(1)
@maxLength(63)
@sys.description('The name of the resource.')
param name string

@sys.description('The location resources will be deployed.')
param location string = resourceGroup().location

@sys.description('The location of a secondary replica.')
param secondaryLocation string = location

// An example Azure Managed Redis instance with availability zones.
resource primary 'Microsoft.Cache/redisEnterprise@2025-07-01' = {
name: name
location: location
properties: {
highAvailability: 'Enabled'
publicNetworkAccess: 'Disabled'
}
sku: {
name: 'Balanced_B10'
}
}

// An example secondary replica in an alternative region.
resource secondary 'Microsoft.Cache/redisEnterprise@2025-07-01' = {
name: name
location: secondaryLocation
properties: {
highAvailability: 'Enabled'
publicNetworkAccess: 'Disabled'
}
sku: {
name: 'Balanced_B10'
}
}

// An example database replicated across the primary and secondary instances.
resource database 'Microsoft.Cache/redisEnterprise/databases@2025-07-01' = {
parent: primary
name: 'default'
properties: {
clientProtocol: 'Encrypted'
evictionPolicy: 'VolatileLRU'
clusteringPolicy: 'OSSCluster'
deferUpgrade: 'NotDeferred'
modules: [
{
name: 'RedisJSON'
}
]
persistence: {
aofEnabled: false
rdbEnabled: true
rdbFrequency: '12h'
}
accessKeysAuthentication: 'Disabled'
geoReplication: {
groupNickname: 'group'
linkedDatabases: [
{
id: resourceId('Microsoft.Cache/redisEnterprise/databases', secondary.name, 'default')
}
]
}
}
}
97 changes: 97 additions & 0 deletions docs/examples/resources/amr.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.39.26.7824",
"templateHash": "6517319720095351040"
}
},
"parameters": {
"name": {
"type": "string",
"minLength": 1,
"maxLength": 63,
"metadata": {
"description": "The name of the resource."
}
},
"location": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "The location resources will be deployed."
}
},
"secondaryLocation": {
"type": "string",
"defaultValue": "[parameters('location')]",
"metadata": {
"description": "The location of a secondary replica."
}
}
},
"resources": [
{
"type": "Microsoft.Cache/redisEnterprise",
"apiVersion": "2025-07-01",
"name": "[parameters('name')]",
"location": "[parameters('location')]",
"properties": {
"highAvailability": "Enabled",
"publicNetworkAccess": "Disabled"
},
"sku": {
"name": "Balanced_B10"
}
},
{
"type": "Microsoft.Cache/redisEnterprise",
"apiVersion": "2025-07-01",
"name": "[parameters('name')]",
"location": "[parameters('secondaryLocation')]",
"properties": {
"highAvailability": "Enabled",
"publicNetworkAccess": "Disabled"
},
"sku": {
"name": "Balanced_B10"
}
},
{
"type": "Microsoft.Cache/redisEnterprise/databases",
"apiVersion": "2025-07-01",
"name": "[format('{0}/{1}', parameters('name'), 'default')]",
"properties": {
"clientProtocol": "Encrypted",
"evictionPolicy": "VolatileLRU",
"clusteringPolicy": "OSSCluster",
"deferUpgrade": "NotDeferred",
"modules": [
{
"name": "RedisJSON"
}
],
"persistence": {
"aofEnabled": false,
"rdbEnabled": true,
"rdbFrequency": "12h"
},
"accessKeysAuthentication": "Disabled",
"geoReplication": {
"groupNickname": "group",
"linkedDatabases": [
{
"id": "[resourceId('Microsoft.Cache/redisEnterprise/databases', parameters('name'), 'default')]"
}
]
}
},
"dependsOn": [
"[resourceId('Microsoft.Cache/redisEnterprise', parameters('name'))]",
"[resourceId('Microsoft.Cache/redisEnterprise', parameters('name'))]"
]
}
]
}
1 change: 1 addition & 0 deletions src/PSRule.Rules.Azure/en/PSRule-rules.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -131,4 +131,5 @@
ResourceHasNoTags = "The resource does not have any tags. Expected tags: {0}."
ActiveSecurityAlerts = "There are {0} active security alerts of high or medium severity."
KeyValueShouldNotContainSecrets = "The key value '{0}' property should not contain secrets."
CacheRedisMigrateAMR = "Azure Cache for Redis is being retired. Migrate to Azure Managed Redis."
}
5 changes: 5 additions & 0 deletions src/PSRule.Rules.Azure/rules/Azure.Redis.Rule.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,11 @@ Rule 'Azure.Redis.Version' -Ref 'AZR-000347' -Type 'Microsoft.Cache/redis' -Tag
).Reason($LocalizedData.AzureCacheRedisVersion)
}

# Synopsis: Azure Cache for Redis is being retired. Migrate to Azure Managed Redis.
Rule 'Azure.Redis.MigrateAMR' -Ref 'AZR-000533' -Type 'Microsoft.Cache/redis' -Tag @{ release = 'GA'; ruleSet = '2025_12'; 'Azure.WAF/pillar' = 'Operational Excellence'; } {
$Assert.Fail($LocalizedData.CacheRedisMigrateAMR)
}

#region Helper functions

function global:GetCacheMemory {
Expand Down
12 changes: 12 additions & 0 deletions tests/PSRule.Rules.Azure.Tests/Azure.Redis.Tests.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -353,6 +353,18 @@ Describe 'Azure.Redis' -Tag 'Redis' {
$ruleResult.TargetName | Should -BeIn 'redis-R';
$ruleResult.Length | Should -Be 1;
}

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

# Fail - all instances should fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 12;
$ruleResult.TargetName | Should -BeIn 'redis-A', 'redis-B', 'redis-C', 'redis-D', 'redis-E', 'redis-F', 'redis-G', 'redis-H', 'redis-I', 'redis-J', 'redis-Q', 'redis-R';

$ruleResult[0].Reason | Should -BeExactly "Azure Cache for Redis is being retired. Migrate to Azure Managed Redis.";
}
}

Context 'With Configuration Option' -Tag 'Configuration' {
Expand Down
Loading