In our previous post we saw how to create a server on AWS and access.
We have a brief idea of what Amazon
charges for. In terms of webserver without a database, it depends on
EC2
instancesInternet Gateway
to serve internet traffic outside AWS.This post, we will understand
To understand a VPC (Virtual Private Cloud), we have to understand a Public Cloud
.
Public Cloud is basically when you have a single infra (not a single instance, but the whole infra as a whole), shared between multiple people.
For example a restaurant serving multiple dishes. Most SaaS appication.
But a Private Cloud is when you want to set boundaries on the network, resources etc thats available to the clients. Such that one doesn’t access other’s information or resources.
For example, many people can use AWS
but the content served is different for different clients. But that doesn’t mean, each client/company gets its own set of servers. It might as well be shared with some other processes.
So you get your own nameservers, (DNS servers) which help you translate IP
or DNS
and connect to the devies on that network. You can’t access them from your internet like you do for facebook or google.
You can specify an IP address range for the VPC, add subnets, add gateways, and associate security groups. Although you have mostly 10.x.x.x
and 172.x.x.x
for amazon in ap-south-1
Since you can assign IP address range, you can also borrow from this ip range to split traffic between multiple regions.
Each subnet must be associated with a route table
, which specifies the allowed routes for outbound traffic leaving the subnet.
Now AWS
’s way of exposing a VPC to receive external traffic is by using an Internet Gateway
. An example will help understand it better.
A router is a gateway between your local network (your VPC) and the ISP. It allows you to share internet using Wi-Fi of multiple devices and connect to your ISP. On the ISP’s end’ its a device that connects to the outside world to get the data and send it back to you.
It is also a protocol converter
, because LAN
uses different protocol than rest of the Internet.
Before arriving at the router, packets go to the gateway channel first, and the gateway checks the header information at once. After checking for any kind of error in the destination IP address and packet. According to the needs of the destination network, it carries out data conversion and protocol conversion on the packet, which is also the most critical step. Finally, the processed packet is forwarded to the router to establish intelligent communication between the two different networks.
Anyway, its a separate component, so that you can attach and deattach your vpc
from the internet
faster.
Route table
A route table contains a set of rules, called routes, that determine where network traffic from your subnet or gateway is directed.
Basically we want to:
/16
subnets
in two regions
. Also called __A__vailability __Z__onesInternet Gateway
and attach it to the VPC
on one endroute table
that allows traffic from internet
to this subnet
via the gateway
aws_deploy.tf
provider "aws" {
region = "ap-south-1"
}
// Create vpc and add a subnet
// Add a routing table and direct all traffic from internet
// to the subnet
resource "aws_vpc" "app_vpc" {
cidr_block = "10.0.0.0/16"
enable_dns_support = true
enable_dns_hostnames = true
tags = {
Name = "Konoha VPC"
}
}
// Create subnet 1
resource "aws_subnet" "app_server_subnet" {
vpc_id = aws_vpc.app_vpc.id
cidr_block = cidrsubnet(aws_vpc.app_vpc.cidr_block, 8, 1)
map_public_ip_on_launch = true
availability_zone = "ap-south-1b"
}
// Create subnet 2 and its routing table
resource "aws_subnet" "app_server_subnet2" {
vpc_id = aws_vpc.app_vpc.id
cidr_block = cidrsubnet(aws_vpc.app_vpc.cidr_block, 8, 2)
map_public_ip_on_launch = true
availability_zone = "ap-south-1b"
}
// Create an IG for the VPC
resource "aws_internet_gateway" "app_server_ig" {
vpc_id = aws_vpc.app_vpc.id
}
// Routing table which allows traffic
// from anywhere to the VPC via the IG
resource "aws_route_table" "app_sever_rt" {
vpc_id = aws_vpc.app_vpc.id
route {
cidr_block = "0.0.0.0/0"
gateway_id = aws_internet_gateway.app_server_ig.id
}
}
// Making subnets public by attaching them to the routing table
// which is associated the the app_server_ig Internet Gateway
resource "aws_route_table_association" "app_server_route" {
route_table_id = aws_route_table.app_sever_rt.id
subnet_id = aws_subnet.app_server_subnet.id
}
resource "aws_route_table_association" "app_server_route2" {
route_table_id = aws_route_table.app_sever_rt.id
subnet_id = aws_subnet.app_server_subnet2.id
}
Elastic Container orchestration Service, as the name implies, deals with creating ec2/fargate instances (forget fargate for now, this is fancy enough).
Its used to manage a cluster of ec2 instances
. Like a swarm of bees
. Teach that to your kids.
For this we need to provide a task definition
file, which will tell what image to run, or what commands to run, resource allocation, ports to expose etc.
ecs_task_definition.json
[
{
"essential": true,
"memory": 512,
"name": "konoha-server",
"cpu": 1,
"image": "${IMAGE}",
"environment": [],
"portMappings": [
{
"containerPort": 9090,
"hostPort": 9090
}
]
}
]
You can read about it here.
aws_deploy.tf
resource "aws_ecs_cluster" "app_server_cluster" {
name = "app-server-cluster"
}
resource "aws_ecs_task_definition" "app_task_definition" {
family = "konoha-server"
// We dont need this for EC2 instances.
// task_role_arn = format("arn:aws:iam::%s:role/ecsEcrTaskExecutionRole", var.AWS_ACCOUNT)
// execution_role_arn = format("arn:aws:iam::%s:role/ecsEcrTaskExecutionRole", var.AWS_ACCOUNT)
container_definitions = templatefile("${path.module}/templates/ecs/ecs-task-definition.json", { IMAGE: var.DOCKER_IMAGE })
}
// Assoicating the task deifintion to the cluster
resource "aws_ecs_service" "app_ecs_service" {
name = "konoha-server" // keep the name same here as the family
cluster = aws_ecs_cluster.app_server_cluster.id
task_definition = aws_ecs_task_definition.app_task_definition.arn
desired_count = 1
force_new_deployment = true
triggers = {
redeployment = timestamp()
}
}
Please make sure to the the directory structure to your resources match in the terraform file
You can’t really have a different host
and container
port mapping, cause penny pincher Amazon
doesn’t allow it. This will also cause us to open port 9090
on the security group. And therby requiring a Load Balancer (:80)
to map traffic with a prefix or domain to the cluster on 9090
. Lol
Auto Scaling Groups allow you to increase, decrease or maintain the allocation of ec2 instances. There are different scaling providers.
You can read about them here
An ASG would use the aws_launch_template
in order to provison the machine for ECS to run its task.
The way the ASG
and the ECS
communicate is via a config
file which is expected to be present each of the EC2
instances. That file contains the cluster
name.
Previously in the aws_ecs_service
we had joined the cluster
to the task_definition
. And with the cluster
name present in the EC2
instance, ECS
knows which machine to run the task
on. Phew!!
ecs.sh
#!/bin/bash
echo ECS_CLUSTER=app-server-cluster >> /etc/ecs/ecs.config
ecs_deploy.tf
// resource "aws_instance" "konoha_server" {
// ami = "ami-027a0367928d05f3e"
// instance_type = "t2.micro"
// associate_public_ip_address = true
// key_name = "tf-key-pair"
//
// vpc_security_group_ids = [aws_security_group.konoha_api_sg.id]
//
// iam_instance_profile = "ecsInstanceRole"
//
// user_data = base64encode(templatefile("${path.module}/templates/ecs/setup.sh", {
// IMAGE = var.DOCKER_IMAGE,
// AWS_ACCOUNT = var.AWS_ACCOUNT
// }))
//
// tags = {
// Name = "konoha-api-instance"
// }
// }
resource "aws_launch_template" "app_server_launch_configuration" {
name_prefix = "konoha-server"
image_id = "ami-027a0367928d05f3e"
instance_type = "t2.micro"
key_name = "tf-key-pair"
vpc_security_group_ids = [aws_security_group.konoha_api_sg.id]
iam_instance_profile {
name = "ecsInstanceRole"
}
user_data = filebase64("${path.module}/templates/ecs/ecs.sh")
}
resource "aws_autoscaling_group" "app_server_ecs_asg" {
name = "AppServerAsg"
vpc_zone_identifier = [aws_subnet.app_server_subnet.id, aws_subnet.app_server_subnet2.id]
target_group_arns = [aws_lb_target_group.app_lb_tg.arn] // linking to the LB
launch_template {
id = aws_launch_template.app_server_launch_configuration.id
version = "$Latest"
}
min_size = 1
max_size = 1
desired_capacity = 1
health_check_type = "EC2"
tag {
key = "Name"
value = "AppServerInstance"
propagate_at_launch = true
}
tag {
key = "AmazonECSManaged"
value = true
propagate_at_launch = true
}
}
We will use no Scaling Policy, which is manual scaling policy. All capacity is set to 1
, because Free Tier
baby.
You can checkout one of the examples of a dynamic policy
, where it scales based on cpu
utilization limit.
You might as well take a break and stretch a leg.
Its nearing the end of the setup. All we need is a load balancer. For a load balancer, we will forward all traffic on port 80
having a path /konoha/api
to port 9090
. And the way to associate that is by using a target group
.
You can specify health check on your target group, which in our case is sending a /ping
request on port :9090
.
From inside the VPC
on only one server is running on that port, which is our server
. You can go through the links below, but the setup is quite easy here.
application load balancer
to manage traffic, like nginx.VPC
and the PORT
mapping.aws_lb_target_group
to forward traffic to.
// Create an application load balancer attached with
// a security group and which zones to be present in.
resource "aws_lb" "app_lb" {
name = "app-server-lb"
internal = false
load_balancer_type = "application"
security_groups = [aws_security_group.konoha_api_sg.id]
subnets = [aws_subnet.app_server_subnet.id, aws_subnet.app_server_subnet2.id]
enable_deletion_protection = false
tags = {
Environment = var.ENVIRONMENT
Name = "KonohaServerLoadBalancer"
}
}
// Load balancer target group which is resposible for
// identifying which VPC to sent traffic and what port
// Depending on the health check, the target groups are
// registered or deregistered.
resource "aws_lb_target_group" "app_lb_tg" {
name = "KonohaServerTgHttp"
port = 9090
protocol = "HTTP"
vpc_id = aws_vpc.app_vpc.id
health_check {
healthy_threshold = 3
unhealthy_threshold = 10
timeout = 5
interval = 30
path = "/ping"
port = "9090"
matcher = "200-388"
}
}
// This here is the LB listening on port 80
resource "aws_lb_listener" "app_lb_listener" {
load_balancer_arn = aws_lb.app_lb.arn
port = "80"
protocol = "HTTP"
default_action {
type = "fixed-response"
fixed_response {
content_type = "text/plain"
message_body = "HEALTHY"
status_code = "200"
}
}
}
// This rule allows urls with /konoha/api/ prefix
// on port 80, to be forwared to the KonohaServerTgHttp target group
// which send the traffic to port 9090 in the VPC
resource "aws_lb_listener_rule" "app_server_lb" {
listener_arn = aws_lb_listener.app_lb_listener.arn
priority = 100
action {
type = "forward"
target_group_arn = aws_lb_target_group.app_lb_tg.arn
}
condition {
path_pattern {
values = ["/konoha/api/*"]
}
}
}
Here is a Gist of everything till now. Feel free to use it, modify it, or ask for help in comments.
Well, now everything over the network is unencrypted, cause we are not using HTTPS
on port :443
.
For that we will need to have to create a Route 53
thing, which will allow us to generate certificates
, which we can attach. The auto renewal is done by AWS
. I pray.