What is Lift and Shift?
Lift and Shift, or re-hosting, is one of the strategies to migrate existing applications into the cloud, where the application is not modified, only the hosting platform is changed.
Check the next link for the 6'R of migrations: AWS Docs
In this article case we're going to see the Open Source project of E-Commerce Medusa Backend Server and an Admin Panel
What is Medusa?
Medusa is an open source project for building blocks of e-commerce, in order to not re-invent the wheel, which provides a extensive API's, Backoffice and the UI using SSR/SSG with Next.JS
How to migrate to AWS?
To migrate to AWS, we have to make some considerations before dropping that artifact in a compute and expose it with a DNS.
Considerations when migrating to AWS
1 - Operations
What things do we need to keep running in order to sustain the platform?
2 - Security
What security assessment or compliances i need to be comply to?
3 - Costs
What is my monthly and annually budget?
4 - Performance
Which is my desired traffic, or user consumption business goal?
5 - Availability
How is my application going to be available in case of a cloud provider failure?
6 - Resillience
Is my application going to be ready to fail, and keep running?
With that being said we can choose the right resources in AWS to make this lift-shift migration strategy
Ready?
Let's migrate.
Steps to migrate to AWS
Install Dependencies
npm install @medusajs/medusa-cli -g
Create the Medusa Backend Server
medusa new my-medusa-store
Dockerize the application
You can use the next Docker, i recommend to make this one with Multi-stage pattern for PROD and keep the platform in case the CI is running on arm64 if not, remove it
FROM --platform=linux/amd64 public.ecr.aws/docker/library/node:lts
# Create app directory
WORKDIR /usr/src/app
# Bundle app source
COPY . .
RUN npm install
# If you are building your code for production
# RUN npm ci --omit=dev
EXPOSE 80
CMD [ "node", "index.js" ]
Build Docker and Run it Locally
docker build -f ./Dockerfile. -t <myImage> --no-cache
docker run <myImage> -p 80:80
Deploy to AWS
For this architecture we decided to go with AWS ECS Launch Type Fargate, since the business needs a fully managed container service orchestration to handle the deployment, load balancing, where Amazon ECS will launch, monitor, and scale your application across flexible compute options with automatic integrations to other supporting AWS services that your application needs
*How we are going to deploy *
For deployment we're going to use best practice DevOps using Infrastructure as Code ( IaC ) with Pulumi
Run the next code at the root level
pulumi new aws-typescript --dir ./infra
First we need to create Roles, Repository of ECR and Image for our Docker
import * as pulumi from "@pulumi/pulumi";
import * as aws from "@pulumi/aws";
import * as awsx from "@pulumi/awsx";
const imageName = 'medusa-ecr-image';
const repositoryName = 'medusa-ecr-repository';
const repository = new awsx.ecr.Repository(repositoryName);
const imageArgs: awsx.ecr.ImageArgs = {
dockerfile: '../dockerfile',
path: '../',
repositoryUrl: repository.url,
};
const dockerImage = new awsx.ecr.Image(imageName, imageArgs);
// Define the Log Group Creator IAM
const logGroupCreatorPolicy = new aws.iam.Policy("logGroupCreatorPolicy", {
policy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: [
"*"
],
Effect: "Allow",
Resource: "*"
}]
})
});
// Create a new Role
const ecsRole = new aws.iam.Role("ecsRole", {
assumeRolePolicy: JSON.stringify({
Version: "2012-10-17",
Statement: [{
Action: "sts:AssumeRole",
Principal: {
Service: "ecs-tasks.amazonaws.com",
},
Effect: "Allow",
}],
}),
});
// Attach the log creator policy to the role
new aws.iam.RolePolicyAttachment("rolePolicyAttachment", {
role: ecsRole.name,
policyArn: logGroupCreatorPolicy.arn,
});
const executionECSRole = {
roleArn: ecsRole.arn,
};
Now we need to create:
- ECS Cluster
- Fargate Service
- Task Definition
- Load Balancer
// create ECS cluster
const clusterName = 'medusa-ecs-cluster';
const loadBalancer = 'medusa-ecs-load-balancer';
const ecsService = 'medusa-ecs-service';
const configLoadBalancer: awsx.lb.ApplicationLoadBalancerArgs = {
defaultTargetGroup: {
port: 80,
healthCheck: {
path: '/health',
}
},
enableWafFailOpen: true,
};
const lb = new awsx.lb.ApplicationLoadBalancer(loadBalancer, configLoadBalancer);
const cluster = new aws.ecs.Cluster(clusterName, {});
const service = new awsx.ecs.FargateService(ecsService, {
cluster: cluster.arn,
assignPublicIp: true,
desiredCount: 2,
taskDefinitionArgs: {
executionRole: executionECSRole,
containers: {
app: {
image: dockerImage.imageUri,
cpu: 512,
memory: 1024,
essential: true,
portMappings: [{
containerPort: 80,
hostPort: 80,
targetGroup: lb.defaultTargetGroup,
}],
healthCheck: {
command: ["CMD-SHELL", "curl --fail http://localhost/health || exit 1"],
interval: 30,
timeout: 3,
retries: 3
},
logConfiguration: {
logDriver: "awslogs",
options: {
"awslogs-create-group": "true",
"awslogs-group": "medusa-logs-group",
"awslogs-region": "us-east-1",
"awslogs-stream-prefix": "medusa-ecs-logs"
}
}
}
},
},
});
export const url = lb.loadBalancer.dnsName;
You can see important things here.
Load balancer config with healthcheck route, the definition of the container app with hardware resources and port mapping, this will allow that the Task Definition of the Cluster exposes a port to the cluster and to the container.
Last the container has a healthcheck inside where checks if the Docker is healthy
Now let's wrap this up!
pulumi up
Pulumi will expose the DNS for our service, check the /health route and it should be returning OK
.
Also check the /app which holds the Admin Panel.
Hope you enjoy it and learn how to Lift and Shift an App to AWS using ECS Fargate.
Top comments (0)