Now we continue improving the VPC template from my previous blog entry “Starting with Cloudformation templates”

What we ended up with there was a VPC with one subnet connected to the Internet. Or what is know in AWS lingo as a “Public Subnet”.

The goal now is a VPC with presence in tree Availability Zones with a “Public Subnet” in each, and a “Private Subnet” in each as well.

Humble beginnings

Before we go all out on tree Availability Zones, let us set it up with only one. It will not be that hard to expand the template to tree Availability Zones when we have got it up and running on one.

Public vs Private subnet

The main difference between a Private and a Public subnet is its routing to Internet.

Public subnet

The Public subnet is routed directly through an Internet Gateway so you need an Elastic IP on a resource accessing Internet. Resources can then also be available on the Internet through their Elastic IP.

Private subnet

The Private subnet does not have direct route to Internet, and can be configured in a manner it does not have access to Internet at all. We will allow access to the Internet through a NAT Gateway. Resources in a Private subnet have no use of Elastic IP.

NAT Gateway

The NAT Gateway we talk about here is a managed service from AWS, and it comes with a price. Each instance of NAT Gateway costs $0.045 per hour and $0.045 per GB data transferred in or out. In our setup with tree Availability Zones and a NAT Gateway in each of them, we get up to $100 a month just for the Gateways alone.

You can read more about NAT Gateway pricing here

Private Subnet in Cloudformation

As stated, the main difference is in the routing. And we know we need a NAT Gateway, let us check the documentation.

It gives us this skeleton:

Type: AWS::EC2::NatGateway
Properties:
  AllocationId: String
  SubnetId: String
    Tags:
      - Tag

Both SubnetId and AllocationId are required. SubnetId needs a reference to a AWS::EC2::Subnet resource. But AllocationId is The allocation ID of an Elastic IP address to associate with the NAT gateway.

So we check the documentation for Elastic IP and it gives us

Type: AWS::EC2::EIP
Properties:
  Domain: String
  InstanceId: String
  PublicIpv4Pool: String
  Tags:
    - Tag

Of those, only Domain is of interest, and as it is to be used with a NAT Gateway we not care for EC2-Classic, so we set this to vpc.

The documentation also mentions that if we are creating this resource in the same template as our VPC we need to create dependency on the VPC-gateway attachment.

A very important thing we can read from the documentation page is the return values of the resource. If you ask for the !Ref value, you will get the IP address associated with the resource, but we want the allocation ID of it.

To access that value we need to use the Fn::GetAtt function. And we need to use it with the AllocationId key word. Look for that below. There we use the shorter !GetAtt form of the function.

Dependency

You can give Cloudformation directions on order it must create resources. You can do that by using the keyword DependsOn. Like for example:

  GatewayToInternet:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGW

  NatGWIP:
    DependsOn: GatewayToInternet
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

You do not need to specify the DependsOn relationship if the dependency is show through reference as seen here:

  InternetGW:
    Type: AWS::EC2::InternetGateway

  GatewayToInternet:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGW

Here it would be superfluous to declare that GatewayToInternet was dependent on InternetGW. You can read more about DependsOn here

How a subnet is defined

Now we need to look back to a normal subnet and see what is needed to define it and its routing. The parts from my previous blog are:

  SubNett:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.42.0/24
      VpcId: !Ref VPC

  RouteTablePublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  RoutePublic:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePublic
      GatewayId: !Ref InternetGW

  Route2Subnet:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref SubNett

So there is a route, in a route table. Then there is a subnet, and then there is the association between subnet and route table.

The difference from this to the Private subnet is where to route the traffic not local to the VPC.

Route for a Private subnet

As we stated in last blog, these are all the options for the Route resource:

Type: AWS::EC2::Route
Properties:
  DestinationCidrBlock: String
  DestinationIpv6CidrBlock: String
  EgressOnlyInternetGatewayId: String
  GatewayId: String
  InstanceId: String
  NatGatewayId: String
  NetworkInterfaceId: String
  RouteTableId: String
  TransitGatewayId: String
  VpcPeeringConnectionId: String

