From 092cf327986f0b83c9c6e51fd695a42008826c58 Mon Sep 17 00:00:00 2001 From: Sven Aelterman <17446043+SvenAelterman@users.noreply.github.com> Date: Tue, 17 Dec 2024 17:02:11 -0500 Subject: [PATCH] Backup Azure Files to Recovery Services Vault (#142) * Create Backup Policy for AzFiles * Rename protected item module * Protect file shares with Backup * Clarify output name of VM enhanced backup policy --- research-spoke/main.bicep | 11 +- shared-modules/compute/virtualMachine.bicep | 2 +- .../recovery/recoveryServicesVault.bicep | 189 +++++++++++------- .../recovery/rsvProtectedItem-fs.bicep | 29 +++ ...edItem.bicep => rsvProtectedItem-vm.bicep} | 0 5 files changed, 159 insertions(+), 72 deletions(-) create mode 100644 shared-modules/recovery/rsvProtectedItem-fs.bicep rename shared-modules/recovery/{rsvProtectedItem.bicep => rsvProtectedItem-vm.bicep} (100%) diff --git a/research-spoke/main.bicep b/research-spoke/main.bicep index ba526a8..c98c9bb 100644 --- a/research-spoke/main.bicep +++ b/research-spoke/main.bicep @@ -520,7 +520,7 @@ module vdiModule '../shared-modules/virtualDesktop/main.bicep' = if (useSessionH diskEncryptionSetId: diskEncryptionSetModule.outputs.id sessionHostCount: sessionHostCount - backupPolicyName: recoveryServicesVaultModule.outputs.backupPolicyName + backupPolicyName: recoveryServicesVaultModule.outputs.vmBackupPolicyName recoveryServicesVaultId: recoveryServicesVaultModule.outputs.id // TODO: Use activeDirectoryDomainInfo type @@ -649,6 +649,13 @@ module recoveryServicesVaultModule '../shared-modules/recovery/recoveryServicesV roles: rolesModule.outputs.roles keyVaultResourceGroupName: keyVaultModule.outputs.resourceGroupName keyVaultName: keyVaultModule.outputs.keyVaultName + + timeZone: 'Central Standard Time' + + protectedStorageAccountId: storageModule.outputs.storageAccountId + protectedAzureFileShares: [ + fileShareNames.shared + ] } } @@ -667,7 +674,7 @@ resource avdConnectionPrivateDnsZone 'Microsoft.Network/privateDnsZones@2020-06- } output recoveryServicesVaultId string = recoveryServicesVaultModule.outputs.id -output backupPolicyName string = recoveryServicesVaultModule.outputs.backupPolicyName +output vmBackupPolicyName string = recoveryServicesVaultModule.outputs.vmBackupPolicyName output diskEncryptionSetId string = diskEncryptionSetModule.outputs.id output computeSubnetId string = networkModule.outputs.createdSubnets.computeSubnet.id output computeResourceGroupName string = computeRg.name diff --git a/shared-modules/compute/virtualMachine.bicep b/shared-modules/compute/virtualMachine.bicep index 5a46e1e..c500b6b 100644 --- a/shared-modules/compute/virtualMachine.bicep +++ b/shared-modules/compute/virtualMachine.bicep @@ -243,7 +243,7 @@ var rsvRgName = !empty(recoveryServicesVaultId) ? split(recoveryServicesVaultId, // Create a backup item for each session host // This must be deployed in a separate module because it's in a different resource group -module backupItems '../recovery/rsvProtectedItem.bicep' = if (!empty(backupPolicyName) && !empty(recoveryServicesVaultId)) { +module backupItems '../recovery/rsvProtectedItem-vm.bicep' = if (!empty(backupPolicyName) && !empty(recoveryServicesVaultId)) { name: replace(deploymentNameStructure, '{rtype}', '${vmHostName}-backup') scope: resourceGroup(rsvRgName) params: { diff --git a/shared-modules/recovery/recoveryServicesVault.bicep b/shared-modules/recovery/recoveryServicesVault.bicep index f7819ea..e7d47c8 100644 --- a/shared-modules/recovery/recoveryServicesVault.bicep +++ b/shared-modules/recovery/recoveryServicesVault.bicep @@ -16,6 +16,83 @@ param location string = resourceGroup().location param tags object param storageType string = 'GeoRedundant' +param backupTime string = '2023-12-31T08:00:00.000Z' +param dailyRetentionDurationCount int = 8 +param weeklyRetentionDurationCount int = 6 +param monthlyRetentionDurationCount int = 13 +param weeklyRetentionDays ('Sunday' | 'Monday' | 'Tuesday' | 'Wednesday' | 'Thursday' | 'Friday' | 'Saturday')[] = [ + 'Sunday' +] +param timeZone string + +param protectedStorageAccountId string +param protectedAzureFileShares string[] + +@description('The schedule policy used for the custom Virtual Machine backup policy.') +param schedulePolicy object = { + schedulePolicyType: 'SimpleSchedulePolicyV2' + scheduleRunFrequency: 'Hourly' + hourlySchedule: { + interval: 4 + scheduleWindowStartTime: backupTime + scheduleWindowDuration: 4 + } + dailySchedule: null + weeklySchedule: null +} + +@description('The schedule policy used for the custom Azure File Shares backup policy.') +param fileShareSchedulePolicy object = { + schedulePolicyType: 'SimpleSchedulePolicy' + scheduleRunFrequency: 'Daily' + scheduleRunDays: null + scheduleRunTimes: [ + backupTime + ] +} + +@description('The retention policy used for all custom backup policies.') +param retentionPolicy object = { + retentionPolicyType: 'LongTermRetentionPolicy' + + dailySchedule: { + retentionTimes: [backupTime] + retentionDuration: { + count: dailyRetentionDurationCount + durationType: 'Days' + } + } + + weeklySchedule: { + daysOfTheWeek: weeklyRetentionDays + retentionTimes: [backupTime] + retentionDuration: { + count: weeklyRetentionDurationCount + durationType: 'Weeks' + } + } + + monthlySchedule: { + retentionScheduleFormatType: 'Daily' + retentionScheduleDaily: { + daysOfTheMonth: [ + { + date: 1 + isLast: false + } + ] + } + retentionTimes: [backupTime] + retentionDuration: { + count: monthlyRetentionDurationCount + durationType: 'Months' + } + retentionScheduleWeekly: null + } + + yearlySchedule: null +} + var vaultName = replace(namingStructure, '{rtype}', 'rsv') resource keyVaultResourceGroup 'Microsoft.Resources/resourceGroups@2024-03-01' existing = { @@ -68,7 +145,7 @@ resource recoveryServicesVault 'Microsoft.RecoveryServices/vaults@2024-04-01' = publicNetworkAccess: 'Enabled' - // Use a customer-managed key when not debugging and when specified + // Use a customer-managed key when not debugging and when required encryption: !debugMode && useCMK ? { keyVaultProperties: { @@ -112,9 +189,6 @@ resource backupConfig 'Microsoft.RecoveryServices/vaults/backupconfig@2024-04-01 } } -// Create a new enhanced policy to use custom schedule -var backupTime = '2023-12-31T08:00:00.000Z' - // Break up the naming convention on the sequence placeholder to use for the backup RG name var processNamingConventionPlaceholders = replace( replace( @@ -133,77 +207,54 @@ var splitNamingConvention = split(processNamingConventionPlaceholders, '{seq}') var azureBackupRGNamePrefix = '${splitNamingConvention[0]}${sequenceFormatted}-' var azureBackupRGNameSuffix = length(splitNamingConvention) > 1 ? splitNamingConvention[1] : '' -// LATER: Parameterize backup policy values -resource enhancedBackupPolicy 'Microsoft.RecoveryServices/vaults/backupPolicies@2024-04-01' = { - name: 'EnhancedPolicy-${workloadName}-${sequenceFormatted}' - parent: recoveryServicesVault - properties: { - backupManagementType: 'AzureIaasVM' - - instantRPDetails: { - // Following the naming convention of the other resource groups - azureBackupRGNamePrefix: azureBackupRGNamePrefix - azureBackupRGNameSuffix: azureBackupRGNameSuffix - } - - instantRpRetentionRangeInDays: 2 - timeZone: 'Central Standard Time' - policyType: 'V2' - - schedulePolicy: { - schedulePolicyType: 'SimpleSchedulePolicyV2' - scheduleRunFrequency: 'Hourly' - hourlySchedule: { - interval: 4 - scheduleWindowStartTime: backupTime - scheduleWindowDuration: 4 - } - dailySchedule: null - weeklySchedule: null - } +var backupPolicyCommonProperties = { + retentionPolicy: retentionPolicy + timeZone: timeZone +} - retentionPolicy: { - retentionPolicyType: 'LongTermRetentionPolicy' +var backupPolicyIaasVmProperties = { + schedulePolicy: schedulePolicy + backupManagementType: 'AzureIaasVM' + instantRpRetentionRangeInDays: 2 + policyType: 'V2' + instantRPDetails: { + azureBackupRGNamePrefix: azureBackupRGNamePrefix + azureBackupRGNameSuffix: azureBackupRGNameSuffix + } +} - dailySchedule: { - retentionTimes: [backupTime] - retentionDuration: { - count: 8 - durationType: 'Days' - } - } +var backupPolicyAzureStorageProperties = { + backupManagementType: 'AzureStorage' + workloadType: 'AzureFileShare' + schedulePolicy: fileShareSchedulePolicy +} - weeklySchedule: { - retentionTimes: [backupTime] - retentionDuration: { - count: 6 - durationType: 'Weeks' - } - daysOfTheWeek: ['Sunday'] - } +// Create an enhanced VM backup policy to backup multiple times per day +resource iaasVmBackupPolicy 'Microsoft.RecoveryServices/vaults/backupPolicies@2024-04-01' = { + name: 'EnhancedPolicy-${workloadName}-${sequenceFormatted}' + parent: recoveryServicesVault + properties: union(backupPolicyCommonProperties, backupPolicyIaasVmProperties) +} - monthlySchedule: { - retentionTimes: [backupTime] - retentionDuration: { - count: 13 - durationType: 'Months' - } - retentionScheduleFormatType: 'Daily' - retentionScheduleDaily: { - daysOfTheMonth: [ - { - date: 1 - isLast: false - } - ] - } - retentionScheduleWeekly: null - } +// Create a single Azure File backup policy, even if there are multiple file shares or storage accounts +resource filesBackupPolicy 'Microsoft.RecoveryServices/vaults/backupPolicies@2024-04-01' = if (length(protectedAzureFileShares) > 0) { + name: 'AzureFileSharesPolicy-${workloadName}-${sequenceFormatted}' + parent: recoveryServicesVault + properties: union(backupPolicyCommonProperties, backupPolicyAzureStorageProperties) +} - yearlySchedule: null +// Create a protected item per Azure File Share to be protected +module fileShareProtectedItems 'rsvProtectedItem-fs.bicep' = [ + for fileShare in protectedAzureFileShares: { + name: take(replace(deploymentNameStructure, '{rtype}', 'rsv-fs-${fileShare}'), 64) + params: { + backupPolicyName: filesBackupPolicy.name + fileShareName: fileShare + recoveryServicesVaultId: recoveryServicesVault.id + storageAccountId: protectedStorageAccountId } } -} +] // Lock the Recovery Services Vault to prevent accidental deletion resource lock 'Microsoft.Authorization/locks@2020-05-01' = if (!debugMode) { @@ -216,7 +267,7 @@ resource lock 'Microsoft.Authorization/locks@2020-05-01' = if (!debugMode) { output id string = recoveryServicesVault.id output name string = recoveryServicesVault.name -output backupPolicyName string = enhancedBackupPolicy.name +output vmBackupPolicyName string = iaasVmBackupPolicy.name // For debug purposes only output backupResourceGroupNameStructure string = '${azureBackupRGNamePrefix}{N}${azureBackupRGNameSuffix}' diff --git a/shared-modules/recovery/rsvProtectedItem-fs.bicep b/shared-modules/recovery/rsvProtectedItem-fs.bicep new file mode 100644 index 0000000..fe00666 --- /dev/null +++ b/shared-modules/recovery/rsvProtectedItem-fs.bicep @@ -0,0 +1,29 @@ +param backupPolicyName string +param recoveryServicesVaultId string +param storageAccountId string +param fileShareName string + +var rsvName = split(recoveryServicesVaultId, '/')[8] + +var storageAccountRgName = split(storageAccountId, '/')[4] +var storageAccountName = split(storageAccountId, '/')[8] + +resource protectionContainer 'Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers@2024-04-01' = { + name: '${rsvName}/Azure/storagecontainer;Storage;${storageAccountRgName};${storageAccountName}' + properties: { + backupManagementType: 'AzureStorage' + containerType: 'StorageContainer' + sourceResourceId: storageAccountId + } +} + +resource protectedItem 'Microsoft.RecoveryServices/vaults/backupFabrics/protectionContainers/protectedItems@2024-04-01' = { + parent: protectionContainer + name: 'AzureFileShare;${fileShareName}' + properties: { + protectedItemType: 'AzureFileShareProtectedItem' + sourceResourceId: storageAccountId + policyId: resourceId('Microsoft.RecoveryServices/vaults/backupPolicies', rsvName, backupPolicyName) + //isInlineInquiry: true + } +} diff --git a/shared-modules/recovery/rsvProtectedItem.bicep b/shared-modules/recovery/rsvProtectedItem-vm.bicep similarity index 100% rename from shared-modules/recovery/rsvProtectedItem.bicep rename to shared-modules/recovery/rsvProtectedItem-vm.bicep