Lift and Shift to AWS with IaC
Tags: aws, devops, docker, microservices
Published: 2023-07-24 11:54:21

## 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](https://aws.amazon.com/blogs/enterprise-strategy/6-strategies-for-migrating-applications-to-the-cloud/)

In this article case we're going to see the Open Source project of E-Commerce [Medusa](https://medusajs.com/) Backend Server and an Admin Panel

![Medusa Architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/j4zp0oyce11hdshyvsj4.png)


## 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


```dockerfile
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


```typescript
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


```typescript

// 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`



![Resources Graph](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7s8pzwjs6o0xdpnbpxwc.png)

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.**