Selectively recovering data in an Aurora PostgreSQL database

Using automated backups and snapshots we can restore an AWS RDS database
– to the most current point in time
– to a prior point in time
– use the restored database going forward
– or restore particular tables

In summary, we have multiple options depending on the nature and extent of the data lost.

In this example, I will demonstrate a method to restore certain rows in one table in the original database.

Create a test table

CREATE TABLE dc_schema.test_table (
                object_id numeric NOT NULL,
                "owner" varchar(128) NULL,
                object_name varchar(128) NULL,
                CONSTRAINT test_table_pk PRIMARY KEY (object_id)
);

and inserted some data. For demonstration purposes, let us assume that the rows with object_id between 30,000 and 40,000 are critical to the application. Further, assume that someone accidentally deleted this data with the following SQL:

select *
from   dc_schema.test_table daac 
where  object_id between 30000 and 40000
;

We now need this data restored as it is critical to the operation of the application.

On the AWS RDS Console page, select the DB cluster, click on “Actions” and then on “Restore to point in time”. On the next page, populate the custom date and time to a point before the data was deleted. Most of the options will be populated except for a few such as the DB instance identifier.

Click on the orange button at the bottom right of the page to begin the recovery. This will take some time to occur.

Some points to keep in mind with respect to the restored database:

– You can use the restored DB instance as soon as its status is available. The DB instance continues to load data in the background. This is known as lazy loading.

– If you access data that hasn’t been loaded yet, the DB instance immediately downloads the requested data from Amazon S3, and then continues loading the rest of the data in the background

– To help mitigate the effects of lazy loading on tables to which you require quick access, you can perform operations that involve full-table scans, such as SELECT *. This allows Amazon RDS to download all of the backed-up table data from S3.

The restore process will create a new instance. While the “restore” is occurring, you can continue to use the original database. To simulate this, I ran a Python program to add another 100,000 rows to the table from which the data was accidentally deleted. The situation of creating keys in the range of the data that was deleted has to be handled by the application staff. In this particular example, I am creating keys higher than the range of keys that were deleted to ensure that there is no primary key collision.

After the restore was completed, I connected to the restored database to check if my data is present. After this was confirmed, I switched back to the original database from where the critical data was deleted and created a link to the recovered database with the following commands:

Create an extension:

CREATE EXTENSION dblink;

Create a dblink:

SELECT dblink_connect
(
  'link_to_recovery’, 
  'dbname=clientdb 
   port=5432  
   host= red-recovery-cluster…….rds.amazonaws.com 
   user=dbadmin
   password=password_here'
);

Execute the following SQL to select the data from the recovered database and insert it into the original database:

insert
into    dc_schema.test_table
SELECT  * 
from    dblink
(
'link_to_recovery', 
'SELECT object_id, owner, object_name 
 FROM   dc_schema.test_table 
 where  object_id between 30000 and 40000'
)
AS 
p(object_id numeric, owner varchar(128), object_name varchar(128))
;

Assuming that you have recovered all the data that was accidentally deleted, you can drop the recovered database. I will do this via AWS CLI commands running in CloudShell.

First, we delete the instance created under the cluster with the command:

aws rds delete-db-instance \
  --db-instance-identifier red-recovery-cluster \
  --skip-final-snapshot \
  --delete-automated-backups

After the instance is deleted, I deleted the cluster with the following command:

aws rds delete-db-cluster \
  --db-cluster-identifier red-recovery-cluster \
  --skip-final-snapshot

Please note that the above commands delete the database along with automated backups and without a final snapshot.

CloudWatch alarms

One of the methods to be alerted on an issue with one of your AWS resources is to use a CloudWatch alert that will send an email (or page) to a subscriber. One issue with this approach is that “An alarm invokes actions only when the alarm changes state.”. This is documented at

Using Amazon CloudWatch alarms

In other words, you will receive one email or page when the event occurs. As an example, if you have an alert that is based on the CPU utilization of the database exceeding a certain threshold, the alert will be sent out once when the database first breaches the threshold. If you have a requirement to be paged every (for example) five minutes while the issue continues, you will need to develop some other methodology. One of the suggestions is to use a combination of services such as Eventbridge and Lambda to keep checking the alarm state.

As an option for DBAs used to handling alerts via scripting, I created a small script (see below) that can be executed via cron on an EC2 server that will monitor the CloudWatch alarm status and keep paging while the alarm is occurring.

#!/bin/bash

#
## alarms that are to be checked
#
alarm_names=("mydb-CPUUtilizationAlarm" "mydb-FreeLocalStorageAlarm")

#
## write out a file header
#
echo 'Report on CloudWatch alarms' > alarm_status.txt
echo 'Executed at ' `date` >> alarm_status.txt
echo ' ' >> alarm_status.txt
printf "%-50s %-10s\n" "Alarm Name" "Status" >> alarm_status.txt
pager_flag='n'