Now, we will not use GatewayId but the NatGatewayId for the Private subnet.

Private Subnet

Putting the parts together, our Private subnet definition becomes:

  PrivateSubNett:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.43.0/24
      VpcId: !Ref VPC

  NatGWIP:
    DependsOn: GatewayToInternet
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NatGW:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGWIP.AllocationId
      SubnetId: !Ref PrivateSubNett

  RouteTablePrivate:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  RoutePrivate:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePrivate
      NatGatewayId: !Ref NatGW

  Route2PrivateSubnet:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePrivate
      SubnetId: !Ref PrivateSubNett

Putting the parts together

Now we combine the end product from previous blog and the last part, moving sections around so they are logically grouped together.

---
Description: This is an attempt to create a VPC in a Cloudformation stack
Resources:
  VPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: 10.0.0.0/16

  InternetGW:
    Type: AWS::EC2::InternetGateway

  GatewayToInternet:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGW

  NatGWIP:
    DependsOn: GatewayToInternet
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NatGW:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGWIP.AllocationId
      SubnetId: !Ref PrivateSubNett

  SubNett:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.42.0/24
      VpcId: !Ref VPC

  PrivateSubNett:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.43.0/24
      VpcId: !Ref VPC

  RouteTablePublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  RouteTablePrivate:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  RoutePublic:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePublic
      GatewayId: !Ref InternetGW

  RoutePrivate:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePrivate
      NatGatewayId: !Ref NatGW

  Route2Subnet:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref SubNett

  Route2PrivateSubnet:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePrivate
      SubnetId: !Ref PrivateSubNett

This makes up a template that will create a Private and a Public subnet.

More Availability Zones

Above, we did not say anything about the Availability Zone thing were instantiated in. But now we want to create 3 Public subnets and 3 Private subnets, with each Public and Private pair in distinct Availability Zones.

But let us recap quickly what we need to define.

  • A VPC, only one.
  • An Internet Gateway, only one.
  • A VPC Gateway Attachment, only one.
  • Elastic IPs, three of them, one for each NAT Gateway
  • NAT Gateways, three of them, one for each Availability Zone.
  • Subnets, six of them, one Private and one Public for each Availability Zone.
  • Route Tables, four total, one for the Public subnets, and one for each of the Private subnets
  • Routes, four total, one for the Public subnets, and one for each of the Private subnets
  • Subnet Associations, six total, one for each subnet.

So that is a lot of resources that need to be spelled out. And the only thing that we are missing before we start copy’n’paste bonanza is a way to decide which Availability Zone to instantiate our resources.

Specifying the Availability Zone

For specifying the Availability Zone for our resources we use the fact that the AWS::EC2::Subnet resource has a AvailabilityZone parameter that can be set. So if we are to create a subnet in the Stockholm region, we could use the values:

  • eu-north-1a
  • eu-north-1b
  • eu-north-1c

and we would be set.

But what if we want to use the template in Singapore? Then we would have to change those values. So that would be no good.

What we should do is to use the intrinsic function Fn::GetAZs which returns an array of the Availability Zones in a region. If the function only gets empty "" it defaults to the region the template is instantiated in. Then we need to use the intrinsic function Fn::Select to give use the first, the second and the third Availability Zone in the region. (You can read more about these functions here and here)

The !Select function takes an two element array, where the first element is the index that you want, and the second element is the array you want to select from, using the index given.

Combining everything

Let us combine this, but yet break it up in sections for clarity.

VPC and Internet Gateway

---
Description: This is an attempt to create a VPC in a Cloudformation stack
Resources:
  VPC:
    Type: 'AWS::EC2::VPC'
    Properties:
      CidrBlock: 10.0.0.0/16

  InternetGW:
    Type: AWS::EC2::InternetGateway

  GatewayToInternet:
    Type: AWS::EC2::VPCGatewayAttachment
    Properties:
      VpcId: !Ref VPC
      InternetGatewayId: !Ref InternetGW

Elastic IP addresses for the NAT Gateways

