- Upgrading the Firmware on a Tulip
- Learning Code Through the Advent of Code Challenge
- Common Loki Misconfigurations
- Iterating Through a List in Ink
- Debugging Misconfigured Container Networks
- Minimum Viable EC2 in Terraform
- Storylets in Ink
- Interactive Fiction Tooling Overview
- In-Place Resizing for Digitalocean Droplets
- Unity Demonstrates the Importance of FOSS
- Target Labels in Prometheus
- My View of AI is the Same
- Verify DNS Ownership with TXT Records
- Sane Droplet Defaults
- Editing Made Easy with Vim
- Gatsby Gotchas
- Concatinating Default AWS Tags in Terraform
- Easily Updating the Default Github Branch
- Lifetimes in Rust
- Checking for Bad Links
- Maybe TypeScript and React is Bad
- Static Asset Management in React
- Bundler Down Time
- Using React Context for Localization
- JS Implementation of a Sticky Footer
- Custom Aliases
- Trying Out the 7drl Challenge
- Trash Opinions
- Building Your First Program in Rust
- Fixing mongod reports errors related to opening a socket
- Improving Open Source Maintenance
- Technical Interviewing Tips
- Housekeeping Note
- Dynamic Programming Basics
- The Oddity of Naming Conventions in Programming Languages
- An Experiment Using Machine Learning, Part 3
- Debugging with grep
- An Experiment Using Machine Learning, Part 2
- An Experiment Using Machine Learning, Part 1
- The Value of while
- National Day of Civic Hacking
- OpenAI and the Future of Humanity
- Creating a Whiteboard App in Django
- Creating Meaningful, Organized Information
- Towards A Critique of Social Media Feeds
- Setting up Routes in Django
- Developing a Messaging Component for Code for SF
- Dream Stream 2.0
- Keyed Collections in Javascript: Maps and Sets
- Blog Soft Relaunch
- Scraping with Puppeteer
- Looking Ahead to Dream Stream 2.0
- Solving West of Loathing's Soupstock Lode Puzzle
- Installing Ubuntu
- Interview with David Jickling Evaluation
- Compare Text Evaluation
- Dream Stream Evaluation
Minimum Viable EC2 in Terraform
The Terraform AWS tutorial will have you create an EC2 instance with just a few lines of configuration that look something like this:
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 4.16"
}
}
required_version = ">= 1.2.0"
}
provider "aws" {
region = "us-west-2"
}
resource "aws_instance" "app_server" {
ami = "ami-830c94e3"
instance_type = "t2.micro"
tags = {
Name = "ExampleAppServerInstance"
}
}
This is nice for new users that are just learning the basics of how the Terraform config language works. However this is essentially just a toy EC2 instance. It’s configuration is obviously not something you’d want in any production environment, and it isn’t really anything you’d want in a testing or dev environment either. For one thing, this EC2 instance is just going to connect to the region’s default VPC. That is not something you want to do in Terraform because Terraform cannot modify the default VPC, it can only read changes to its state, which defeats the purpose of Infrastructure as Code (IaC). Additionally the default VPC doesn’t support ipv6 addresses. Ipv6 has been around for a long time, but the default behavior of most engineers has been to look at how to manage ipv6, and then proceed to ignore it. That is starting to change now that cloud providers like AWS are charging much higher rates for ipv4 addresses. Regardless of where you are in your journey with working with ipv6, you probably don’t want to be locked into only being able to work with ipv4. So the default VPC is a non-starter. But once you create your own VPC resource that you associate with an EC2 instance you instantly create a lot of extra complexity. This is what your config file will look like at the end when you create the necessary resources to have an EC2 instance connected to your VPC that you can ssh into, and can accept HTTP and HTTPS requests.
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
}
}
}
provider "aws" {
region = "us-east-1"
}
resource "aws_key_pair" "my_instance_key" {
key_name = "my_ssh_key"
}
resource "aws_vpc" "my_instance_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
}
resource "aws_internet_gateway" "my_instance_igw" {
vpc_id = aws_vpc.my_instance_vpc.id
}
resource "aws_subnet" "my_instance_subnet" {
vpc_id = aws_vpc.my_instance_vpc.id
cidr_block = "10.0.0.0/24"
availability_zone = "us-east-1a"
}
resource "aws_route_table" "my_instance_rtb" {
vpc_id = aws_vpc.my_instance_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.my_instance_igw.id
}
}
resource "aws_route_table_association" "my_instance_rta" {
subnet_id = aws_subnet.my_instance_subnet.id
route_table_id = aws_route_table.my_instance_rtb.id
}
resource "aws_security_group" "my_instance_sg" {
name = "my_instance_sg"
vpc_id = aws_vpc.my_instance_vpc.id
ingress {
from_port = 22
to_port = 22
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 80
to_port = 80
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
ingress {
from_port = 443
to_port = 443
protocol = "tcp"
cidr_blocks = ["0.0.0.0/0"]
}
egress {
from_port = 0
to_port = 0
protocol = "-1"
cidr_blocks = ["0.0.0.0/0"]
}
}
resource "aws_instance" "my_instance" {
key_name = "my_ssh_key"
ami = "ami-058bd2d568351da34"
instance_type = "t2.micro"
vpc_security_group_ids = [aws_security_group.my_instance_sg.id]
subnet_id = aws_subnet.my_instance_subnet.id
associate_public_ip_address = true
}
Also note I omitted tagging for brevity’s sake, but you would want to include tagging as well. This is a pretty substantial leap. We’ve gone from 23 lines of code to 86. Lets go over these resources.
VPC
Our VPC resource defines a CIDR block, and also sets enable_dns_support
, and enable_dns_hostnames
to true
. It is intuitive enough that you need a CIDR block if you want to expose a public IP address. It is less intuitive that you would need to enable the DNS values, but it turns out you’ll run into connection problems without those values.
Internet Gateway
This is a VPC component that enables the internet traffic between your VPC and the internet, per the docs. The only thing you need to do here is associate it with the VPC you created.
Subnet
The subnet defines the range of IP addresses available on your VPC. The important thing here is we will define a specific availability zone for our subnet, and later when we define the config for our EC2 resource, we’ll associate it with the subnet ID. Note that there are some subtletys with regard to whether you have an IPv4 or IPv6 subnet. I’m hoping to cover that in a future post at some point.
Route Table
Okay, so the Internet Gateway connects to the internet for your VPC, but we use a route table resource to connect that internet traffic from the gateway to the VPC…
Route Table Association
… And then on top of that we have a route table association that connects our subnet to the route table. Everything is just a bunch of glue!
Security Group
This is the firewall for your VPC. This is a pretty standard definition for any public facing instance you might deploy as it opens up incoming TCP traffic on ports 22, 80, and 443 (SSH, HTTP, and HTTPS respectively), and allows all outgoing traffic. You might add some custom ports as well if you have some backend services that listen in on other ports.
EC2 Instance
Finally there is the EC2 instance. Besides defining the AMI ID being used and the instance size, we also define the VPC security group, and subnet ID used by the instance. Finally, and this is the most important thing to include, we set associate_public_ip_address
to true
. If you don’t do that you won’t have an easy way to ssh into your new instance once it is provisioned.