#
## get the status of each alarm
#
for cur_value in "${alarm_names[@]}"
do
    alarm_status=`aws cloudwatch describe-alarms --alarm-names "$cur_value" --output="json" | grep -iE '"StateValue"' | sed 's/StateValue\|"\|\':'\|','//g'`
      alarm_status=$(echo "$alarm_status" | tr -d ' ')
      if  [ "$alarm_status" == "ALARM" ]; then
          pager_flag='y'
      fi
      printf "%-50s %-5s\n" "$cur_value" "$alarm_status" >> alarm_status.txt
done

#
## mail to the appropriate person/team
# 
mail -s "CloudWatch alarm status" dba-group@company.com < alarm_status.txt

#
## check if a page out is required
#
if  [ $pager_flag == 'y' ]; then
    mail -s "CloudWatch alarm status - Alarm detected" dba-pager@company.com < alarm_status.txt
fi

Creating an AWS RDS instance via the command line interface (CLI)

We recently ran into an unusual circumstance working with a customer and were unable to create an RDS instance via the console and ended up creating the instance via the command line. This is a useful command in that we can use it in a script to create multiple instances.

The complete documentation for all options can be found at AWS CLI reference.

Code with explanation of the options

I will attempt to explain each option of the command below. The complete code in one unit is at the bottom of the post.

aws rds create-db-instance \
--db-name MYDB \

The name of the database. This option has different results depending on the database engine you use. This example is for Oracle.

--db-instance-identifier mydb \

The DB instance identifier. This parameter is stored as a lowercase string.

--allocated-storage 300 \

The amount of storage in gibibytes (GiB) to allocate for the DB instance. The constraints on the storage are different depending on the database engine you use.

--db-instance-class db.m5.2xlarge \

The compute and memory capacity of the DB instance. Not all DB instance classes are available in all Amazon Web Services Regions, or for all database engines.

--engine oracle-se2-cdb \

The type of database engine to be used for this instance. In this example, it is Oracle, Standard Edition container database.

--master-username admin \

The account name for the master user.

--master-user-password admin_pwd \

The password for the master user. Please pick a secure password.

--vpc-security-group-ids "sg1" "sg2" \

A list of Amazon EC2 VPC security groups to associate with this DB instance in the format “string 01””String 02”

--db-subnet-group-name “sub_net_1” \

A DB subnet group to associate with this DB instance.

--db-parameter-group-name default.oracle-se2-cdb-19 \

The name of the DB parameter group to associate with this DB instance. If you do not specify a value, then the default DB parameter group for the specified DB engine and version is used.

--backup-retention-period 2 \

The number of days for which automated backups are retained. Setting this parameter to a positive number enables backups. Setting this parameter to 0 disables automated backups.

--port 1521 \

The database port. There are different default values depending on the database engine.

--no-multi-az \

Specifies that the database is not to be deployed in multi-AZ mode. This is a Boolean and can be specified as –multi-az or –no-multi-az

--engine-version 19.0.0.0.ru-2022-04.rur-2022-04.r1 \

The version number of the database engine to use. For a list of valid engine versions, use the DescribeDBEngineVersions operation.

--auto-minor-version-upgrade \

Indicates whether minor engine upgrades are applied automatically to the DB instance during the maintenance window. By default, minor engine upgrades are applied automatically.

--license-model license-included \

License model information for this DB instance.

--option-group-name my_option_group \

Indicates that the DB instance should be associated with the specified option group.

--character-set-name WE8ISO8859P1 \

For supported engines, such as Oracle, this value indicates that the DB instance should be associated with the specified Character Set.

--nchar-character-set-name AL16UTF16 \

The name of the NCHAR character set for the Oracle DB instance.

--no-publicly-accessible \

Whether the DB instance is publicly accessible

--storage-type gp2 \

Specifies the storage type to be associated with the DB instance.

--storage-encrypted \

Specifies that the DB instance storage is encrypted with the below KMS Key

--kms-key-id  \

Specify the ARN of the KMS key to be used to encrypt the storage

--copy-tags-to-snapshot \

Specifies that the tags associated with the database instance will be applied to the snapshots of the database

--no-enable-iam-database-authentication \

A value that indicates whether to enable mapping of Amazon Web Services Identity and Access Management (IAM) accounts to database accounts. IAM database authentication works with MariaDB, MySQL, and PostgreSQL. With this authentication method, you don’t need to use a password when you connect to a DB instance. Instead, you use an authentication token.

--enable-performance-insights \

Whether to enable Performance Insights for the DB instance. Performance Insights expands on existing Amazon RDS monitoring features to illustrate and help you analyze your database performance.

--performance-insights-kms-key-id  \

The ARN of the KMS key used to encrypt your performance insights. If you do not specify a value, then Amazon RDS uses your default KMS key.

--performance-insights-retention-period 7 \

The duration to retain the performance insights data in days.

--deletion-protection \

Specifies that this DB instance has deletion protection enabled.

--max-allocated-storage 500 \

The upper limit in gibibytes (GiB) to which Amazon RDS can automatically scale the storage of the DB instance.