Here we start on the tedious repetion of similar resources, I postfix the name of all that kind of resources with the number assosiated with the Availability Zone index

  NatGWIP0:
    DependsOn: GatewayToInternet
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NatGWIP1:
    DependsOn: GatewayToInternet
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

  NatGWIP2:
    DependsOn: GatewayToInternet
    Type: AWS::EC2::EIP
    Properties:
      Domain: vpc

All the subnets…

The IP addresses chosen for each subnet is done this way to behave nicely in the next blog entry.

  PublicSubnet0:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.0.0/24
      VpcId: !Ref VPC
      AvailabilityZone:
        !Select
          - 0
          - !GetAZs ""

  PrivateSubnet0:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.100.0/24
      VpcId: !Ref VPC
      AvailabilityZone:
        !Select
          - 0
          - !GetAZs ""

  PublicSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.1.0/24
      VpcId: !Ref VPC
      AvailabilityZone:
        !Select
          - 1
          - !GetAZs ""

  PrivateSubnet1:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.101.0/24
      VpcId: !Ref VPC
      AvailabilityZone:
        !Select
          - 1
          - !GetAZs ""

  PublicSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.2.0/24
      VpcId: !Ref VPC
      AvailabilityZone:
        !Select
          - 2
          - !GetAZs ""

  PrivateSubnet2:
    Type: AWS::EC2::Subnet
    Properties:
      CidrBlock: 10.0.102.0/24
      VpcId: !Ref VPC
      AvailabilityZone:
        !Select
          - 2
          - !GetAZs ""

The NAT Gateways

  NatGW0:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGWIP0.AllocationId
      SubnetId: !Ref PrivateSubnet0

  NatGW1:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGWIP1.AllocationId
      SubnetId: !Ref PrivateSubnet1

  NatGW2:
    Type: AWS::EC2::NatGateway
    Properties:
      AllocationId: !GetAtt NatGWIP2.AllocationId
      SubnetId: !Ref PrivateSubnet2

The Route Tables

  RouteTablePublic:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  RouteTablePrivate0:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  RouteTablePrivate1:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

  RouteTablePrivate2:
    Type: AWS::EC2::RouteTable
    Properties:
      VpcId: !Ref VPC

The Routes

  RoutePublic:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePublic
      GatewayId: !Ref InternetGW

  RoutePrivate0:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePrivate0
      NatGatewayId: !Ref NatGW0

  RoutePrivate1:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePrivate1
      NatGatewayId: !Ref NatGW1

  RoutePrivate2:
    Type: AWS::EC2::Route
    Properties:
      DestinationCidrBlock: 0.0.0.0/0
      RouteTableId: !Ref RouteTablePrivate2
      NatGatewayId: !Ref NatGW2

Association between route tables and subnets

  Route2PublicSubnet0:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref PublicSubnet0

  Route2PrivateSubnet0:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePrivate0
      SubnetId: !Ref PrivateSubnet0

  Route2PublicSubnet1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref PublicSubnet1

  Route2PrivateSubnet1:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePrivate1
      SubnetId: !Ref PrivateSubnet1

  Route2PublicSubnet2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePublic
      SubnetId: !Ref PublicSubnet2

  Route2PrivateSubnet2:
    Type: AWS::EC2::SubnetRouteTableAssociation
    Properties:
      RouteTableId: !Ref RouteTablePrivate2
      SubnetId: !Ref PrivateSubnet2

And there is still thing missing here. We need to export references to the resources created here, so that we can use them in other stacks. That and other improvements await in the next chapter.

Jónas Helgi Pálsson

Senior Systems Consultant at Redpill Linpro

Jónas joined Redpill Linpro over a decade ago and has in that period worked as both a consultant and a system administrator. Main focus currently for Jónas is AWS and infrastructure on that platform. Previously been working with KVM and OpenStack, dabbles with programming and has a soft spot for openSUSE.

Migrating to OpenJDK

In the beginning of 2019 Oracle stopped releasing free-of-charge updates to their JDK, except for personal use. At the same time Oracle started requiring a subscription for Oracle JDK use in production environments. In this blog post we will look into OpenJDK as an alternative to the Oracle provided JDK and how one would migrate to it.

... [continue reading]