Supreet Singh - May 20, 2023
In the first part of this series, we set the stage for deploying a Rails application on AWS using Pulumi. We explored the benefits of our chosen trio (Rails, AWS and Pulumi) and prepared our development environment. Now, let's dive deeper into the application preparation process and explore more of what Pulumi has to offer.
If I could go back in time, I would have started with learning deeply about AWS services first. The great thing about services like Render is that they abstract away so many details that are required for an application to run. It took me a while to wrap my head around how AWS networking stack worked - VPC, subnets, security groups, etc. I would recommend reading up on these concepts before diving into Pulumi. As a matter of fact, I think it's a good idea to draw up your networking diagram before you start coding.
Here's the initial take for this project that I drew up:
This is a very simplistic take on setting up a networking stack.
Gotchas
To make things easier, I used Pulumi's Typescript SDK. My primary reasons were that I was already familiar with Typescript and since Typescript is very popular, there were a lot of resources on the web as well.
Pulumi has some great starter docs so I won't go into too much detail here. I'll just highlight some of the things that I found useful.
A lot of the tutorials online only use a single index.ts
file. I found it helpful (for my own sanity) to split up the code by resources.
Here's a list of services I ended up using:
pulumi.aws.iam
for creating and access IAM rolespulumi.aws.ec2.Vpc
for creating a VPC. I created a public and private subnet with two availability zones. Also helpful to set NatGateways strategy to None here to avoid unnecessary costs.pulumi.aws.ec2.SecurityGroup
for creating security groups. I created a security group for the load balancer, ECS cluster, EC2 instances and RDS.pulumi.aws.lb
for creating an application load balancer. I created a public load balancer that forwards traffic to the ECS cluster. Also created a target group and listener to achieve that.pulumi.aws.acm
for creating an SSL certificate. I created a certificate for HTTPS traffic and attached it to the LB listener.pulumi.aws.ec2.LaunchTemplate
for creating a launch template. I created a launch template for the EC2 instances that are part of the ECS cluster. This launch template was used by the Autoscaling group.pulumi.aws.autoscaling
for creating an autoscaling group. I created an autoscaling group that uses the launch template creates earlier.pulumi.aws.ecs
to create the EcsCluster
and EcsService
. I also created a EcsTaskDefinition
that is used by the service. CapacityProvider
is used to make sure the service uses the autoscaling group.pulumi.aws.ecr
to create a repository for the Docker image. I created a repository for the Rails app and this was consumed by ECS as defined in the container definition.pulumi.aws.rds
to create a database. I created a Postgres database in a private subnet. I also created a security group for the database and allowed access from the ECS cluster security group.Gotchas
const ami = AwsEC2.getAmi({
mostRecent: true,
filters: [
{
name: "name",
values: ["amzn2-ami-ecs-hvm-2.0.20220831-x86_64-ebs"],
},
{
name: "virtualization-type",
values: ["hvm"],
},
],
owners: ["..."],
});
const userData = `
#!/bin/bash
sudo bash
echo ECS_CLUSTER=${ECS_CLUSTER_NAME} >> /etc/ecs/ecs.config
`;
export const ec2LaunchTemplate = new AwsEC2.LaunchTemplate(
"ms-ec2-launch-template",
{
imageId: ami.then((ubuntu) => ubuntu.id),
instanceType: AwsEC2.InstanceType.T3_Small,
userData: Buffer.from(userData, "binary").toString("base64"),
}
);
In the next post, we'll dig deeper into Pulumi and prepping our infrastructure for deployment.
Check out Part 3 here: Deploying Your Rails App to AWS with Pulumi: Part 3