--region us-east-1

The region that the database will be deployed in.

Complete code in one unit

aws rds create-db-instance \
--db-name MYDB \
--db-instance-identifier mydb \
--allocated-storage 300 \
--db-instance-class db.m5.2xlarge \
--engine oracle-se2-cdb \
--master-username admin \
--master-user-password admin_pwd \
--vpc-security-group-ids "sg1" "sg2" \
--db-subnet-group-name “sub_net_1” \
--db-parameter-group-name default.oracle-se2-cdb-19 \
--backup-retention-period 2 \
--port 1521 \
--no-multi-az \
--engine-version 19.0.0.0.ru-2022-04.rur-2022-04.r1 \
--auto-minor-version-upgrade \
--license-model license-included \
--option-group-name my_option_group \
--character-set-name WE8ISO8859P1 \
--nchar-character-set-name AL16UTF16 \
--no-publicly-accessible \
--storage-type gp2 \
--storage-encrypted \
--kms-key-id  \
--copy-tags-to-snapshot \
--no-enable-iam-database-authentication \
--enable-performance-insights \
--performance-insights-kms-key-id  \
--performance-insights-retention-period 7 \
--deletion-protection \
--max-allocated-storage 500 \
--region us-east-1

Oracle Active Data Guard on AWS RDS

On Aug 23, 2022, AWS announced support for managed Oracle Data Guard Switchover and Automated Backups for replicas. See the announcement at:

AWS RDS for Oracle now supports managed Oracle Data Guard

Oracle DBAs are familiar with creating a physical standby in an on-premises environment and opening it in read-only (i.e. Active Data Guard) mode. AWS has now automated the process of creating the standby and the process of switching over in the context of an RDS (i.e. managed database service). All the manual tasks of taking an RMAN backup, transferring to the standby, restoring and recovering the database, setting up Data Guard Manager, etc. can be accomplished by a few clicks on the AWS console. This blog describes the steps required to create and test this scenario.

Prerequisites
1. The database instance size must be db.t3.large at the minimum.
2. The primary database should be using an option group that is exclusive to this database.
3. Ensure you have the required KMS key in both regions
4. Create a parameter group in the target region that exactly matches the parameter group from the source region

Creating a replica
After you have created a primary database in any region:
1. Select the primary database and under the actions button, select “Create replica”.
2. Choose multi-AZ on the target also
3. Click on the create replica button. Replica creation will take approximately 1.5 hours. This may vary based on the size of the database.
4. Change the backup option on the replica. The replica will be created with the backup retention period set to “0” as shown below. This is contrary to the standard practice of not backing up a data guard standby. However, for the replica to switch over to primary at some point in the future, the retention period should be greater than “0”.

Performing a switchover across regions
A switchover is a role reversal between the primary database and one of its standby databases. A switchover guarantees no data loss. This is typically done for planned maintenance of the primary system. During a switchover, the primary database transitions to a standby role, and the standby database transitions to the primary role.

1. Confirm there is no lag. This can be done via the console under the “Replication” section under the “Lag” column or via SQL

