AWS Cloud Automation Quickstart – EC2 Instance and cloud-init

In Part 1 and Part 2 of this AWS quick start series we have walked through the steps on how to use the AWS console to create a Virtual Private Cloud (VPC). Then, we secured it by creating a custom Network Access Control List (NACL) that opens up ports 80 (HTTP) and 22 (SSH) and associated the NACL to our subnet. We also created a Resource Group and tagged our network objects to allow us to keep track of all of our cloud infrastructure.

We then took all of those manual steps and we created a Tuono Blueprint in under 40 lines of “code”. This Blueprint will repeatedly and reliably build that infrastructure for you. It just happens to be declarative as well, so you can edit the Blueprint and re-apply to make any changes you want over time.

In this part we are going to deploy an Ubuntu virtual machine that will be attached to our network and leverage cloud-init to configure NGINX and display a custom message that is accessible on the public interface via port 80.

How do I deploy an EC2 Instance in the AWS Console?

Navigate to the EC2 dashboard in the AWS console and select Instances from the side blade. Click “Launch Instances”.

1-AWS-launch-instance-wizard

The first step in deploying an AWS instance is picking an Amazon Machine Image (AMI). There are a variety of ways to search for an AMI. In order to stay consistent with our Azure quick start, we are going to search for the Canonical Ubuntu Community AMI that we have been using. We will search for that specific AMI by id ami-04bb0cc469b2b81cc.

Let’s select our image under Community AMIs.

2-AWS-select-image

This brings us to selecting an Instance Type. For this tutorial we are sticking with the free tier, so select the t2.micro image and click “NEXT: Configure Instance Details”.

3-AWS-select-instance-type

The ‘Configuring Instance Details’ page allows us to specify our Network as well as customize our VM with a cloud-init script. Under “Network” and “Subnet” select the VPC and subnet we previously created in Part 1. Since we associated a secure NACL with this subnet in Part 2 we know that we are deploying our Instance into a VPC that is secured to only allow traffic required for the web server.

We are also going to Enable “Auto-assign Public IP”. This is going to get the instance setup with a public IPv4 address that will allow the system to configure NGINX on first boot.

4-AWS-configure-instance1

How do I use cloud-init to customize an EC2 Instance in the AWS Console?

Under Advanced Details we are going to focus on the ‘User data’ section which will allow us to configure the Instance at launch. The cloud-init script below will install the NGINX package, set our login user and authorized ssh public key, and configure NGINX to display a custom welcome message. We are electing to use our own generated SSH key rather than generating a new AWS keypair. If you don’t know why you should be using SSH keys or know how to create one we have a blog article to help you with that! (The Dummy SSH key below is a legitimate key, but we want to be sure nobody mistakes it as their own key.)

#cloud-config
package_upgrade: false
packages:
  - nginx
users:
  - name: adminuser
    groups:
      - sudo
    sudo: ALL=(ALL) NOPASSWD:ALL
    ssh_authorized_keys:
      - ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDu= dummy_key@tuono.com


runcmd:
  - sudo su 
  - echo 'Congratulations on configuring an AWS web server! ' > /var/www/html/index.nginx-debian.html

In the ‘User data’ section paste in the cloud-init script above and then click “Next: Add Storage”

5-AWS-configure-instance2

The ‘Add Storage’ page allows you to create additional volumes for your Instance. For our purposes we will keep the default and click “Next: Add Tags”

6-AWS-add-storage

We wish the AWS console was consistent with when they allow you to add tags in their wizards. Instances are a case where there is a dedicated screen to add tags. We will add a tag to Name our web server then add our Resource Group Tag Key walkthrough and value webserver so we can keep track of our deployed infrastructure. Click “Next: Configure Security Group”.

7-AWS-add-tags

How do I configure a security group on an EC2 Instance during deploy?

The ‘Configure Security Group’ page allows us to add security at the Instance level. While our NACL defines the rules for the subnet, the security group is enforced directly on the Instance. We will keep the default SSH rule and add Type HTTP (Port 80). We can remove the range, ::/0, as that is for IPv6 and we will only be using IPv4 in this example. Click “Review and Launch”

8-AWS-security-group

After a review of the settings click “Launch”

9-AWS-review-instance

A screen pops up to configure a key pair that will be used to SSH into the Instance. We previously configured the SSH key in our cloud-init script to use the SSH key we already have. That allows us to launch our Instance without creating a new key pair. Select ‘Proceed without a key pair” and acknowledge then click “Launch Instances”

10-AWS-key-pair

The ‘Launch Status’ screen lets us know that AWS is turning the gears.

11-AWS-launch-instance

