I deployed my first AWS Cloud Development Kit app. Here’s what I learned.

Subscribe to my newsletter and never miss my upcoming articles

I'm doing the #AWSCertified challenge introduced by freeCodeCamp and I decided to write about my journey. I'm preparing now the AWS Certified Developer Associate exam with the course developed by Andrew Brown of ExamPro. To pass the certification, I decided to experiment more and share with you what I learn and also the pain points I encounter during the process.

During the past few days, I decided to explore AWS Cloud Development Kit (CDK) and feel the difference with AWS CloudFormation.

My objective was to rewrite partially the VPC Follow Along with the CDK and use mostly command lines.

Prerequisites

If you want to follow along and run the commands below, you simply need to create a Cloud9 environment. AWS already installed the CDK for us and we can start right away.

Initialize the app

In your Cloud9 terminal, type the following commands:

mkdir helloworld
cd helloworld
cdk init --language javascript

It will create a new folder for your app (e.g. helloworld) and initialize the CDK with one of the supported languages: C#, F#, Java, Python, JavaScript, or TypeScript.

Install dependencies

The initialized app only has the CDK core package installed. Other AWS services are in their own packages as defined in the API reference.

Because the objective is to create a Virtual Private Cloud (VPC) and define an Elastic Compute Cloud (EC2) within, you need to install @aws-sdk/aws-ec2 package by typing the following command in your terminal:

npm install @aws-cdk/aws-ec2

Implement the logic

Open lib/helloworld-stack.js file and be ready to write some code!

There are 4 steps to deploy an EC2 instance as in the VPC Follow Along:

  1. Create a VPC
  2. Create a Security Group (SG) and define an HTTP ingress rule
  3. Select an Amazon Machine Image (AMI) and create the EC2 instance
  4. Add a script to the EC2 instance

But first, include the package installed previously at the top of lib/helloworld-stack.js before continuing:

const ec2 = require('@aws-cdk/aws-ec2');

Create a Virtual Private Cloud

To define a VPC with the CDK, you have to write the following lines of code in the constructor:

const vpc = new ec2.Vpc(this, 'Vpc', {
  cidr: '10.0.0.0/16',
  subnetConfiguration: [
    {
      cidrMask: 24,
      name: 'Public1',
      subnetType: ec2.SubnetType.PUBLIC,
    },
    {
      cidrMask: 24,
      name: 'Public2',
      subnetType: ec2.SubnetType.PUBLIC,
    },
    {
      cidrMask: 24,
      name: 'Public3',
      subnetType: ec2.SubnetType.PUBLIC,
    },
    {
      cidrMask: 24,
      name: 'Private1',
      subnetType: ec2.SubnetType.PRIVATE,
    },
  ],
});

This will create a VPC with public and private subnets and spread over at most 2 or 3 Availability Zones.

Create a Security Group and define an HTTP ingress rule

Let's add an HTTP rule for the future EC2 instance now that a VPC is defined:

const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc });
securityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(80),
  'Allow HTTP access from the world'
);

Select an Amazon Machine Image and create the EC2 instance

Before creating the EC2 instance within the VPC, you need to select which AMI you want to use:

const machineImage = new ec2.AmazonLinuxImage({
  generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
});

const instance = new ec2.Instance(this, 'Instance', {
  instanceType: 't2.micro',
  machineImage,
  vpc,
  vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
  securityGroup,
});

You need to explicitly define that you want to place your EC2 instance in a public subnet. By default, the CDK places all the instances in a private subnet.

Add a script to the EC2 instance

Now the EC2 instance is defined, add the following script to have an HTML page to display after the deployment:

instance.addUserData(`
  #!/usr/bin/env bash
  su ec2-user
  sudo yum install httpd -y
  sudo service httpd start
  sudo su -c "cat > /var/www/html/index.html <<EOL
  <html>
    <head>
      <title>Call to Arms</title>
      <style>
        html, body { background: #000; padding: 0; margin: 0; }
        img { display: block; margin: 0px auto; }
      </style>
    </head>
    <body>
      <img src='https://media.giphy.com/media/10YoCxWqM3NHxK/giphy.gif' height='100%'/>
    </body>
  </html>
  EOL"
`);