SELECT ARCH.thread#                        "Thread",
       ARCH.sequence#                      "Last Sequence Received",
       APPL.sequence#                      "Last Sequence Applied",
       ( ARCH.sequence# - APPL.sequence# ) "Difference"
FROM   (SELECT thread#,
               sequence#
        FROM   v$archived_log
        WHERE  ( thread#, first_time ) IN (SELECT thread#,
                                                  Max(first_time)
                                           FROM   v$archived_log
                                           GROUP  BY thread#)) ARCH,
       (SELECT thread#,
               sequence#
        FROM   v$log_history
        WHERE  ( thread#, first_time ) IN (SELECT thread#,
                                                  Max(first_time)
                                           FROM   v$log_history
                                           GROUP  BY thread#)) APPL
WHERE  ARCH.thread# = APPL.thread#
ORDER  BY 1;

2. Confirm that there is no pending maintenance on either the primary of the replica

3. Initiate the switchover from the replica (not the primary!).

4. Select the RDS instance and click on “Switch over replica”. And agree to the warning panel that is displayed

5. The former replica will display the message “The Switchover to the read replica started” under “Logs & events”, “Recent events” section. (Remember to sort the messages such that the latest messages are displayed.)

6. After the switchover is complete, both databases will display “The Switchover to the read replica finished successfully”

7. Applications will have to reconnect to the databases

8. You can confirm that the data is replicating from the new primary via the console or SQL

Test reboot with multi-AZ failover – Primary
Initiate a reboot with failover on the current primary.

1. Select the RDS instance, under “Actions”, and click on “Reboot”. Check the option “Reboot With Failover?”

2. After about 8 to 10 minutes, the database will complete the multi-AZ failover. Under “Logs & events”, “Recent events” section, look for messages “Multi-AZ instance failover started” and “Multi-AZ instance failover completed”.

A similar test can be done on the Replica.

Observations
1. After a switchover, both the databases may show as Read-Only. This can be ignored

2. You cannot temporarily shut down the database for the customary 7 days as it has a read replica

3. Ensure that there is no pending maintenance before issuing a switchover

4. In the event that you are using a modified character set on your primary, the console may show the replica’s character set as “AL32UTF8”. However, in the database, it matches the primary in the database. This can be confirmed with

select  property_value 
from    DATABASE_properties 
where   property_name='NLS_CHARACTERSET'
;

5. The console will display a message that the switchover is complete. However, this is displayed prematurely. The actual switchover will take additional time. Monitor the status of the two database instances under the “Status” column.

CloudFormation 101 – Part 03

This is part 03 in this series. Previously in

Part 01 – I created a VPC, and internet gateway and attached the internet gateway to the VPC

Part 02 – I added a public and private subnet in one availability zone

In this part, I am adding
– an EIP for the NAT gateway,
– a NAT gateway
– public and private route tables with default routes, and
– associating the route tables with their respective subnets.

The complete (includes all parts 01-03) code is below:

AWSTemplateFormatVersion: '2010-09-09'
#
## The Description section (optional) enables you to include comments about your template.
#
Description:  
  Create VPC, and related components
#
## Parameters section to customize your templates
#
Parameters:
  VPCName:
    Description: Name of the VPC
    Type: String
    Default: "MyVPC"    
    MinLength: '1'
    MaxLength: '30'
    AllowedPattern: '^[a-zA-Z]+[0-9a-zA-Z\-]*$'
    ConstraintDescription: Must contain alphabets and/or numbers.

  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/16    
    MinLength: '10'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.

  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24
    MinLength: '10'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.

  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.3.0/24
    MinLength: '10'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.

#
## Resources created by the stack
#
Resources:
  #
  ## Create the VPC
  ##
  ## Uses the intrinsic function Ref to get the value of the VPC Name
  ## from parameters above
  #
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref VPCName
  #
  ## Create the IGW
  #
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref VPCName
  #
  ## Connect the IGW to the VPC
  #
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  #
  ## Create a public subnet
  ##
  ## The VpcId is obtained by referring back to the VPC created above
  ##
  ## The CIDR block is from the parameters
  ##
  ## The Availability Zone is obtained by querying the available availability
  ## zones in this region and returning the first (offset 0) entry
  ##
  ## The MapPublicIpOnLaunch is set to true indicating that instances launched 
  ## in this subnet receive a public IPv4 address
  #
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet1CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} Public Subnet (AZ1)
  #
  ## Create a private subnet
  ##
  ## The MapPublicIpOnLaunch is set to false indicating that instances launched 
  ## in this subnet will not receive a public IPv4 address
  #
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs  '' ]
      CidrBlock: !Ref PrivateSubnet1CIDR
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} Private Subnet (AZ1)

  #
  ## Create an Elastic IP (EIP) address
  ##
  ## The below section also uses the DependsOn attribute. With the DependsOn 
  ## attribute you can specify that the creation of a specific resource follows 
  ## another. When you add a DependsOn attribute to a resource, that resource is 
  ## created only after the creation of the resource specified in the DependsOn 
  ## attribute. In this case the NAT Gateway is dependent on the prior creation 
  ## of the InternetGatewayAttachment and the attachment to the VPC 
  #
  NatGateway1EIP:
    Type: AWS::EC2::EIP
    DependsOn: InternetGatewayAttachment
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} NatGateway1 EIP

  #
  ## Create a NAT GW
  ##  
  ## Connect the EIP created above to the NAT GW
  ##
  ## Default to public connectivity
  ##
  ## Connecting to the subnet by using the parameter
  #
  NatGateway1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGateway1EIP.AllocationId
      SubnetId: !Ref PublicSubnet1
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} NatGateway 1

  #
  ## Create a public route table
  #
  PublicRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} Public Routes
  #
  ## Add a public route in the above route table to allow the 
  ## subnets to access the internet through the IGW
  #
  DefaultPublicRoute:
    Type: AWS::EC2::Route
    DependsOn: InternetGatewayAttachment
    Properties:
      RouteTableId: !Ref PublicRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId: !Ref InternetGateway
  #
  ## Associate the public route tables with the public subnets
  #
  PublicSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PublicRouteTable1
      SubnetId: !Ref PublicSubnet1

  #
  ## Create a private route table
  #
  PrivateRouteTable1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} Private Routes (AZ1)
  #
  ## Add a route in the above route table to allow the 
  ## subnets to access the internet through the IGW
  #
  DefaultPrivateRoute1:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId: !Ref NatGateway1

  #
  ## Associate the private route tables with the private subnets
  #
  PrivateSubnet1RouteTableAssociation:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref PrivateRouteTable1
      SubnetId: !Ref PrivateSubnet1

