Earlier, I posted an example CoudFormation template that creates a simple VPC and promised an update that would also create a a VPC with an EC2 Linux jump server. That template is below — and is, surprise!, in YAML.
While I am not a fan of the tooling AWS provides for CloudFormation, now that you can use its CloudFormation Designer with YAML, there’s no reason not to use YAML. YAML is so much easier to code and debug, though I wish my favorite editor (Visual Studio Code) had better support for YAML indentation. I’m not sure which is harder: debugging JSON with missing braces and/or brackets or YAML with messed up indentation. Wait a minute….YAML is definitely easier. 🙂 And this nifty little translation tool between JSON and YAML makes conversions easy.
I was reminded just today of how useful these skeleton, but runnable, templates can be. A client asked for a management VPC to be peered to a running production VPC. Using this template, I produced a VPC to the client’s specifications in about 10 minutes — far faster than you can do it by hand. And, everything is nicely labelled via tags and clear to anyone who looks at it. I did add the VPC peering connections by hand simply because that was faster and I wanted the client to be able to respond to his internal users before lunch. That’s the power of using CloudFormation!
Here are some notes on this template:
- It builds precisely the same VPC and subnets as the earlier post
- It has a table for AWS Linux AMIs only in the regions I use
- Be sure you have a valid EC2 key pair that you can specify when using the template — the name in the KeyPairName parameter is invalid.
I hope you find this as useful as I have. I have been gratified by the positive comments on previous CloudFormation sample posts and hope you’ll take a minute to comment, good or bad, on this example.
AWSTemplateFormatVersion: '2010-09-09' Description: Creates a two-subnet VPC (public w/ NAT gateway and private) with a Linux bastion instance in the public subnet (c) 2017 Air11 Technology LLC -- licensed under the Apache OpenSource 2.0 license, https://opensource.org/licenses/Apache-2.0 Metadata: AWS::CloudFormation::Interface: ParameterGroups: - Label: default: VPC configuration parameters Parameters: - VPCCIDR - PublicSubnetCIDR - PrivateSubnetCIDR - SSHLocation - KeyPairName ParameterLabels: VPCCIDR: default: Enter CIDR of new VPC PublicSubnetCIDR: default: Enter CIDR of the public subnet PrivateSubnetCIDR: default: Enter CIDR of the private subnet SSHLocation: default: Subnet allowed to ssh on TCP to public subnet KeyPairName: default: Key pair for bastion host Parameters: VPCCIDR: AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 Default: 10.10.0.0/16 Description: CIDR block for entire VPC. Type: String PublicSubnetCIDR: AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 Default: 10.10.10.0/24 Description: CIDR block for the public subnet Type: String PrivateSubnetCIDR: AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(\/(1[6-9]|2[0-8]))$ ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/16-28 Default: 10.10.20.0/24 Description: CIDR block for the private subnet Type: String SSHLocation: AllowedPattern: ^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])(/([0-9]|[1-2][0-9]|3[0-2]))$ ConstraintDescription: CIDR block parameter must be in the form x.x.x.x/0-28 Default: 0.0.0.0/0 Description: Network allowed to ssh to instances in public subnet. Type: String KeyPairName: Description: Keypair for Linux bastion host Type: AWS::EC2::KeyPair::KeyName Default: your-keypair-name-here Mappings: RegionMap: us-east-1: AMI: ami-22ce4934 us-west-1: AMI: ami-9e247efe us-east-2: AMI: ami-7bfcd81e us-west-2: AMI: ami-8ca83fec Resources: VPC: Type: AWS::EC2::VPC Properties: EnableDnsSupport: 'true' EnableDnsHostnames: 'true' CidrBlock: !Ref 'VPCCIDR' Tags: - Key: Name Value: !Sub 'VPC ${VPCCIDR}' - Key: CloudFormationStack Value: !Ref 'AWS::StackId' InternetGateway: Type: AWS::EC2::InternetGateway Properties: Tags: - Key: Name Value: IGW - Key: CloudFormationStack Value: !Ref 'AWS::StackId' PublicSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref 'VPC' CidrBlock: !Ref 'PublicSubnetCIDR' Tags: - Key: Name Value: !Sub 'Public ${PublicSubnetCIDR}' - Key: CloudFormationStack Value: !Ref 'AWS::StackId' PrivateSubnet: Type: AWS::EC2::Subnet Properties: VpcId: !Ref 'VPC' CidrBlock: !Ref 'PrivateSubnetCIDR' Tags: - Key: Name Value: !Sub 'Private ${PrivateSubnetCIDR}' - Key: CloudFormationStack Value: !Ref 'AWS::StackId' AttachGateway: Type: AWS::EC2::VPCGatewayAttachment Properties: VpcId: !Ref 'VPC' InternetGatewayId: !Ref 'InternetGateway' EIP: Type: AWS::EC2::EIP Properties: Domain: vpc NAT: DependsOn: AttachGateway Type: AWS::EC2::NatGateway Properties: AllocationId: !GetAtt 'EIP.AllocationId' SubnetId: !Ref 'PublicSubnet' PublicSubnetRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref 'VPC' Tags: - Key: Name Value: !Sub 'Public ${PublicSubnetCIDR}' - Key: CloudFormationStack Value: !Ref 'AWS::StackId' PublicRoute: Type: AWS::EC2::Route DependsOn: AttachGateway Properties: RouteTableId: !Ref 'PublicSubnetRouteTable' DestinationCidrBlock: 0.0.0.0/0 GatewayId: !Ref 'InternetGateway' PublicSubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref 'PublicSubnet' RouteTableId: !Ref 'PublicSubnetRouteTable' PublicInstanceSG: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref 'VPC' GroupDescription: Enable SSH access via port 22 SecurityGroupIngress: - IpProtocol: tcp FromPort: '22' ToPort: '22' CidrIp: !Ref 'SSHLocation' - IpProtocol: tcp FromPort: '80' ToPort: '80' CidrIp: 0.0.0.0/0 Tags: - Key: Name Value: !Sub 'PublicSG ${VPCCIDR}' - Key: CloudFormationStack Value: !Ref 'AWS::StackId' PrivateSubnetRouteTable: Type: AWS::EC2::RouteTable Properties: VpcId: !Ref 'VPC' Tags: - Key: Name Value: !Sub 'Private ${PrivateSubnetCIDR}' - Key: CloudFormationStack Value: !Ref 'AWS::StackId' PrivateSubnetRoute: Type: AWS::EC2::Route Properties: RouteTableId: !Ref 'PrivateSubnetRouteTable' DestinationCidrBlock: 0.0.0.0/0 NatGatewayId: !Ref 'NAT' PrivateSubnetRouteTableAssociation: Type: AWS::EC2::SubnetRouteTableAssociation Properties: SubnetId: !Ref 'PrivateSubnet' RouteTableId: !Ref 'PrivateSubnetRouteTable' PrivateSubnetInstanceSG: Type: AWS::EC2::SecurityGroup Properties: VpcId: !Ref 'VPC' GroupDescription: Enable all TCP ports from instances in this VPC SecurityGroupIngress: - IpProtocol: tcp FromPort: '1' ToPort: '65535' CidrIp: !Ref 'VPCCIDR' Tags: - Key: Name Value: !Sub 'PrivateSG ${VPCCIDR}' - Key: CloudFormationStack Value: !Ref 'AWS::StackId' LinuxBastionHost: Type: AWS::EC2::Instance DependsOn: - AttachGateway Properties: KeyName: !Ref 'KeyPairName' ImageId: !FindInMap [RegionMap, !Ref 'AWS::Region', AMI] InstanceType: t2.micro NetworkInterfaces: - AssociatePublicIpAddress: true DeleteOnTermination: true Description: ENI for bastion host DeviceIndex: '0' SubnetId: !Ref 'PublicSubnet' GroupSet: - !Ref 'PublicInstanceSG' Tags: - Key: Name Value: !Sub 'Linux bastion ${VPCCIDR}' - Key: CloudFormationStack Value: !Ref 'AWS::StackId' Outputs: VPCId: Description: VPCId of the newly created VPC Value: !Ref 'VPC' NatGateway: Description: NAT gateway instance Value: !Ref 'NAT' EIPAddress: Description: EIP allocated to NAT gateway Value: !Ref 'EIP' PublicSubnet: Description: SubnetId of the public subnet Value: !Ref 'PublicSubnet' PublicSubnetRouteTable: Description: Public route table Value: !Ref 'PublicSubnetRouteTable' PublicInstanceSG: Description: SG for instances in public subnet Value: !Ref 'PublicInstanceSG' PrivateSubnet: Description: SubnetId of the public subnet Value: !Ref 'PrivateSubnet' PrivateSubnetRouteTable: Description: Private route table Value: !Ref 'PrivateSubnetRouteTable' PrivateSubnetInstanceSG: Description: SG for instances in the private subnet Value: !Ref 'PrivateSubnetInstanceSG' LinuxBastionHost: Description: Linux bastion instance Value: !Ref 'LinuxBastionHost'
Leave a Reply