This document covers the steps on setting up this repository on various cloud hosting providers.
Deploying on AWS requires a basic understanding of the following tools and services:
- Docker
- GitHub actions & workflows
- GitHub environments and secrets
- AWS Elastic Container Registry (ECR)
- AWS Elastic Container Service (ECS)
- AWS Virtual Private Cloud (VPC)
- AWS Fargate
- AWS Elastic Load Balancer (ELB)
- AWS Elastic IPs
- AWS Identity and Access Management (IAM)
- AWS Relational Database Service (RDS)
An overview of how the continuous delivery cycle works in Plio with GitHub action and Amazon ECR & ECS.
Follow the steps below to set up the staging environment on AWS.
-
Login to your AWS console.
-
Go to VPC. (skip this step if you've already created a VPC when setting up the frontend repository)
-
Create a new VPC.
-
Name it
plio-staging
. -
In IPv4 CIDR block, enter
10.0.0.0/16
- this will reserve 256 * 256 IPs within the VPC. -
Click on the create button. You will see the new VPC under the list of VPCs.
-
Check out this AWS guide for more details on VPC.
-
You'll need to attach an Internet Gateway to this VPC.
- Click on
Internet Gateways
in the VPC Dashboard. - Select
Create internet Gateaway
. - Name it as
plio-staging
and save it. - Click on
Attach to a VPC
in the next page and select the VPC created above.
- Click on
-
Next, you'll need to attach Subnets to the VPC created above.
- Click on
Subnets
in the VPC Dashboard. - Click on
Create Subnet
. - Choose the VPC created above as VPC ID.
- Enter the
Subnet name
asplio-staging-1
. - Either choose the
Availability Zone
if you have a preference or leave it to the default - Under
IPv4 CIDR block
, add a range of IPv4s that belong to your subnet. If you followed the steps above exactly, you can set this value as10.0.0.0/24
. This will reserve the IPs10.0.0.0
to10.0.0.255
to this Subnet. - If you want to, you can create more subnets using
Add new subnet
but it's not needed going forward. If you do choose to do so, you'll need to choose a different non-overlapping range of IPv4s for theIPv4 CIDR block
option - for example, you could set it to:10.0.1.0/24
to reserve the IPs10.0.1.0
to10.0.1.255
. - Finally, create the subnet.
- Click on
-
You need to update your
Route Tables
to give make your subnets publicly accessible.- Click on
Route Tables
in the VPC Dashboard - Select the entry corresponding to the VPC you created above.
- Navigate to the
Routes
tab. - Click on
Edit routes
. - Click on
Add route
. - Add
0.0.0.0/0
as theDestination
. - Select
Internet Gateway
underTarget
and link to the Internet Gateway you created above. - Click on
Save routes
.
- Click on
-
-
Set up the database. Click on
Databases
on the AWS RDS page.- Click on
Create Database
. - Use
Standard create
the in database creation method. - Use Postgres12 in the DB engine.
- Under templates, go with
Production
. - Name the DB instance identifier as
plio
. - Set the master user name and password.
- For DB instance, we can go with Burstable classes to get started with. Select the one having vCPUs & RAM as per your needs.
- Set the alloted storage as per your needs.
- For Availability and Durability, use multi-AZ deployment if you need. Take a note that this may almost double your monthly expense.
- Under VPC settings, select the VPC created in previous step.
- In case you prefer to connect to your database directly from your machine, set the public access to
Yes
. - Under additional configuration, set initial database name as
plio_staging
. This will create an empty database. - Finally, create the RDS instance by clicking on
Create Database
button. - You will see the new RDS instance in the list of RDS instances. Click on your DB instance to see the connectivity settings.
- To connect to RDS from command line, run the following command (this will work if you've set public access to yes):
psql -h your-db-instance-endpoint.aws-region.rds.amazonaws.com -p 5432 -U master_username
- Once you are logged in into the PSQL CLI, you can run all the SQL commands to create a new database, user, grant privileges etc.
- Click on
-
Create a new Elastic IP by going to EC2 dashboard and navigating to the
Elastic IP
section.- Click on
Allocate Elastic IP address
and click onAllocate
. - You will see a new IP address in the IPs list. Name it
plio-backend-staging
. - This will be used in later steps to give the load balancer a permanent IP address.
- Click on
-
Go to Target groups.
- Create a new target group.
- Choose target type to be
IP addresses
. - Name the target group as
plio-backend-staging
. - Set the protocol to
TCP
and port to80
. - Select the
plio-staging
for the target group VPC. - In the next step, add an IP address in the
IP
text area - the IP address should belong to your VPC - if you followed the steps above exactly, you can use any IP address between10.0.0.0
to10.0.255.255
. - Proceed to create the target group. You will see the created target group in the list of all target groups.
-
Go to Load Balancers (LBs).
- Create a new load balancer.
- Select
Network Load Balancer
option. We use NLB for easy support of web socket connections. - Name the LB as
plio-backend-staging
. - Select the
plio-staging
VPC for the load balancer. - In the subnet mappings, check the first desired zone and use Elastic IP under IPv4 settings for that subnet.
- Under listeners and routing, select the target group
plio-backend-staging
for TCP port 80. - Proceed to create the load balancer. You will see the created load balancer in the list of all load balancers.
- Enable access logs for your load balancer (optional, recommended for production)
- Go to your S3 dashboard and create a new bucket. Name it as
plio-nlb-logs
. - Enable server-side encryption with Amazon S3-Managed Encryption Keys (SSE-S3).
- All public access to be blocked. Click on
Create
button. - After creating the bucket, edit the bucket policy from
Permissions
tab and add the following. Make sure to edit the bucket name below if you have named it different. This policy is to allow bucket put objects permissions to the load balancer logger.
{ "Version": "2012-10-17", "Statement": [ { "Sid": "AWSLogDeliveryWrite", "Effect": "Allow", "Principal": { "Service": "delivery.logs.amazonaws.com" }, "Action": "s3:PutObject", "Resource": "arn:aws:s3:::plio-nlb-logs/*" }, { "Sid": "AWSLogDeliveryAclCheck", "Effect": "Allow", "Principal": { "Service": "delivery.logs.amazonaws.com" }, "Action": "s3:GetBucketAcl", "Resource": "arn:aws:s3:::plio-nlb-logs" } ] }
- Go back to the load balancer dashboard and select your load balancer.
- Select the load balancer from the list and click on
Edit Attributes
from theActions
dropdown. - Enable
Access logs
option. - Enter the S3 location you created in previous step. If you want to use same bucket for multiple load balancer access logs, add a sub-directory after S3 bucket name in this step. For example:
plio-nlb-logs/plio-backend-staging
.
- Go to your S3 dashboard and create a new bucket. Name it as
-
Go to ECR and create a new repository named
plio-backend-staging
and set the settings as per your needs. -
Now go to ECS and create a new task definition
- Select Fargate and name the task definition as
plio-backend-staging
. - Set the task role as
ecsTaskExecutionRole
. - Set the task memory and task CPU based on your needs. Good defaults to use are: 2 GB for memory and 0.5 vCPU.
- Create a new container with name
plio-backend-staging
. - In the image field, you can just type in
image_arn
. This is not a valid entry and just a placeholder for now as it'll be replaced by the actual image ARN once the GitHub workflow triggers. - Enter port
80
in the port mapping field. - Use the
.env.example
file to set all the required environment variables for your container in theEnvironment Variables
section. - Leave the variables
REDIS_HOSTNAME
andREDIS_PORT
as empty for now. We'll fill them up later. - Save the container definition and the task definition.
- You will see the new task definition within the list of all task definitions.
- Select Fargate and name the task definition as
-
Under ECS, go to
Clusters
and create a new cluster with the nameplio-staging-cluster
. (skip this step if you've already created a Cluster when setting up the frontend repository)- Select
Networking only
. We will go with serverless deployment so that we don't worry about managing our own server instances. - Don't create a new VPC for your cluster. We'll use the VPC created in previous step in the next step of creating a service.
- Click on the create button.
- You will see the new cluster within the list of clusters.
- Select
-
Get into
plio-staging-cluster
and create a new service.- Set launch type to Fargate. We'll use serverless deployments for Plio.
- Name the service as
plio-backend-staging
. - Under task definition, select
plio-backend-staging
and use latest revision. - Set the number of tasks to be one.
- Service type to be
REPLICA
. - Minimum healthy percentage should be 100 and maximum percent to be 200. Minimum healthy percentage defines the percentage of tasks that should be running at all times. Maximum percent defines how many additional tasks the service can create in parallel to running tasks, before killing the running tasks.
- Deployment type to be
rolling update
. - Keep other values as default.
- Use the Cluster VPC and the subnet that you configured previously with Elastic IP.
- Set Auto-assign public IP to
ENABLED
. Otherwise, it makes the task accessible only through VPC and not public. - Under load balancing, select the
Network Load Balancing
option and select theplio-backend-staging
load balancer. - Inside
Container to Load Balancer
, click onAdd to load balancer option
and selectplio-backend-staging
in the target group. - For auto-scaling, go with
Do not adjust the service's desired count
for staging. - Review and create the service.
-
Create a
redis
cluster on AWS Elasticache.- Go to the ElastiCache dashboard. Select the region and click on
Create
button. - Select
Redis
as your Cluster engine. - Select
Cluster mode disabled
. More details on cluster mode here. - Choose
Amazon Cloud
as your location. - Set the name of your cluster as
plio-redis-staging
. - Choose the latest
Engine version compatibility
. - Enter
6379
as thePort
. - No need to change anything for
Parameter Group
. - Choose your
Node Type
. Plio usescache.t2.micro
. More details here. - For
Number of replicas
, enter0
. Note: Each replica node will carry it's own cost. More on pricing here and here - For high availability, select the
Multi AZ
checkbox. Take a note that this may almost double your monthly expense. More details here. - Select
Advanced Redis settings
and fill in the details as mentioned below.- For
Subnet group
, create a new group. - Add the subnets that were created above.
- For
VPC ID
, add the VPC that was created above. - For
Availability zone(s)
, chooseNo preference
. More details here. - For
Security groups
choose the default security group.
- For
- Click the
Create
button. - Proceed when the cluster's status becomes
available
. Next we'll grant access to the cluster. - Go to the AWS EC2 console here.
- In the navigation pane, under
Network & Security
, chooseSecurity Groups
. - Choose the
default
security group for your VPC. - Choose the
Inbound
tab, and then do the following:- Choose
Edit
and selectAdd Rule
. - In the
Type
column, chooseCustom TCP rule
. - In the
Port range
box, type6379
. - In the
Source
box, chooseAnywhere
. - Click
Save
.
- Choose
- Find the endpoints of your redis instance using this and copy them somewhere for later use.
- Enable access logs for your redis instance (optional, recommended for production)
- Switch to the Redis listing screen and click on your redis instance
- Switch to
Logs
tab. - Click on
Enable slow log
button. - Select log format as
Text
(orJSON
if you prefer that). - Select destination type as
CloudWatch Logs
. - Set Log destination as
Create new
and enter then name of the log group that should be created. For example:/redis/plio-staging
. - Click on save and you should see the log status as "enabling". Wait for some time and it should turn to "active".
- Click on
log destination
link to access the log stream for the redis instance.
- Go to the ElastiCache dashboard. Select the region and click on
-
Connect the redis instance to the
plio-backend-staging
service that was created above.- Go to the ECS dashboard and select
plio-staging-cluster
. - Select the
plio-backend-staging
service. - Select the
Tasks
tab. - Select the running task definition in the
Task Definition
column. - Click
Create new revision
button on the top and select the existing container with the name:plio-backend-staging
. - In the new window, scroll down to
Environment Variables
section. - Update the
REDIS_HOSTNAME
andREDIS_PORT
keys that were copied before. - Click the
Update
button to update the environment variables. - Click the
Create
button to create a new revision of the task definition. - Navigate back to the service
plio-backend-staging
. - Click
Update
on top right. - Update the revision of the newly created task definition and wait for it to get initiated.
- After the new revision starts running, stop the older revision.
- Go to the ECS dashboard and select
-
Next, go to your GitHub repository and create a new environment from the settings tab.
- Name the environment as
Staging
. - Make sure you have added the following GitHub secrets on repository level. If not, add these as your environment secrets.
- AWS_ACCESS_KEY_ID
- AWS_SECRET_ACCESS_KEY
- AWS_REGION
- AWS_STORAGE_BUCKET_NAME
- Name the environment as
-
We are using Github Actions to trigger deployments. You can find the workflow defined in
.github/workflows/deploy_to_ecs_staging.yml
. It defines a target branch such that a deployment is initiated whenever a change is pushed to the target branch. -
Once done, push some changes to the target branch so that the GitHub workflow
deploy_to_ecs_staging.yml
gets triggered.
Setting up a production environment on AWS is almost the same as staging. Additionally, take care of the following things:
- Rename all services as
plio-backend-production
or a similar naming convention. - Change all the environment variables to the corresponding values for the production environment.
- We chose to use the same RDS instance for production and staging, but a separate user and database for each environment to optimize for cost.
- Once the RDS instance is created after following the step above, create the production DB in the same instance.
- Connect to your RDS instance
psql -h your-db-instance-endpoint.aws-region.rds.amazonaws.com -p 5432 -U master_username
- Ensure that you are in the
psql
shell - now you are connected to your RDS instance. - Create a new database using the
psql
shellpostgres=> CREATE DATABASE plio_production;
- Create a new user
postgres=> CREATE USER postgres_prod WITH PASSWORD 'YOUR_PASSWORD';
- Grant the newly created user all the privileges to the production DB
postgres=> GRANT ALL PRIVILEGES ON DATABASE plio_production TO postgres_prod;
- Go with auto-scaling option when creating a new service from ECS.
- When creating a service or when updating it, navigate to the service auto-scaling section.
- Select
Configure Service Auto Scaling to adjust your service's desired count
. - Set minimum number of tasks to
1
. This is the minimum count of running tasks when scale-in operation happen. - Set desired number of tasks to
1
. This may come pre-populated if you had set it before. - Set maximum number of tasks to
2
or more based on your desired need. This is the maximum count of total running tasks in case of scale-out operation. - In IAM role for Service Auto Scaling, select
AWSServiceRoleForApplicationAutoScaling_ECSService
. - Click on
Add scaling policy
. - Select policy type to
Target tracking
. - Set policy name to
plio-backend-production-autoscaling-cpu
. - In the ECS service metric, select the option for average CPU utilization.
- Target value should be
60
(or as per your requirement). This is the threshold value when the service will trigger a new event and perform scale-out operation. - Scale-out & Scale-in cooldown period to be
300
seconds. Disable scale-in
to remain unchecked.- Save the scaling policy.
- Create or update the service name.
- Use k6.io or other load testing tool to check if auto-scaling is working fine or not. You can lower down the target threshold for testing purposes.