#
## Resources created by the stack
##
## Uses the intrinsic function Sub to get the stack name 
## from parameters above and substitute it into the name of
## the internet gateway
#          
Outputs:
  VPC:
    Description: Name of the VPC
    Value: !Ref VPC
    Export:
      Name: !Sub '${AWS::StackName}'

  InternetGateway:
    Description: Internet Gateway 
    Value: !Ref InternetGateway
    Export:
      Name: !Sub '${AWS::StackName}-InternetGateway'

  PublicSubnet1:
    Description: AZ1 - public subnet
    Value: !Ref PublicSubnet1
    Export:
      Name: !Sub '${AWS::StackName}-PublicSubnet1'
      
  PrivateSubnet1:
    Description: AZ1 - private subnet 01
    Value: !Ref PrivateSubnet1
    Export:
      Name: !Sub '${AWS::StackName}-PrivateSubnet1'
      
  NatGateway1EIP:
    Description: NAT Gateway EIP
    Value: !Ref NatGateway1EIP
    Export:
      Name: !Sub '${AWS::StackName}-NatGateway1EIP'
      
  NatGateway1:
    Description: NAT Gateway 1
    Value: !Ref NatGateway1
    Export:
      Name: !Sub '${AWS::StackName}-NatGateway1'

  PublicRouteTable1:
    Description: Public route table
    Value: !Ref PublicRouteTable1
    Export:
      Name: !Sub '${AWS::StackName}-PublicRouteTable1'

  PrivateRouteTable1:
    Description: Private route table - AZ1 - private subnet 01
    Value: !Ref PrivateRouteTable1
    Export:
      Name: !Sub '${AWS::StackName}-PrivateRouteTable1'

CloudFormation – Create and customize a windows server using user data

This blog demonstrates how to

  • create a windows server via CloudFormation
  • create a user directory
  • install the aws cli
  • add the aws cli to the windows path
  • Create a log file to track the output of the commands executed in the user data section
  • Download a file from an s3 bucket

By using these commands, you can download and install software after the Windows instance is created.

The IAM profile assigned to the EC2 instance should have access to the s3 bucket used.

The code is below:

AWSTemplateFormatVersion: 2010-09-09
Description: Innovate - Create App and Web Servers
Resources:
  rMyBastionInstance:
    Type: 'AWS::EC2::Instance'
    Properties:
      ImageId: 'ami-I-want-to-use'
      KeyName: 'my_windows_key'
      IamInstanceProfile: 'My-IAM-profile'
      InstanceType: 'm4.2xlarge'
      SecurityGroupIds: 
        - sg-security-group-id
      SubnetId: 'my-subnet-id'
      BlockDeviceMappings:
        - DeviceName: /dev/sda1
          Ebs:
                VolumeType: gp2
                DeleteOnTermination: true
                VolumeSize: 100
                Encrypted: true
      UserData:
        'Fn::Base64': !Sub |
          <script>
              cd \
              mkdir tempdc
              msiexec.exe /i https://awscli.amazonaws.com/AWSCLIV2.msi /qn
              cd \tempdc
              echo Current date and time > c:\tempdc\s3_output.txt
              echo %DATE% %TIME% >> c:\tempdc\s3_output.txt
              SET PATH=%PATH%;C:\Program Files\Amazon\AWSCLIV2
              echo %PATH% >> c:\tempdc\s3_output.txt
              aws s3 cp s3://mybucket/my-file.exe c:\tempdc\my-file.exe >> c:\tempdc\s3_output.txt
          </script>
      Tags:
        - Key: Name
          Value: MyWindowsInstance

CloudFormation 101 – Part 02

In the previous blog, I had created a VPC and internet gateway and attached the internet gateway to the VPC. I will add a public and private subnet in one availability zone in this post.

Some definitions:
Subnets
A subnet is a range of IP addresses in your VPC. You can launch AWS resources into a specified subnet.

Public subnet
A public subnet contains resources that will be connected from and to the internet—for example, load balancers, web servers, etc.

Private subnet
A private subnet contains resources that will not be accessed directly from the internet—for example, application servers, databases, etc.

It is critical to place your resources in the appropriate subnet. Placing resources with sensitive code or information in a public subnet would open them up to compromise by bad actors, of which there are many on the internet 😊.

The code is below:

AWSTemplateFormatVersion: '2010-09-09'
#
## The Description section (optional) enables you to include comments about your template.
#
Description:  
  Create VPC, an internet gateway, and attach the internet gateway to the VPC
#
## Parameters section to customize your templates
#
Parameters:
  VPCName:
    Description: Name of the VPC
    Type: String
    Default: "MyVPC"    
    MinLength: '1'
    MaxLength: '30'
    AllowedPattern: '^[a-zA-Z]+[0-9a-zA-Z\-]*$'
    ConstraintDescription: Must contain alphabets and/or numbers.

  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/16    
    MinLength: '10'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.

  PublicSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the public subnet in the first Availability Zone
    Type: String
    Default: 10.0.1.0/24
    MinLength: '10'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.

  PrivateSubnet1CIDR:
    Description: Please enter the IP range (CIDR notation) for the private subnet in the first Availability Zone
    Type: String
    Default: 10.0.3.0/24
    MinLength: '10'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.

