Building IaaS in the cloud is becoming more popular. And part of building IaaS is providing some level of disaster recovery. After spending the last few weeks working in AWS, I realized that the toolsets I expected to exist just don’t. So what did I do? Scripted my own, or at least a start.
Coming from the world of Azure, I am used to the availability of what they call LRS (locally redundant storage) and GRS (globally redundant storage). When a virtual machine is backed by LRS, there are always three-local copies of the data in the data center which are in separate failure domains. GRS takes that a step further by creating three copies of the data in a companion datacenter. When I started deploying instances in AWS, I mistakenly assumed that a similar storage backing was available to provide disaster recovery to a companion region. Evidently, I was wrong. So what’s the suggested alternative? Create snapshots of the volumes backing your instance, and then copy those snapshots to another region for durability. So naturally I looked for the setting to auto-create snapshots, and… there isn’t one. So instead I rolled my own using AWS PowerShell (since I love PowerShell) and running the process as a scheduled task.
The first script in the series is below. It takes the following:
The script does a few things. First it finds the existing volumes for the instance and tags them with new tags. I included the instance name, instance ID, the mount point of the volume, and what region it’s in. All this information can be used later when the snapshot it copied over to another region. If it’s the root volume of the instance, I also add in the VPC ID and the Subnet ID, and set an IsRootVolume tag to true. I’m doing that in order to automate the recovery of the instance in another region. That script is still in development, but I figure if I know the instance ID, VPC, and subnet, I can probably recover a bunch of instances in a mirrored config in another region. Now that the volumes are tagged appropriately, I’ll create a snapshot. The snapshot will copy all the tags on the volume, and add a Date and Name tag to the snapshot. The date is nice for deleting stale snapshots, or finding a consistent point in time for a bunch of them.
The script ends by outputting the snapshot IDs, which can be ingested by another script to copy them over to another region.
Here is the current version of the script:
<# | |
.SYNOPSIS | |
This script is intended to tag the volumes of an existing instance and create a snapshot for those volumes. | |
.DESCRIPTION | |
The script takes an instance ID and optional region parameter. It will find the instance in the region submitted or the | |
current default region and find all volumes attached to the instance. Then it will tag those volumes, and create a snapshot. | |
The snapshot will include the volume tags, as well as a date and name tag of its own. | |
.PARAMETER instanceID | |
Required, string, the instanceID of the instance with volumes to have snapshots taken. | |
.PARAMETER region | |
Optional, string, the region in which the instance is located. If no region is specified, the script will use the | |
default region from the user's AWS profile. | |
.INPUTS | |
None | |
.OUTPUTS | |
A list of snapshot IDs created by the script. | |
.NOTES | |
Version: 1.0 | |
Author: Ned Bellavance | |
Creation Date: 9/25/2016 | |
Purpose/Change: Initial script development | |
.EXAMPLE | |
Create-EC2Snapshots -InstanceID "i-abd2f39a" | |
Tags volumes and creates a snapshot for the instance "i-abd2f39a" in the default region. | |
#> | |
#region parameters# | |
param( | |
[Parameter(Mandatory = $true)] | |
[string] $instanceID, | |
[string] $region | |
) | |
#endregion parameters# | |
#region helper functions# | |
#Import Module | |
Import-Module AWSPowerShell | |
#Function takes a resource ID, Key and Value and adds the tag | |
#Why this isn't already a function I have no idea | |
Function Add-EC2Tag { | |
param( | |
[string] $resourceID, | |
[string] $Name, | |
[string] $value | |
) | |
$tag = New-Object Amazon.EC2.Model.Tag | |
$tag.Key = $Name | |
$tag.Value = $value | |
New-EC2Tag -Resource $resourceID -Tag $tag | |
} | |
#endregion helper functions# | |
#region main# | |
#Store current default region to change it back if necessary | |
$currentDefaultRegion = (Get-AWSRegion | where{$_.IsShellDefault}).Region | |
#If a region is specified, set it to the default region | |
if($region){ | |
Set-DefaultAWSRegion -Region $region | |
} | |
#Get the instance in question | |
try{ | |
$i = Get-EC2Instance -InstanceId $instanceID | |
} | |
catch{ | |
Write-Error "Instance with ID $instanceID was not found. Error is: $Error[0]" | |
} | |
#For instance, get the attached volumes | |
$vols = $i.Instances[0].BlockDeviceMappings | |
#Create an array for the snaps to be returned as output | |
$snaps = @() | |
foreach($vol in $vols){ | |
#For each volume add tags | |
#instance id, instance name, mount point, instance region | |
$instanceName =$i.Instances[0].Tag.Where({$_.Key -eq "Name"}).Value | |
Add-EC2Tag -resourceID $vol.Ebs.VolumeId -Name "InstanceName" -value $instanceName | |
Add-EC2Tag -resourceID $vol.Ebs.VolumeId -Name "InstanceID" -value $i.Instances[0].InstanceId | |
Add-EC2Tag -resourceID $vol.Ebs.VolumeId -Name "MountPoint" -value $vol.DeviceName | |
if($region){ | |
Add-EC2Tag -resourceID $vol.Ebs.VolumeId -Name "SourceRegion" -value $region | |
} | |
else{ | |
Add-EC2Tag -resourceID $vol.Ebs.VolumeId -Name "SourceRegion" -value $currentDefaultRegion | |
} | |
#if root volume, include subnet-id and VPC-id, and set IsRootVolume tag to true | |
If($i.Instances[0].RootDeviceName -eq $vol.DeviceName){ | |
Add-EC2Tag -resourceID $vol.Ebs.VolumeId -Name "VpcId" -value $i.Instances[0].VpcId | |
Add-EC2Tag -resourceID $vol.Ebs.VolumeId -Name "SubnetId" -value $i.Instances[0].SubnetId | |
Add-EC2Tag -resourceID $vol.Ebs.VolumeId -Name "IsRootVolume" -value $true | |
} | |
else{ | |
Add-EC2Tag -resourceID $vol.Ebs.VolumeId -Name "IsRootVolume" -value $false | |
} | |
#Create a snapshot with description and copy tags from volume | |
$snap = New-EC2Snapshot -Description "Snap of $($vol.Ebs.VolumeId)" -VolumeId $vol.Ebs.VolumeId | |
$tags = Get-EC2Tag -Filter @{Name="resource-id";Values=$vol.Ebs.VolumeId} | |
foreach($tag in $tags){ | |
if($tag.Key -eq "Name"){ | |
Add-EC2Tag -resourceID $snap.SnapshotId -Name "VolumeName" -value $tag.Value | |
} | |
else{ | |
Add-EC2Tag -resourceID $snap.SnapshotId -Name $tag.Key -value $tag.Value | |
} | |
} | |
#Add tag for date and name | |
Add-EC2Tag -resourceID $snap.SnapshotId -Name "Date" -value $snap.StartTime.ToString("MM-dd-yyyy") | |
Add-EC2Tag -resourceID $snap.SnapshotId -Name "Name" -value "Snap-$($vol.Ebs.VolumeId)" | |
#Add snap to array | |
$snaps += $snap | |
} | |
#Output the snapshot IDs | |
Write-Output ($snaps | select SnapshotID) | |
if($region){ | |
Set-DefaultAWSRegion -Region $currentDefaultRegion | |
} | |
#endregion main# |
In a follow up post I will show a script for copying the snapshots over, and an orchestration script to perform the operation for an entire set of instances based on tags or VPC ID.
On HashiCorp, IBM, and Acceptance
March 3, 2025
The Science and Magic of Network Mapping and Measurement
January 9, 2025
January 2, 2025
December 30, 2024