Once our Instance is complete let’s get the Public IPv4 address from the Instance.

13-AWS-congrats

Having created a resource group and tagging our infrastructure as a best practice we can see all of our deployed infrastructure under our resource group.

14-AWS-resource-group-final

Tuono gets us consistent, secure deployable infrastructure every time

Let’s build on our network infrastructure Blueprint we created in the previous posts. We are now going to add the pieces to define an image and deploy our VM along with configuring NGINX with cloud-init using Tuono. When deploying to AWS with Tuono we automatically create and tag Infrastructure and place it in a resource group so you know exactly what your cloud automation platform has deployed.

Compute

Below you can see that we are selecting the same Canonical Ubuntu 18.04 image as our manual deploy.

compute:
  image:
    bionic:
      publisher: Canonical
      product: UbuntuServer
      sku: 18.04-LTS
      venue:
        aws:
          image_id: ami-04bb0cc469b2b81cc

VM

We define the VM size and image and assign it a network interface bound to our previously created firewall and subnet.

vm:
    webserver:
      cores: 1
      memory: 1 GB
      image: bionic
      nics:
        external:
          ips:
            - private:
                type: dynamic
              public:
                type: static
          firewall: fw-external-access
          subnet: subnet-walkthrough

User Data

Setting up the SSH key and NGINX is accomplished with a configure stanza where we pass in our details and use cloud-init after first boot to customize NGINX.

configure:
        admin:
          username: (( admin_username ))
          public_key: (( admin_public_key ))
          
        userdata:
          type: cloud-init
          content: |
            #cloud-config
            package_upgrade: false
            packages:
              - nginx
            users:
              - name: (( admin_username ))
                groups:
                  - sudo
                sudo: ALL=(ALL) NOPASSWD:ALL
                ssh_authorized_keys:
                  - (( admin_public_key ))
                  
            runcmd:
              - sudo su 
              - echo '(( your_caption ))' > /var/www/html/index.nginx-debian.html

The complete Blueprint with user defined variables for this series is below.

#
# This is an example blueprint that demonstrates the creation of a webservice
#
---
variables:
  admin_username:
    description: The username for the administrative user.
    type: string
    default: adminuser
  admin_public_key:
    description: The OpenSSH Public Key to use for administrative access.
    type: string
    default: ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQDDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDummyDu= dummy_key@tuono.com
  your_caption:
    description: Name of webserver
    type: string
    default: "Congratulations on configuring an AWS web server!"

location:
  region:
    my-region:
      country: USA
      area: northwest
  folder:
    aws-walkthrough:
      region: my-region
networking:
  network:
    vnet-walkthrough:
      range: 10.0.0.0/16
      scope: public
  subnet:
    subnet-walkthrough:
      range: 10.0.0.0/24
      network: vnet-walkthrough
      firewall: fw-external-access  # Part 2 adds a Firewall to the subnet and marks it public
      scope: public

# Part 2 Protocols
  protocol:
    ssh:
      ports:
        - port: 22
          proto: tcp
    http:
      ports:
        - port: 80
          proto: tcp
        
# Part 2 adds a Firewall using a protocol
  firewall:
    fw-external-access:
      rules:
        - protocols: ssh
          to: self
        - protocols: http
          to: self
# Part 3 adds VM and configures NGINX with cloud-init
compute:
  image:
    bionic:
      publisher: Canonical
      product: UbuntuServer
      sku: 18.04-LTS
      venue:
        aws:
          # if provisioning fails due to image not found, go to:
          # https://cloud-images.ubuntu.com/locator/ec2/
          # and search for "bionic amd64 ebs" and also add your AWS zone name like "us-west-2"
          image_id: ami-04bb0cc469b2b81cc
  vm:
    webserver-var:
      cores: 1
      memory: 1 GB
      image: bionic
      nics:
        external:
          ips:
            - private:
                type: dynamic
              public:
                type: static
          firewall: fw-external-access
          subnet: subnet-walkthrough
      configure:
        admin:
          username: (( admin_username ))
          public_key: (( admin_public_key ))
        userdata:
          type: cloud-init
          content: |
            #cloud-config
            package_upgrade: false
            packages:
              - nginx
            users:
              - name: (( admin_username ))
                groups:
                  - sudo
                sudo: ALL=(ALL) NOPASSWD:ALL
                ssh_authorized_keys:
                  - (( admin_public_key ))
            runcmd:
              - sudo su 
              - echo '(( your_caption ))' > /var/www/html/index.nginx-debian.html

As always, all of the Tuono code samples in this post can be used for free in the Community Edition of the Tuono platform.

Deploy your first environment