#
## Resources created by the stack
#
Resources:
  #
  ## Create the VPC
  ##
  ## Uses the intrinsic function Ref to get the value of the VPC Name
  ## from parameters above
  #
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref VPCName
  #
  ## Create the IGW
  #
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref VPCName
  #
  ## Connect the IGW to the VPC
  #
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC
  #
  ## Create a public subnet
  ##
  ## The VpcId is obtained by referring back to the VPC created above
  ##
  ## The CIDR block is from the parameters
  ##
  ## The Availability Zone is obtained by querying the available availability
  ## zones in this region and returning the first (offset 0) entry
  ##
  ## The MapPublicIpOnLaunch is set to true indicating that instances launched 
  ## in this subnet receive a public IPv4 address
  #
  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs '' ]
      CidrBlock: !Ref PublicSubnet1CIDR
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} Public Subnet (AZ1)
  #
  ## Create a private subnet
  ##
  ## The MapPublicIpOnLaunch is set to false, indicating that instances launched 
  ## in this subnet will not receive a public IPv4 address
  #
  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      VpcId: !Ref VPC
      AvailabilityZone: !Select [ 0, !GetAZs  '' ]
      CidrBlock: !Ref PrivateSubnet1CIDR
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: !Sub ${VPCName} Private Subnet (AZ1)

#
## Resources created by the stack
##
## Uses the intrinsic function Sub to get the stack name 
## from parameters above and substitute it into the name of
## the internet gateway
#          
Outputs:
  VPC:
    Description: Name of the VPC
    Value: !Ref VPC
    Export:
      Name: !Sub '${AWS::StackName}'

  InternetGateway:
    Description: Internet Gateway 
    Value: !Ref InternetGateway
    Export:
      Name: !Sub '${AWS::StackName}-InternetGateway'

  PublicSubnet1:
    Description: AZ1 - public subnet
    Value: !Ref PublicSubnet1
    Export:
      Name: !Sub '${AWS::StackName}-PublicSubnet1'
      
  PrivateSubnet1:
    Description: AZ1 - private subnet 01
    Value: !Ref PrivateSubnet1
    Export:
      Name: !Sub '${AWS::StackName}-PrivateSubnet1'

CloudFormation 101 – Part 01

Some quick blogs on using CloudFormation to provision AWS infrastructure using code. I have attempted to put descriptive comments in line to explain what each sub-unit of code is doing.

This YAML code creates a VPC, an internet gateway, and attaches the gateway to the VPC.

Some definitions:
VPC
Amazon Virtual Private Cloud (Amazon VPC) enables you to launch AWS resources into a virtual network that you’ve defined. This virtual network closely resembles a traditional network that you’d operate in your own data center, with the benefits of using the scalable infrastructure of AWS.

Internet Gateway
An internet gateway is a horizontally scaled, redundant, and highly available VPC component that allows communication between your VPC and the internet.

VPC Gateway Attachment
Attaches an internet gateway, or a virtual private gateway to a VPC, enabling connectivity between the internet and the VPC.

The code is below:

AWSTemplateFormatVersion: '2010-09-09'
#
## The Description section (optional) enables you to include comments about your template.
#
Description:  
  Create VPC, an internet gateway, and attach the internet gateway to the VPC
#
## Parameters section to customize your templates
#
Parameters:
  VPCName:
    Description: Name of the VPC
    Type: String
    Default: "MyVPC"    
    MinLength: '1'
    MaxLength: '30'
    AllowedPattern: '^[a-zA-Z]+[0-9a-zA-Z\-]*$'
    ConstraintDescription: Must contain alphabets and/or numbers.

  VpcCIDR:
    Description: Please enter the IP range (CIDR notation) for this VPC
    Type: String
    Default: 10.0.0.0/16    
    MinLength: '10'
    MaxLength: '18'
    AllowedPattern: "(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})\\.(\\d{1,3})/(\\d{1,2})"
    ConstraintDescription: Must be a valid CIDR range of the form x.x.x.x/x.

#
## Resources created by the stack
#
Resources:
  #
  ## Create the VPC
  ##
  ## Uses the intrinsic function Ref to get the value of the VPC Name
  ## from parameters above
  #
  VPC:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: !Ref VpcCIDR
      EnableDnsSupport: true
      EnableDnsHostnames: true
      Tags:
        - Key: Name
          Value: !Ref VPCName
  #
  ## Create the IGW
  #
  InternetGateway:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: !Ref VPCName
  #
  ## Connect the IGW to the VPC
  #
  InternetGatewayAttachment:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      InternetGatewayId: !Ref InternetGateway
      VpcId: !Ref VPC

#
## Resources created by the stack
##
## Uses the intrinsic function Sub to get the stack name 
## from parameters above and substitute it into the name of
## the internet gateway
#          
Outputs:
  VPC:
    Description: Name of the VPC
    Value: !Ref VPC
    Export:
      Name: !Sub '${AWS::StackName}'

  InternetGateway:
    Description: Internet Gateway 
    Value: !Ref InternetGateway
    Export:
      Name: !Sub '${AWS::StackName}-InternetGateway'
      

