Centralized Backup of Various Resources with AWS Backup

aws

AWS Backup is a managed service that allows you to centrally backup resources like S3, RDS, DynamoDB, and more specified by resource IDs or tags. You can strengthen governance by applying policies to accounts under your Organization, and prepare for disaster recovery by copying to vaults in other regions or accounts.

AWS Organizaionsで複数のアカウントを一元管理する - sambaiz-net

Pricing is based on storage capacity for both backup and restore operations. However, for S3, the backup cost is $0.06/GB while the storage cost in the Tokyo region is $0.025/GB, which makes AWS Backup quite expensive. For larger scale deployments, it may be better to implement custom backup solutions rather than using AWS Backup. From a data recovery perspective, versioning can be a viable option if you can handle bucket deletion. In fact, versioning must be enabled for S3 buckets to be eligible for AWS Backup.

Enable S3 versioning to retrieve accidentally overwritten or deleted objects - sambaiz-net

import * as cdk from 'aws-cdk-lib';
import { Construct } from 'constructs';
import * as ec2 from 'aws-cdk-lib/aws-ec2';
import * as rds from 'aws-cdk-lib/aws-rds';
import * as s3 from 'aws-cdk-lib/aws-s3';
import * as backup from 'aws-cdk-lib/aws-backup';
import * as iam from 'aws-cdk-lib/aws-iam';

export class AwsBackupTestStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    // VPC for RDS
    const vpc = new ec2.Vpc(this, 'BackupTestVpc', {
      maxAzs: 2,
      natGateways: 0,
      subnetConfiguration: [
        {
          name: 'Public',
          subnetType: ec2.SubnetType.PUBLIC,
          cidrMask: 24,
        },
      ],
    });

    // RDS Instance (PostgreSQL)
    const dbInstance = new rds.DatabaseInstance(this, 'BackupTestDatabase', {
      engine: rds.DatabaseInstanceEngine.postgres({
        version: rds.PostgresEngineVersion.VER_16_6,
      }),
      instanceType: ec2.InstanceType.of(ec2.InstanceClass.T3, ec2.InstanceSize.MICRO),
      vpc,
      vpcSubnets: {
        subnetType: ec2.SubnetType.PUBLIC,
      },
      publiclyAccessible: true,
      multiAz: false,
      allocatedStorage: 20,
      maxAllocatedStorage: 100,
      databaseName: 'testdb',
      credentials: rds.Credentials.fromGeneratedSecret('dbadmin'),
      backupRetention: cdk.Duration.days(7), // Enable RDS automatic backups for PITR (AWS Backup handles long-term retention)
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      deletionProtection: false,
    });

    // Allow PostgreSQL access from anywhere (test environment only)
    dbInstance.connections.allowFromAnyIpv4(ec2.Port.tcp(5432));

    // S3 Bucket
    const bucket = new s3.Bucket(this, 'BackupTestBucket', {
      versioned: true, // Required for AWS Backup
      encryption: s3.BucketEncryption.S3_MANAGED,
      removalPolicy: cdk.RemovalPolicy.DESTROY,
      autoDeleteObjects: true,
    });

    // Tag resources for AWS Backup selection
    cdk.Tags.of(dbInstance).add('Backup', 'daily');
    cdk.Tags.of(bucket).add('Backup', 'daily');

    // Backup Vault
    const backupVault = new backup.BackupVault(this, 'BackupVault', {
      backupVaultName: 'AwsBackupTestVault',
      removalPolicy: cdk.RemovalPolicy.DESTROY,
    });

    // Backup Plan
    const backupPlan = new backup.BackupPlan(this, 'BackupPlan', {
      backupPlanName: 'DailyBackupPlan',
      backupVault,
      backupPlanRules: [
        new backup.BackupPlanRule({
          ruleName: 'HourlyBackup',
          scheduleExpression: cdk.aws_events.Schedule.cron({
            minute: '0',  // Every hour
          }),
          deleteAfter: cdk.Duration.days(7),
          startWindow: cdk.Duration.hours(1),
          completionWindow: cdk.Duration.hours(2),
        }),
      ],
    });

    // Backup Selection (tag-based)
    const backupRole = new iam.Role(this, 'BackupRole', {
      assumedBy: new iam.ServicePrincipal('backup.amazonaws.com'),
      managedPolicies: [
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSBackupServiceRolePolicyForBackup'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('service-role/AWSBackupServiceRolePolicyForRestores'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('AWSBackupServiceRolePolicyForS3Backup'),
        iam.ManagedPolicy.fromAwsManagedPolicyName('AWSBackupServiceRolePolicyForS3Restore'),
      ],
    });

    backupPlan.addSelection('DailyBackupSelection', {
      resources: [
        backup.BackupResource.fromTag('Backup', 'daily'),
      ],
      role: backupRole,
      allowRestores: true,
    });
  }
}

Recovery points are created at specified intervals with a minimum of 60 minutes, and you can select them to restore to a new instance.