DEV Community

Cover image for Deploying Yii2 Application to Amazon ECS and AWS Fargate using AWS CDK - Building Modern PHP/Yii2 Application using AWS
Petra Barus
Petra Barus

Posted on • Updated on

Deploying Yii2 Application to Amazon ECS and AWS Fargate using AWS CDK - Building Modern PHP/Yii2 Application using AWS

This is my first tutorial posted at dev.to. I am currently building a series of tutorial on Building Modern PHP/Yii2 Application using AWS posted in my Personal Blog but in Indonesian language. Since I am now trying to improve my English, I will post the translated version here.

The series will follow a journey building a Yii2-based application from scratch to full application using AWS services. In this case we will use Amazon Elastic Container Service and AWS Fargate to host the application. We also will use AWS Cloud Development Kit to define the AWS resources we need to run the application. The application will follow some best practices and opinions from my personal experience.

I assume the readers already have AWS account and knows a bit about AWS, Docker, and PHP. Let's get started.

Here's the software that I used when building this tutorial

  • AWS CLI 1.16
  • PHP 7.3
  • Composer 1.19
  • Node 10
  • Node Package Manager 6.9
  • AWS CDK 1.19
  • Docker 19.03
  • Docker Compose 1.25

Installing Yii2 Basic App Template

In this series, I will build the Yii2 application using the yii2-app-basic template. As suggested by the name, it's a basic template that contains one single web application with small functionalities like home page, menu, login, and register.

To install the template, execute the command below.

composer create-project --prefer-dist yiisoft/yii2-app-basic myapp

We will now have a fresh Yii2 application project in myapp directory.

Let's try to run the application in local. In the myapp directory, execute the command below.

docker-compose up

This command will take a while to download the container image for running the application. It will then show logging line like this.

docker log result

You can now access the application through URL http://127.0.0.1:8000. It should look like this.

screenshot 1

Running AWS CDK

Adding CDK Script

To run the CDK you will need the scripts and some configurations. I already made a patch that you can apply on your project. Execute the command below.

curl -L -O http://bit.ly/modern-yii2-app_part-1_patch
patch -p1 < modern-yii2-app_part-1_patch

This patch will installs 9 files; 3 files for NodeJS and Typescript configuration, 3 files for Docker configuration, 1 .gitignore, and we have only 2 for the CDK itself.

the patch files

Installing Requirements

After the scripts are patched, we now need to install Node libraries. Execute the command below.

npm install

To see if the CDK can be run, execute the command below.

cdk ls

The command will show a text WebStack. Up to this point, you are in the correct path. Please let me know if you have difficulty so far.

Because the current sample I wrote need a staging environment for the CDK itself to store data and configurations, we need to do bootstrapping first. The command below will try to create S3 bucket required for the CDK.

cdk bootstrap

It will show something like this.

bootstrap

Deployment

Now we try to deploy the application into AWS. You simply execute the command below.

cdk deploy

The command will show first changes of configuration that will be made, such as IAM and Security Group. It will ask you if you agree with the change or not. The configurations will affect the resources that you will provision, not existing in your account. Press y to continue.

confirmation

We are now going to the deployment process. This process can take some time. It will first build the docker image locally and push to Amazon Elastic Container Repository. For Internet connection in Indonesia, this can take really while, that is why in the next series I will write about how to run the process in build server.

build image

It then will provision the required resources like VPC, Subnets, ECS Clusters, and Load Balancer.

When it finished, the command will show the URL to the load balancer.

terminal shows finish

Open the URL and see that the Yii2 application is now deployed.

browser showing the app

If we open the Amazon ECS console, we can see a new ECS cluster has been provisioned.

ecs console

We can also see the service and task created after we opened the details for the cluster. It is displayed in the screenshot below that we now have 1 Fargate task to run the PHP application.

ecs task screenshot

Redeploying

The command cdk deploy can also be executed again after we modified the code, say updating the PHP, HTML, JS, etc. Because the resources have been provisioned in the first deployment, the subsequent deployments will not take as much time unless you add new resources.

Deleting Resource

To avoid additional cost incurring, we need to destroy the resource if we don't need it again. We simply execute command below.

cdk destroy

Explanation

From all the files that we patched earlier, the one first one I like to explain is the app.ts. This Typescript file declares the infrastructure that we want to provision to run the application.

#!/usr/bin/env node
import 'source-map-support/register';
import cdk = require('@aws-cdk/core');
import ecs = require("@aws-cdk/aws-ecs");
import ecsPatterns = require("@aws-cdk/aws-ecs-patterns");