Creating an EC2 in AWS via Cloud Formation

This blog post explains how to create (“spin up” in the cool lingo 🙂 ) an EC2 in AWS using Cloud Formation. I will attempt to explain each line or section of code before the code itself. The complete cloud formation template is available at the end of this post. This template is written in JSON. It could also have been done in YAML.

This is the opening curly brace. Just make sure you have one at the end to close it out. Strongly suggest using a JSON formatter and syntax checker.

{

These lines identify the AWS template format version and a description tag for documentation purposes

    "AWSTemplateFormatVersion":"2010-09-09",
    "Description":"Linux EC2 to test with",

The parameters section contains the parameters that will be passed from my Jenkins job to this cloud formation template. Each of these can have a type, default value, and description.

    "Parameters":{
        "CFstackname":{
            "Type":"String"
        },
        "AppID":{
            "Type":"String",
            "Default":"ABC",
            "Description":"Name of the application. Default=ABC"
        },
        "Role":{
            "Type":"String",
            "Default":"App",
            "Description":"Enter the Role name. Default=App"
        },
        "UserID":{
            "Type":"String",
            "Description":"Enter the userid of the owner(s)"
        }
    },

This next section specifies the resources that are to be created by this cloud formation template.

    "Resources":{

The type of the resource to be created is an EC2 instance

        "EC2Instance":{
            "Type":"AWS::EC2::Instance",

This section contains the properties to describe the resource.

            "Properties":{

This parameter specifies the instance type which in this case is a t3 small

                "InstanceType":"t3.small",

The DisableApiTermination attribute controls whether the instance can be terminated using the console, CLI, or API. While writing this blog post I did learn that the DisableApiTermination attribute does not prevent Amazon EC2 Auto Scaling from terminating an instance. Just a useful piece of information.

                "DisableApiTermination":"false",

This specifies the network interfaces. The AssociatePublicIpAddress is set to false in this case. The subnet ID is your subnet i.e. logical subdivision of an IP network. The group set contains the security groups that you want to be attached to this EC2 instance. These need to be predefined.

                "NetworkInterfaces":[
                    {
                        "AssociatePublicIpAddress":"false",
                        "DeviceIndex":"0",
                        "SubnetId":"my-subnet-01",
                        "GroupSet":[
                            "sg-for-me-01",
                            "sg-for-me-02"
                        ]
                    }
                ],

This next parameter specifies the Amazon machine image (AMI) that you wish to use to create this instance. In my case, I had a pre-created AMI that I was using as it was already encrypted with my KMS key

                "ImageId":"ami-dean-01",

Here I’m specifying the block devices (disks) to be associated with this machine. The volume size is specified in gigabytes. Storage is deleted when the EC2 is terminated. The disk is to be encrypted with the KMS key specified

                "BlockDeviceMappings":[
                    {
                        "DeviceName":"/dev/sda1",
                        "Ebs":{
                            "VolumeSize":"100",
                            "DeleteOnTermination":true,
                            "VolumeType":"gp2",
                            "Encrypted":"true",
                            "KmsKeyId":"my magical KMS key"
                        }
                    }
                ],

These are the tags that are to be associated with this EC2. Some of them are from the parameters of the top of the template. This is a free form to the extent that you can specify any tag string as the key and any value as the value.

                "Tags":[
                    {
                        "Key":"UserID",
                        "Value":"xyz01"
                    },
                    {
                        "Key":"Name",
                        "Value":"My Linux Machine"
                    },
                    {
                        "Key":"Role",
                        "Value":"App"
                    }

Close all the curly braces or else bad things will happen 🙂

 
                ]
            }
        }
    },

This section is the outputs that I am expecting. The first is the instance id, followed by the Availability Zone (AZ) and the private IP

    "Outputs":{
        "InstanceId":{
            "Description":"InstanceId of the newly created EC2 instance",
            "Value":{
                "Ref":"EC2Instance"
            }
        },
        "AZ":{
            "Description":"Availability Zone of the newly created EC2 instance",
            "Value":{
                "Fn::GetAtt":[
                    "EC2Instance",
                    "AvailabilityZone"
                ]
            }
        },
        "PrivateIP":{
            "Description":"PrivateIP of the newly created EC2 instance",
            "Value":{
                "Fn::GetAtt":[
                    "EC2Instance",
                    "PrivateIp"
                ]
            }
        }
    }
}

Complete cloud formation template