Deploy the app

Before deploying your app, you can define the Public DNS as output to directly access the EC2 instance:

new cdk.CfnOutput(this, 'Public DNS (IPv4)', {
  value: instance.instancePublicDnsName,
});

You should normally have this result:

const cdk = require('@aws-cdk/core');
const ec2 = require('@aws-cdk/aws-ec2');

class HelloworldStack extends cdk.Stack {
  /**
   *
   * @param {cdk.Construct} scope
   * @param {string} id
   * @param {cdk.StackProps=} props
   */
  constructor(scope, id, props) {
    super(scope, id, props);

    // Create a VPC
    const vpc = new ec2.Vpc(this, 'Vpc', {
      cidr: '10.0.0.0/16',
      subnetConfiguration: [
        {
          cidrMask: 24,
          name: 'Public1',
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: 'Public2',
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: 'Public3',
          subnetType: ec2.SubnetType.PUBLIC,
        },
        {
          cidrMask: 24,
          name: 'Private1',
          subnetType: ec2.SubnetType.PRIVATE,
        },
      ],
    });

    // Create a SG and define a HTTP ingress rule
    const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc });
    securityGroup.addIngressRule(
      ec2.Peer.anyIpv4(),
      ec2.Port.tcp(80),
      'Allow HTTP access from the world'
    );

    // Select an Amazon Machine Image (AMI)
    const machineImage = new ec2.AmazonLinuxImage({
      generation: ec2.AmazonLinuxGeneration.AMAZON_LINUX_2,
    });

    // Create the EC2 instance
    const instance = new ec2.Instance(this, 'Instance', {
      instanceType: 't2.micro',
      machineImage,
      vpc,
      vpcSubnets: { subnetType: ec2.SubnetType.PUBLIC },
      securityGroup,
    });

    // Add a script to the EC2 instance
    instance.addUserData(`
      #!/usr/bin/env bash
      su ec2-user
      sudo yum install httpd -y
      sudo service httpd start
      sudo su -c "cat > /var/www/html/index.html <<EOL
      <html>
        <head>
          <title>Call to Arms</title>
          <style>
            html, body { background: #000; padding: 0; margin: 0; }
            img { display: block; margin: 0px auto; }
          </style>
        </head>
        <body>
          <img src='https://media.giphy.com/media/10YoCxWqM3NHxK/giphy.gif' height='100%'/>
        </body>
      </html>
      EOL"
    `);

    // Define the Public DNS as output
    new cdk.CfnOutput(this, 'Public DNS (IPv4)', {
      value: instance.instancePublicDnsName,
    });
  }
}

module.exports = { HelloworldStack }

Now, you can deploy your app by typing the following command in your terminal:

cdk deploy

This command will transpile your code into CloudFormation template and then invoke CloudFormation to deploy your stack in your AWS account.

Once deployed, copy the PublicDNSIPv4 displayed in your terminal and paste the URL in your favorite browser. You should have this result:

Once the app is deployed

Let's say you want to add a new SSH rule in your SG:

const securityGroup = new ec2.SecurityGroup(this, 'SecurityGroup', { vpc });
securityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(80),
  'Allow HTTP access from the world'
);

// Define a new SSH ingress rule
securityGroup.addIngressRule(
  ec2.Peer.anyIpv4(),
  ec2.Port.tcp(22),
  'Allow SSH access from the world'
);

Type the following command in the terminal to see how the new code impacts the deployed stack:

cdk diff

After that, you can redeploy your app to apply the changes:

cdk deploy

If you're curious about what your CloudFormation template looks like, you can type the following command in the terminal and open helloworld.yaml file:

cdk synth > helloworld.yaml

Your file should look like that:

Resources:
  Vpc8378EB38:
    Type: AWS::EC2::VPC
    Properties:
      CidrBlock: 10.0.0.0/16
      EnableDnsHostnames: true
      EnableDnsSupport: true
      InstanceTenancy: default
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Resource
  VpcPublic1Subnet1Subnet822C42F4:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/24
      VpcId:
        Ref: Vpc8378EB38
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public1Subnet1
        - Key: aws-cdk:subnet-name
          Value: Public1
        - Key: aws-cdk:subnet-type
          Value: Public
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet1/Subnet
  VpcPublic1Subnet1RouteTable656E2024:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public1Subnet1
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet1/RouteTable
  VpcPublic1Subnet1RouteTableAssociation5CE153E6:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VpcPublic1Subnet1RouteTable656E2024
      SubnetId:
        Ref: VpcPublic1Subnet1Subnet822C42F4
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet1/RouteTableAssociation
  VpcPublic1Subnet1DefaultRouteD855846C:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: VpcPublic1Subnet1RouteTable656E2024
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: VpcIGWD7BA715C
    DependsOn:
      - VpcVPCGWBF912B6E
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet1/DefaultRoute
  VpcPublic1Subnet1EIP6E1EA980:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public1Subnet1
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet1/EIP
  VpcPublic1Subnet1NATGatewayF6E55728:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - VpcPublic1Subnet1EIP6E1EA980
          - AllocationId
      SubnetId:
        Ref: VpcPublic1Subnet1Subnet822C42F4
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public1Subnet1
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet1/NATGateway
  VpcPublic1Subnet2Subnet47603E66:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.1.0/24
      VpcId:
        Ref: Vpc8378EB38
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public1Subnet2
        - Key: aws-cdk:subnet-name
          Value: Public1
        - Key: aws-cdk:subnet-type
          Value: Public
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet2/Subnet
  VpcPublic1Subnet2RouteTable99F73B51:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public1Subnet2
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet2/RouteTable
  VpcPublic1Subnet2RouteTableAssociationC9119526:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VpcPublic1Subnet2RouteTable99F73B51
      SubnetId:
        Ref: VpcPublic1Subnet2Subnet47603E66
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet2/RouteTableAssociation
  VpcPublic1Subnet2DefaultRoute73AD7054:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: VpcPublic1Subnet2RouteTable99F73B51
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: VpcIGWD7BA715C
    DependsOn:
      - VpcVPCGWBF912B6E
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet2/DefaultRoute
  VpcPublic1Subnet2EIP11CAB41D:
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public1Subnet2
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet2/EIP
  VpcPublic1Subnet2NATGateway62B6ED4D:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId:
        Fn::GetAtt:
          - VpcPublic1Subnet2EIP11CAB41D
          - AllocationId
      SubnetId:
        Ref: VpcPublic1Subnet2Subnet47603E66
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public1Subnet2
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public1Subnet2/NATGateway
  VpcPublic2Subnet1Subnet328F1B3A:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.2.0/24
      VpcId:
        Ref: Vpc8378EB38
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public2Subnet1
        - Key: aws-cdk:subnet-name
          Value: Public2
        - Key: aws-cdk:subnet-type
          Value: Public
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public2Subnet1/Subnet
  VpcPublic2Subnet1RouteTable6E564D85:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public2Subnet1
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public2Subnet1/RouteTable
  VpcPublic2Subnet1RouteTableAssociationE69C1E44:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VpcPublic2Subnet1RouteTable6E564D85
      SubnetId:
        Ref: VpcPublic2Subnet1Subnet328F1B3A
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public2Subnet1/RouteTableAssociation
  VpcPublic2Subnet1DefaultRouteF93D1CEC:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: VpcPublic2Subnet1RouteTable6E564D85
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: VpcIGWD7BA715C
    DependsOn:
      - VpcVPCGWBF912B6E
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public2Subnet1/DefaultRoute
  VpcPublic2Subnet2Subnet53582EBF:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.3.0/24
      VpcId:
        Ref: Vpc8378EB38
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public2Subnet2
        - Key: aws-cdk:subnet-name
          Value: Public2
        - Key: aws-cdk:subnet-type
          Value: Public
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public2Subnet2/Subnet
  VpcPublic2Subnet2RouteTableD2EADE87:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public2Subnet2
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public2Subnet2/RouteTable
  VpcPublic2Subnet2RouteTableAssociation1F5A30A9:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VpcPublic2Subnet2RouteTableD2EADE87
      SubnetId:
        Ref: VpcPublic2Subnet2Subnet53582EBF
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public2Subnet2/RouteTableAssociation
  VpcPublic2Subnet2DefaultRouteD5AB6747:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: VpcPublic2Subnet2RouteTableD2EADE87
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: VpcIGWD7BA715C
    DependsOn:
      - VpcVPCGWBF912B6E
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public2Subnet2/DefaultRoute
  VpcPublic3Subnet1SubnetD73BC551:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.4.0/24
      VpcId:
        Ref: Vpc8378EB38
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public3Subnet1
        - Key: aws-cdk:subnet-name
          Value: Public3
        - Key: aws-cdk:subnet-type
          Value: Public
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public3Subnet1/Subnet
  VpcPublic3Subnet1RouteTable80FFB865:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public3Subnet1
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public3Subnet1/RouteTable
  VpcPublic3Subnet1RouteTableAssociationDB54BAEA:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VpcPublic3Subnet1RouteTable80FFB865
      SubnetId:
        Ref: VpcPublic3Subnet1SubnetD73BC551
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public3Subnet1/RouteTableAssociation
  VpcPublic3Subnet1DefaultRoute7988DE43:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: VpcPublic3Subnet1RouteTable80FFB865
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: VpcIGWD7BA715C
    DependsOn:
      - VpcVPCGWBF912B6E
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public3Subnet1/DefaultRoute
  VpcPublic3Subnet2Subnet564C2EA5:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.5.0/24
      VpcId:
        Ref: Vpc8378EB38
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: true
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public3Subnet2
        - Key: aws-cdk:subnet-name
          Value: Public3
        - Key: aws-cdk:subnet-type
          Value: Public
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public3Subnet2/Subnet
  VpcPublic3Subnet2RouteTableD99359F2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Public3Subnet2
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public3Subnet2/RouteTable
  VpcPublic3Subnet2RouteTableAssociationA56AE7BD:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VpcPublic3Subnet2RouteTableD99359F2
      SubnetId:
        Ref: VpcPublic3Subnet2Subnet564C2EA5
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public3Subnet2/RouteTableAssociation
  VpcPublic3Subnet2DefaultRouteAA02C250:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: VpcPublic3Subnet2RouteTableD99359F2
      DestinationCidrBlock: 0.0.0.0/0
      GatewayId:
        Ref: VpcIGWD7BA715C
    DependsOn:
      - VpcVPCGWBF912B6E
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Public3Subnet2/DefaultRoute
  VpcPrivate1Subnet1SubnetC688B2B1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.6.0/24
      VpcId:
        Ref: Vpc8378EB38
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Private1Subnet1
        - Key: aws-cdk:subnet-name
          Value: Private1
        - Key: aws-cdk:subnet-type
          Value: Private
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Private1Subnet1/Subnet
  VpcPrivate1Subnet1RouteTable63B93D7A:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Private1Subnet1
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Private1Subnet1/RouteTable
  VpcPrivate1Subnet1RouteTableAssociation97501102:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VpcPrivate1Subnet1RouteTable63B93D7A
      SubnetId:
        Ref: VpcPrivate1Subnet1SubnetC688B2B1
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Private1Subnet1/RouteTableAssociation
  VpcPrivate1Subnet1DefaultRouteF2E75A1D:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: VpcPrivate1Subnet1RouteTable63B93D7A
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId:
        Ref: VpcPublic1Subnet1NATGatewayF6E55728
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Private1Subnet1/DefaultRoute
  VpcPrivate1Subnet2SubnetA2AF15C7:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.7.0/24
      VpcId:
        Ref: Vpc8378EB38
      AvailabilityZone:
        Fn::Select:
          - 1
          - Fn::GetAZs: ""
      MapPublicIpOnLaunch: false
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Private1Subnet2
        - Key: aws-cdk:subnet-name
          Value: Private1
        - Key: aws-cdk:subnet-type
          Value: Private
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Private1Subnet2/Subnet
  VpcPrivate1Subnet2RouteTable695199F8:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc/Private1Subnet2
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Private1Subnet2/RouteTable
  VpcPrivate1Subnet2RouteTableAssociation24F600FF:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId:
        Ref: VpcPrivate1Subnet2RouteTable695199F8
      SubnetId:
        Ref: VpcPrivate1Subnet2SubnetA2AF15C7
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Private1Subnet2/RouteTableAssociation
  VpcPrivate1Subnet2DefaultRouteD86AEB1B:
    Type: AWS::EC2::Route
    Properties:
      RouteTableId:
        Ref: VpcPrivate1Subnet2RouteTable695199F8
      DestinationCidrBlock: 0.0.0.0/0
      NatGatewayId:
        Ref: VpcPublic1Subnet2NATGateway62B6ED4D
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/Private1Subnet2/DefaultRoute
  VpcIGWD7BA715C:
    Type: AWS::EC2::InternetGateway
    Properties:
      Tags:
        - Key: Name
          Value: HelloworldStack/Vpc
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/IGW
  VpcVPCGWBF912B6E:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId:
        Ref: Vpc8378EB38
      InternetGatewayId:
        Ref: VpcIGWD7BA715C
    Metadata:
      aws:cdk:path: HelloworldStack/Vpc/VPCGW
  SecurityGroupDD263621:
    Type: AWS::EC2::SecurityGroup
    Properties:
      GroupDescription: HelloworldStack/SecurityGroup
      SecurityGroupEgress:
        - CidrIp: 0.0.0.0/0
          Description: Allow all outbound traffic by default
          IpProtocol: "-1"
      SecurityGroupIngress:
        - CidrIp: 0.0.0.0/0
          Description: Allow HTTP access from the world
          FromPort: 80
          IpProtocol: tcp
          ToPort: 80
      VpcId:
        Ref: Vpc8378EB38
    Metadata:
      aws:cdk:path: HelloworldStack/SecurityGroup/Resource
  InstanceInstanceRoleE9785DE5:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
          - Action: sts:AssumeRole
            Effect: Allow
            Principal:
              Service:
                Fn::Join:
                  - ""
                  - - ec2.
                    - Ref: AWS::URLSuffix
        Version: "2012-10-17"
      Tags:
        - Key: Name
          Value: HelloworldStack/Instance
    Metadata:
      aws:cdk:path: HelloworldStack/Instance/InstanceRole/Resource
  InstanceInstanceProfileAB5AEF02:
    Type: AWS::IAM::InstanceProfile
    Properties:
      Roles:
        - Ref: InstanceInstanceRoleE9785DE5
    Metadata:
      aws:cdk:path: HelloworldStack/Instance/InstanceProfile
  InstanceC1063A87:
    Type: AWS::EC2::Instance
    Properties:
      AvailabilityZone:
        Fn::Select:
          - 0
          - Fn::GetAZs: ""
      IamInstanceProfile:
        Ref: InstanceInstanceProfileAB5AEF02
      ImageId:
        Ref: SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter
      InstanceType: t2.micro
      SecurityGroupIds:
        - Fn::GetAtt:
            - SecurityGroupDD263621
            - GroupId
      SubnetId:
        Ref: VpcPublic1Subnet1Subnet822C42F4
      Tags:
        - Key: Name
          Value: HelloworldStack/Instance
      UserData:
        Fn::Base64: "#!/bin/bash


          \      #!/usr/bin/env bash

          \      su ec2-user

          \      sudo yum install httpd -y

          \      sudo service httpd start

          \      sudo su -c \"cat > /var/www/html/index.html <<EOL

          \      <html>

          \        <head>

          \          <title>Call to Arms</title>

          \          <style>

          \            html, body { background: #000; padding: 0; margin: 0; }

          \            img { display: block; margin: 0px auto; }

          \          </style>

          \        </head>

          \        <body>

          \          <img src='https://media.giphy.com/media/10YoCxWqM3NHxK/giphy.gif' height='100%'/>

          \        </body>

          \      </html>

          \      EOL\"

          \    "
    DependsOn:
      - InstanceInstanceRoleE9785DE5
    Metadata:
      aws:cdk:path: HelloworldStack/Instance/Resource
  CDKMetadata:
    Type: AWS::CDK::Metadata
    Properties:
      Modules: aws-cdk=1.32.2,@aws-cdk/aws-cloudwatch=1.35.0,@aws-cdk/aws-ec2=1.35.0,@aws-cdk/aws-events=1.35.0,@aws-cdk/aws-iam=1.35.0,@aws-cdk/aws-kms=1.35.0,@aws-cdk/aws-logs=1.35.0,@aws-cdk/aws-s3=1.35.0,@aws-cdk/aws-ssm=1.35.0,@aws-cdk/cloud-assembly-schema=1.35.0,@aws-cdk/core=1.35.0,@aws-cdk/cx-api=1.35.0,@aws-cdk/region-info=1.35.0,jsii-runtime=node.js/v10.20.1
    Condition: CDKMetadataAvailable
Parameters:
  SsmParameterValueawsserviceamiamazonlinuxlatestamzn2amihvmx8664gp2C96584B6F00A464EAD1953AFF4B05118Parameter:
    Type: AWS::SSM::Parameter::Value<AWS::EC2::Image::Id>
    Default: /aws/service/ami-amazon-linux-latest/amzn2-ami-hvm-x86_64-gp2
Outputs:
  PublicDNSIPv4:
    Value:
      Fn::GetAtt:
        - InstanceC1063A87
        - PublicDnsName
Conditions:
  CDKMetadataAvailable:
    Fn::Or:
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-northeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-1
          - Fn::Equals:
              - Ref: AWS::Region
              - ap-southeast-2
          - Fn::Equals:
              - Ref: AWS::Region
              - ca-central-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - cn-northwest-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-central-1
      - Fn::Or:
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-north-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-2
          - Fn::Equals:
              - Ref: AWS::Region
              - eu-west-3
          - Fn::Equals:
              - Ref: AWS::Region
              - me-south-1
          - Fn::Equals:
              - Ref: AWS::Region
              - sa-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-east-2
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-1
          - Fn::Equals:
              - Ref: AWS::Region
              - us-west-2

It's a very long template with almost 700 lines that you wrote with less than 100 lines of code!

Of course, you can directly invoke CloudFormation to deploy this template by typing the following command in your terminal:

aws cloudformation deploy --template-file helloworld.yaml --stack-name HelloworldStack  --capabilities CAPABILITY_IAM

You need to define the stack name and also explicitly acknowledge that the stack template will affect permissions in your AWS account. Because the stack is already up to date, there will be no changes.

Destroy the app

When you are done, don't forget to delete your app to avoid any costs and save money:

cdk destroy

Conclusion

You created a very long CloudFormation template with only a few lines of CDK and deployed an EC2 instance! πŸŽ‰πŸŽ‰πŸŽ‰

I found the CDK very useful to learn how CloudFormation works. At first, I tried to learn CloudFormation alone and it was very frustrating. The syntax is very verbose and it's hard to have a good example without writing tons of lines.

Using the CDK makes the experience nice and fun because you have results relatively quickly. I was surprised by the number of lines transpiled by the CDK: 700 lines of CloudFormation for 100 lines of CDK. The difference is huge!

What I regret is that the API reference doesn't have a JavaScript section. I started my tutorial in JavaScript based on the CDK Getting Started and it was a little confusing to read the API reference at the beginning... But nothing to worry about! πŸ˜…

That's it for me, hope you learned something!

Don't forget to Stay Home, Save Lives, and Pass your AWS exam!

No Comments Yet