class WebStack extends cdk.Stack {
    constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
        this.createECSCluster();
    }

    createECSCluster() {
        const ecsCluster = new ecs.Cluster(this, 'ecsCluster');
        new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
            cluster: ecsCluster,
            taskImageOptions: {
                image: ecs.ContainerImage.fromAsset('.')
            },
        });
    }
}

const app = new cdk.App();
new WebStack(app, 'WebStack');

app.synth();

Today AWS CDK only supports Javascript/Typescript, Python, Java, and .NET. There is no support for PHP yet. But you want to see AWS CDK in PHP as much as I do, please send your vote in this Github issue.

With very simple code shown above we can have application with complete architecture similar to below.

architecture

Image taken from blog post Task Networking in AWS Fargate

Throughout the process, the code will be synthesized to CloudFormation template that can reach 700 lines like this. To see the generated CloudFormation template, execute the command below.

cdk synth

Construct

Now let us start with a simple line taken from app.ts.

const ecsCluster = new ecs.Cluster(this, 'ecsCluster');

The code above shows we are instantiating class ecs.Cluster. In AWS CDK, this is called as Construct. Construct is a building block that represents one or more components. It is useful to encapsulate multiple cloud components to be reusable as one unit. When building more complex architecture we can build a construct that composes multiple construct.

Stack

Next we will see the class WebStack.

export class WebStack extends cdk.Stack {
  constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);
    //....
  }
  //....
}

In this part, we define what we call as Stack. A stack is unit that defines the CloudFormation stack that we need to provisioned the resources. You can see in the CloudFormation console, that we have a new stack called WebStack, just like the one we instantiate.

cloudformation

App

In the last three lines, we instantiate what we call as App.

const app = new cdk.App();
new WebStack(app, 'WebStack');

app.synth();

The app will represent CDK application that we are building and defines the scope of the Stacks. An App can have more than one Stack. This is very useful to separate multiple parts of an application, for example the web stack and worker stack. In the code sample below we defines two stacks of which we name MyStack1 and MyStack2 respectively.

const app = new cdk.App();
new Stack1(app, 'MyStack1');
new Stack2(app, 'MyStack2');
app.synth();

If we execute cdk ls, we can see the two stacks will be displayed. It is also possible to deploy the two stacks in separate commands.

$cdk deploy MyStack1
$cdk deploy MyStack2

High Level Constructs

In the code app.ts, class ApplicationLoadBalancedFargateService is also a construct but represents very complex architecture. A construct can compose of many other construct. We can imagine that the ApplicationLoadBalancedFargateService can have other constructs for VPC, Subnet, Internet Gateway, NAT Gateway, ENI, Load Balancer, and Fargate. By using that construct, we don't have to worry about orchestrating all of the other constructs. We only need to instantiate the construct and give some parameters.

 const ecsCluster = new ecs.Cluster(this, 'ecsCluster');
        new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
            cluster: ecsCluster,
            taskDefinition: {
                cpu: 512,
                memoryLimitMiB: 1024
            },
            taskImageOptions: {
                image: ecs.ContainerImage.fromAsset('.')
            },
        });

The code above shows that we gives parameters to Fargate task specifying that we need CPU with 512 units and memory 1024 MiB.

Class ApplicationLoadBalancedFargateService is just an example of a construct already provided by aws-ecs-patterns library. The library also provides other patterns for ECS that we can choose. If we need EC2 for compute rather than Fargate, we can use ApplicationLoadBalancedEc2Service. Or if we choose to use Network Load Balancer instead of Application Load Balancer, because we are building games using UDP for protocol, we can use NetworkLoadBalancedFargateService. You can see the list of the patterns here.

Decomposing Using Construct

In the future we will definitely add more resource definition into the CDK code. Construct will help us to decompose the code so our code will stay simple and readable. The decomposition will further enable us for reusing the piece of code and even share it to other developers in form of libraries.

For example, the part in the app.ts where we define the ECS cluster and fargate can be refactor into new Construct, let's say WebECSCluster. We can also move the class to other file so our main file app.ts will stay short.

class WebECSCluster extends cdk.Construct {
    constructor(scope: cdk.Construct) {
        super(scope, "WebECSCluster");

        const ecsCluster = new ecs.Cluster(this, 'ecsCluster');
        new ecsPatterns.ApplicationLoadBalancedFargateService(this, 'Service', {
            cluster: ecsCluster,
            taskImageOptions: {
                image: ecs.ContainerImage.fromAsset('.')
            },
        });
    }
}


class WebStack extends cdk.Stack {
    constructor(scope: cdk.Construct, id: string, props?: cdk.StackProps) {
        super(scope, id, props);
        new WebECSCluster(this);
    }
}

That is all for now. Please drop your comments or questions in the comment section below. I'd love to hear what you think I can improve in the next articles!

References

You can learn more about AWS CDK here

Top comments (0)