{
    "AWSTemplateFormatVersion":"2010-09-09",
    "Description":"Linux EC2 to test with",
    "Parameters":{
        "CFstackname":{
            "Type":"String"
        },
        "AppID":{
            "Type":"String",
            "Default":"ABC",
            "Description":"Name of the application. Default=ABC"
        },
        "Role":{
            "Type":"String",
            "Default":"App",
            "Description":"Enter the Role name. Default=App"
        },
        "UserID":{
            "Type":"String",
            "Description":"Enter the userid of the owner(s)"
        }
    },
    "Resources":{
        "EC2Instance":{
            "Type":"AWS::EC2::Instance",
            "Properties":{
                "InstanceType":"t3.small",
                "DisableApiTermination":"false",
                "NetworkInterfaces":[
                    {
                        "AssociatePublicIpAddress":"false",
                        "DeviceIndex":"0",
                        "SubnetId":"my-subnet-01",
                        "GroupSet":[
                            "sg-for-me-01",
                            "sg-for-me-02"
                        ]
                    }
                ],
                "ImageId":"ami-dean-01",
                "BlockDeviceMappings":[
                    {
                        "DeviceName":"/dev/sda1",
                        "Ebs":{
                            "VolumeSize":"100",
                            "DeleteOnTermination":true,
                            "VolumeType":"gp2",
                            "Encrypted":"true",
                            "KmsKeyId":"my magical KMS key"
                        }
                    }
                ],
                "Tags":[
                    {
                        "Key":"UserID",
                        "Value":"xyz01"
                    },
                    {
                        "Key":"Name",
                        "Value":"My Linux Machine"
                    },
                    {
                        "Key":"Role",
                        "Value":"App"
                    }
 
                ]
            }
        }
    },
    "Outputs":{
        "InstanceId":{
            "Description":"InstanceId of the newly created EC2 instance",
            "Value":{
                "Ref":"EC2Instance"
            }
        },
        "AZ":{
            "Description":"Availability Zone of the newly created EC2 instance",
            "Value":{
                "Fn::GetAtt":[
                    "EC2Instance",
                    "AvailabilityZone"
                ]
            }
        },
        "PrivateIP":{
            "Description":"PrivateIP of the newly created EC2 instance",
            "Value":{
                "Fn::GetAtt":[
                    "EC2Instance",
                    "PrivateIp"
                ]
            }
        }
    }
}

Copying a file from a Linux on-prem server to an AWS RDS Oracle instance

Today we had a requirement to copy a file from a Linux on-prem server to an AWS RDS Oracle instance. We did this using a DB link and utl_file utility. For the rest of this post, “ONP” will refer to the on-prem instance, and “ADS” will refer to the AWS RDS Oracle instance.

On the AWS RDS Oracle instance, create a DB link to the on-prem instance

create database link AWS_TO_ON_PREM CONNECT TO ONPUSER
IDENTIFIED BY SECRET-PASSWORD
USING '(DESCRIPTION =
    (ADDRESS_LIST =
      (ADDRESS = (PROTOCOL = TCP)(HOST = ONPREM.HOST.COM)(PORT = 1776))
    )
    (CONNECT_DATA =
      (SERVICE_NAME = ONPDB)
    )
  )'
   ;

On the AWS RDS Oracle instance, create a directory to store the file

exec rdsadmin.rdsadmin_util.create_directory('ADS_DIRECTORY');

Check that the directory got created

Select * from dba_directories;

As AWS manages the path internally, the above SQL will return the actual directory path.

On the on-prem instance, create a directory pointing to the location of the file that you want to copy to AWS

CREATE OR REPLACE DIRECTORY
FOR_AWS AS '/onprem/host/file/location/file_for_aws.bin';

GRANT READ, WRITE ON DIRECTORY FOR_AWS TO ONPUSER;

Now to copy the file from the on-prem instance to the AWS instance. This is accomplished via the below PL/SQL which is to be executed while connected to the AWS RDS Oracle instance. In our case, the data was binary (raw) data

declare
  ONP    utl_file.file_type@AWS_TO_ON_PREM;
  ADS    utl_file.file_type;
  ldata  raw(32767);
begin
  ONP := utl_file.fopen@AWS_TO_ON_PREM(location=>'FOR_ADS', filename=>'file_for_aws.bin', open_mode=>'rb');
  ADS := utl_file.fopen(location=>'ADS_DIRECTORY', filename=>'file_for_aws.bin', open_mode=>'wb');
  begin
    loop
      begin
        utl_file.get_raw@AWS_TO_ON_PREM(ONP, ldata, 32767);
        utl_file.put_raw(ADS, ldata, true);
      exception
        when no_data_found then
          exit;
      end;
    end loop;
  end;
  utl_file.fclose@AWS_TO_ON_PREM (ONP);
  utl_file.fclose(ADS);  
exception
  when others then
    utl_file.fclose@AWS_TO_ON_PREM(ONP);
    utl_file.fclose(ADS);
    raise;
end;
/

After the above is complete, check that the file size in AWS matches the file size from on-prem with this sql

select    filesize, mtime 
from      table(rdsadmin.rds_file_util.listdir('ADS_DIRECTORY')) 
where     filename='file_for_aws.bin'
;

If you need to delete a file in AWS, you can use this command

exec utl_file.fremove('directory-name','file-name');
142
51
73
77