Containers

Running Adobe ColdFusion applications on Amazon ECS with blue/green deployments

Introduction

Adobe ColdFusion is a web development tool that gives developers the ability to develop and deploy cloud-native applications with ease. In this post, we will demonstrate how you can run your Adobe ColdFusion applications on Amazon Elastic Container Service (Amazon ECS)  with AWS Fargate, as well as, use AWS CodeDeploy to perform blue/green deployments.

By containerizing your web application, you can break apart monolithic applications, accelerate your software development, and modernize your deployment practices using automation. AWS Fargate is a serverless compute engine for containers that works with both Amazon ECS and Amazon Elastic Kubernetes Service (Amazon EKS). Fargate removes the need to provision and manage servers, giving you time back to focus on building your applications.

You can learn more about the advantages of modernizing applications with containers from this whitepaper, and the blue/green deployments from this whitepaper.

Architecture

Prerequisites

Before you get started, you need to have the following prerequisites in place.

  1. AWS CLI v2 configured
  2. AWS ECS CLI configured
  3. Local Docker setup for image build(for MacOS users)
  4. An AWS IAM user with the appropriate IAM policies for Amazon ECS, Amazon Elastic Container Registry (Amazon ECR), AWS CodeDeploy, Amazon S3, Amazon EC2 Security Groups, and Application Load Balancers
  5. If you haven’t already, create a VPC with two public subnets that each have at least 10 IP addresses available

Note: This blog post deploys all resources to the us-east-1 Region. To use a different Region, ensure that all required services are available in that Region and then replace all references the Region that you plan to use for the deployment.

Step 1: Create a new Docker image using the Adobe ColdFusion base image

For this tutorial, you will use the official Adobe ColdFusion application Docker base image and add a test web page into its web root directory using a Dockerfile.

Create a directory for the ColdFusion base image.

mkdir coldfusion-image
cd coldfusion-image

Create the file Dockerfile and include the following.

Dockerfile

FROM eaps-docker-coldfusion.bintray.io/cf/coldfusion:latest
WORKDIR /app/
COPY test.cfm .

Create the file test.cfm and include the following.

test.cfm

<cfscript>
helloWorldStr = createObject("java", "java.lang.String");
helloWorldStrObj = helloWorldStr.init("Hello world!")
</cfscript>
<cfdump var="#helloWorldStrObj#"><br />

Build your new Docker image from the Dockerfile.

docker build -t myrepo/cfusion .

You then have to create an Amazon Elastic Container Registry (Amazon ECR) repository before pushing the container image you just created.

 aws ecr create-repository --region us-east-1 --repository-name myrepo/cfusion

Once the above command has been run successfully, the returned output will contain the repository URL, which you can then use to push the image into an ECR repository.

Retrieve an authentication token and authenticate your Docker client to your registry.

aws ecr get-login-password --region us-east-1 | docker login --username AWS \
--password-stdin {{AWS_ACCOUNT_ID}}.dkr.ecr.us-east-1.amazonaws.com

After the build completes, tag your image so you can push the image to this repository:

docker tag myrepo/cfusion:latest {{AWS_ACCOUNT_ID}}.dkr.ecr.us-east-1.amazonaws.com/myrepo/cfusion:latest

Run the following command to push this image to your newly created AWS repository:

docker push {{AWS_ACCOUNT_ID}}.dkr.ecr.us-east-1.amazonaws.com/myrepo/cfusion:latest

Step 2: Create an application load balancer for the ECS blue/green deployment

Amazon ECS services using the blue/green deployment type require the use of either an Application Load Balancer or a Network Load Balancer. For this tutorial, we will use an Application Load Balancer.

Create a security group for the Application Load Balancer. Replace the {{VPC_ID}} with the identifier for the VPC that you plan to use for the deployment.

aws ec2 create-security-group --description coldfusion \
	--group-name cf-alb-sg \
	--region us-east-1 \
	--vpc-id {{VPC_ID}}

Create a rule in the security group to allow inbound access. Replace the {{SEC_GRP_ID_1}} with the identifier for the security group created in the previous step.

Note: This ingress rule allows inbound connections from any IPv4 address. To improve your security posture, you should restrict inbound access to allow connections only from specific IP address, or ranges.

aws ec2 authorize-security-group-ingress \
	--protocol tcp --port 80 --cidr 0.0.0.0/0 \
	--region us-east-1 \
	--group-id {{SEC_GRP_ID_1}} 

Create an Application Load Balancer and attach the security group created previously. Replace {{SUBNET_ID_1}} and {{SUBNET_ID_2}} with the identifiers for two subnets in different Availability Zones in your VPC that have at least 10 IP addresses available.

aws elbv2 create-load-balancer \
     --name coldfusion-service-alb \
     --region us-east-1 \
     --subnets {{SUBNET_ID_1}} {{SUBNET_ID_2}} \
     --security-groups {{SEC_GRP_ID_1}}

Make a note of the load balancer ARN from above command output. Create target group for load balancer to route traffic.

aws elbv2 create-target-group \
     --name cfusion-target-group1 \
     --protocol HTTP \
     --port 80 \
     --target-type ip \
     --region us-east-1 \
     --vpc-id {{VPC_ID}} 

Make a note of the target group ARN from the above command. Create a load balancer listener to forward requests to the target group create with the previous command. Replace the {{LOAD_BALANCER_ARN}} and {{TARGET_GRP_ARN}} with the ARNs returned by the previous two commands.

aws elbv2 create-listener --protocol HTTP --port 80 \
     --region us-east-1 \
     --load-balancer-arn {{LOAD_BALANCER_ARN}} \
     --default-actions Type=forward,TargetGroupArn={{TARGET_GRP_ARN}}

Step 3: Create the ECS task execution IAM role

The Amazon ECS container agent makes calls to AWS APIs on your behalf and requires an IAM policy and role for the service to know that the agent belongs to you. This IAM role is referred to as a task execution IAM role. If you already have a task execution role created to use, you can skip this step. For more information, see Amazon ECS task execution IAM role.

To create the task execution IAM role using the AWS CLI

Create a file named task-execution-assume-role.json with the following trust relationship policy document:

task-execution-assume-role.json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "",
      "Effect": "Allow",
      "Principal": {
        "Service": "ecs-tasks.amazonaws.com"
      },
      "Action": "sts:AssumeRole"
    }
  ]
}

Run the following command to create the task execution role name ecsTaskExecutionRole.

aws iam create-role --role-name ecsTaskExecutionRole \
	--assume-role-policy-document file://task-execution-assume-role.json \
	--region us-east-1 

Attach the task execution role policy to the role you just created.

aws iam --region us-east-1 attach-role-policy --role-name ecsTaskExecutionRole \
	--policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

Step 4: Create an ECS cluster

An ECS cluster is a logical grouping of tasks or services. In this post, you will use Fargate to launch your containers in a serverless environment. When you first use ECS, a default cluster is created for you, but you can create multiple clusters in an account to keep your resources separate.

Create an ECS cluster configuration using the ECS CLI.

ecs-cli configure --cluster coldfusion-ecs-cluster \
--default-launch-type FARGATE \
--config-name coldfusion-ecs-cluster \
--region us-east-1

Create an ECS cluster with the ecs-cli up command. Since you specified Fargate as your default launch type in the cluster configuration, this command creates an empty cluster using the existing VPC with two public subnets.

ecs-cli up --cluster-config coldfusion-ecs-cluster \
	--vpc {{VPC_ID}} \
	--subnets {{SUBNET_ID_1}}, {{SUBNET_ID_2}}

Now that you have created the ECS cluster, the command should return a message stating Cluster creation succeeded. You can also confirm this by navigating to the ECS dashboard in the AWS console.

Using the AWS CLI, create a second security group for the ECS service and add a security group rule to allow inbound access on port 80 and 8500 for the blue/green deployment in your VPC.

aws ec2 create-security-group --description coldfusion \
	--group-name cf-ecs-cluster-sg \
	--region us-east-1 \
	--vpc-id {{VPC_ID}}

The above command will return the security group id that you will use to create the rules to allow inbound access. If your service does not need to be exposed to the whole Internet, the CIDR range should be changed to a smaller range.

aws ec2 authorize-security-group-ingress \
	--protocol tcp --port 80 --cidr 0.0.0.0/0 \
	--region us-east-1 \
	--group-id {{SEC_GRP_ID_2}} 

aws ec2 authorize-security-group-ingress \
	--protocol tcp --port 8500 --cidr 0.0.0.0/0 \
	--region us-east-1 \
	--group-id {{SEC_GRP_ID_2}} 

Step 5: Create the ECS task definition and Amazon CloudWatch Log Group

First, create a secret for your ColdFusion admin UI password using AWS Secrets Manager. This secret will be used in the ECS task definition later.

aws secretsmanager create-secret --region us-east-1 \
	--name cfusionpass \
	--secret-string '{{YOUR_STRONG_PASSWORD}}'

Then, make sure that the ECS task IAM role has the appropriate inline IAM policy attached to be able to get the secret from Secrets Manager. Create the sample policy JSON file below and replace the ARN address with the ARN from when you created the secret in the previous step. Save the file locally so that it can be referenced in the next command.

ecs-role-policy.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "secretsmanager:GetSecretValue"
            ],
            "Resource": [
                "{{SECRETS_MGR_SECRET_ARN}}"
            ]
        }
    ]
}

Attach the above policy to the ECS task execution IAM role.

aws iam put-role-policy --role-name ecsTaskExecutionRole \
	--policy-name ecs-task-role-secret \
	--policy-document file://ecs-role-policy.json

Then use the JSON file below to register a task definition for your ColdFusion application in ECS.

cfusion-ecs-task-def.json

{
  "requiresCompatibilities": [
    "FARGATE"
  ],
  "containerDefinitions": [
    {
      "name": "cfusion",
      "image": "{{AWS_ACCOUNT_ID}}.dkr.ecr.us-east-1.amazonaws.com/myrepo/cfusion:latest",
      "memory": 1024,
      "cpu": 512,
      "essential": true,
      "secrets": [
        {
          "name": "password",
          "valueFrom": "{{SECRETS_MGR_SECRET_ARN}}"
        }
      ],
      "portMappings": [
        {
          "containerPort": 8500,
          "protocol": "tcp"
        }
      ],
      "environment": [
        {
          "name": "acceptEULA",
          "value": "YES"
        }
      ],
      "logConfiguration": {
        "logDriver": "awslogs",
        "options": {
          "awslogs-group": "coldfusion",
          "awslogs-region": "us-east-1",
          "awslogs-stream-prefix": "coldfusion_ecs"
        }
      }
    }
  ],
  "volumes": [],
  "taskRoleArn": "ecsTaskExecutionRole",
  "executionRoleArn": "ecsTaskExecutionRole",
  "networkMode": "awsvpc",
  "placementConstraints": [],
  "family": "cfusion",
  "memory": "1024",
  "cpu": "512"
}

Register the ColdFusion app task definition by running the command below, referencing the above JSON file.

aws ecs register-task-definition --region us-east-1 --cli-input-json file://cfusion-ecs-task-def.json

Once completed, you can confirm that a task definition named cfusion has been registered successfully by opening the Amazon ECS page in the AWS Management Console.

You then need to create an Amazon CloudWatch Log Group for all the task containers logs to be stored in.

aws logs create-log-group --region us-east-1 --log-group-name coldfusion

Step 6: Create an Amazon ECS service

Create the file below with the following content and make sure to replace the ID placeholders with those for your environment. Note that we specify the deploymentController as CODE_DEPLOY so that you can perform blue/green deployments with CodeDeploy later.

coldfusion-service.json

{
  "cluster": "coldfusion-ecs-cluster",
  "serviceName": "cfusion-service",
  "taskDefinition": "cfusion",
  "loadBalancers": [
    {
      "targetGroupArn": "{{TARGET_GROUP_ARN}}",
      "containerName": "cfusion",
      "containerPort": 8500
    }
  ],
  "launchType": "FARGATE",
  "schedulingStrategy": "REPLICA",
  "deploymentController": {
    "type": "CODE_DEPLOY"
  },
  "platformVersion": "LATEST",
  "networkConfiguration": {
    "awsvpcConfiguration": {
      "assignPublicIp": "ENABLED",
      "securityGroups": [
        "{{SEC_GRP_ID_2}}"
      ],
      "subnets": [
        "{{SUBNET_ID_1}}",
        "{{SUBNET_ID_2}}"
      ]
    }
  },
  "desiredCount": 1
}

Next, create a blue/green ECS service using the command below.

aws ecs create-service --region us-east-1 \
	--cli-input-json file://coldfusion-service.json

When you have verified that the service has deployed successfully, you will be able to view your web application by navigating to the load balancer endpoint:

http://coldfusion-service-alb-{{LOAD_BALANCER_ID}}.us-east-1.elb.amazonaws.com/test.cfm

Step 7: Create CodeDeploy resources

Create the AWS CodeDeploy application with Amazon ECS.

aws deploy create-application \
     --application-name cfusion-bluegreen-app \
     --compute-platform ECS \
     --region us-east-1

Create a second ALB target group. This will be used when creating the CodeDeploy deployment group. Remember to replace the VPC id in the command below.

aws elbv2 create-target-group \
     --name cfusion-target-group2 \
     --protocol HTTP \
     --port 80 \
     --target-type ip \
     --region us-east-1 \
     --vpc-id {{VPC_ID}} 

After this you will need to create a trust policy and service role for CodeDeploy to be able to deploy applications to ECS on your behalf.

codedeploy-trust-policy.json

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "",
            "Effect": "Allow",
            "Principal": {
                "Service": [
                    "codedeploy.us-east-1.amazonaws.com"
                ]
            },
            "Action": "sts:AssumeRole"
        }
    ]
}

Then, run the following command to create the IAM service role.

aws iam create-role --role-name ecsCodeDeployRole \
	--assume-role-policy-document file://codedeploy-trust-policy.json

Next, you need to attach the managed policy AWSCodeDeployRoleForECS to your newly created service role.

aws iam attach-role-policy --role-name ecsCodeDeployRole \
	--policy-arn arn:aws:iam::aws:policy/AWSCodeDeployRoleForECS

Next, create a file with the code deployment group specification. Please make sure to change account id and listener.

coldfusion-deployment-group.json

{
   "applicationName": "cfusion-bluegreen-app",
   "autoRollbackConfiguration": {
      "enabled": true,
      "events": [ "DEPLOYMENT_FAILURE" ]
   },
   "blueGreenDeploymentConfiguration": {
      "deploymentReadyOption": {
         "actionOnTimeout": "CONTINUE_DEPLOYMENT",
         "waitTimeInMinutes": 0
      },
      "terminateBlueInstancesOnDeploymentSuccess": {
         "action": "TERMINATE",
         "terminationWaitTimeInMinutes": 5
      }
   },
   "deploymentGroupName": "cfusion-bluegreen-dg",
   "deploymentStyle": {
      "deploymentOption": "WITH_TRAFFIC_CONTROL",
      "deploymentType": "BLUE_GREEN"
   },
   "loadBalancerInfo": {
      "targetGroupPairInfoList": [
        {
          "targetGroups": [
             {
                 "name": "cfusion-target-group1"
             },
             {
                 "name": "cfusion-target-group2"
             }
          ],
          "prodTrafficRoute": {
              "listenerArns": [
                  "{{LISTERNER_ARN}}"
              ]
          }
        }
      ]
   },
   "serviceRoleArn": "arn:aws:iam::{{AWS_ACCOUNT_ID}}:role/ecsCodeDeployRole",
   "ecsServices": [
       {
           "serviceName": "cfusion-service",
           "clusterName": "coldfusion-ecs-cluster"
       }
   ]
}
aws deploy create-deployment-group --cli-input-json \
    file://coldfusion-deployment-group.json \
    --region us-east-1

Then create the deployment by creating a CodeDeploy AppSpec file.

appspec.json

{
    "version": 0.0,
    "Resources": [
        {
            "TargetService": {
                "Type": "AWS::ECS::Service",
                "Properties": {
                    "TaskDefinition": "arn:aws:ecs:us-east-1:{{AWS_ACCOUNT_ID}}:task-definition/cfusion:1",
                    "LoadBalancerInfo": {
                        "ContainerName": "cfusion",
                        "ContainerPort": 8500
                    },
                    "PlatformVersion": "LATEST"
                }               
            }
        }
    ]
}

Note that if you have updated your task definition for any reason, you will also need to update the task definition revision number in the appspec.json file above. You can view the revisions for your cfusion task definition in the AWS Management Console by clicking here

If needed, create an Amazon S3 bucket to host deployment files.

aws s3api create-bucket \
    --region us-east-1 \
     --bucket {{YOUR_BUCKET_NAME}}

Note that if you use a Region other than us-east-1, you will need to create a LocationConstraint bucket configuration specifying the Region, as documented here.

Copy the application specification JSON file to your S3 bucket.

aws s3 cp appspec.json s3://{{YOUR_BUCKET_NAME}}/appspec.json

Create the deployment specification referencing the appspec.json file in your S3 bucket.

coldfusion-deployment.json

{
    "applicationName": "cfusion-bluegreen-app",
    "deploymentGroupName": "cfusion-bluegreen-dg",
    "revision": {
        "revisionType": "S3",
        "s3Location": {
            "bucket": "{{YOUR_BUCKET_NAME}}",
            "key": "appspec.json",
            "bundleType": "JSON"
        }
    }
}

Then, proceed to create a deployment using the command below.

aws deploy create-deployment \
    --cli-input-json file://coldfusion-deployment.json \
    --region us-east-1

The deployment command returns a deployment ID. You can check the progress of your blue/green deployment on the AWS CodeDeploy page of the AWS Management Console.

While this is happening, you can continue access your web application test page using ALB URL.

http://coldfusion-service-alb-{{LOAD_BALANCER_ID}}.us-east-1.elb.amazonaws.com/test.cfm

Step 8: Update and redeploy application

You now have a working blue/green deployment, and can update your application and redeploy the new version. Start by updating your web application code.

test.cfm

<cfscript>
helloWorldStr = createObject("java", "java.lang.String");
helloWorldStrObj = helloWorldStr.init("Hello blue/green deployment!")
</cfscript>
<cfdump var="#helloWorldStrObj#"><br />

Build a new Docker image. Make sure you running this from the directory containing the test.cfm and Dockerfile files.

docker build -t myrepo/cfusion .

If necessary, retrieve an authentication token.

aws ecr get-login-password --region us-east-1 | docker login --username AWS \
--password-stdin {{AWS_ACCOUNT_ID}}.dkr.ecr.us-east-1.amazonaws.com

After the build completes, tag your image so you can push the image to this repository:

docker tag myrepo/cfusion:latest {{AWS_ACCOUNT_ID}}.dkr.ecr.us-east-1.amazonaws.com/myrepo/cfusion:latest

Run the following command to push this image to your newly created AWS repository:

docker push {{AWS_ACCOUNT_ID}}.dkr.ecr.us-east-1.amazonaws.com/myrepo/cfusion:latest

Proceed to create a new deployment by running the command below again.

aws deploy create-deployment \
    --cli-input-json file://coldfusion-deployment.json \
    --region us-east-1

Once the deployment has shifted traffic to the new version, you can open the URL of the web application again and it will show the updated application.

Step 9: Clean up your “Hello World” application

You’ve now successfully made an update to the code and redeployed the application. To avoid ongoing charges for resources you created, you should delete the resources created as a part of this setup.

Amazon S3 buckets.

aws s3 rb s3://<YOUR-BUCKET-NAME>

CodeDeploy resources.

aws deploy delete-application --application-name cfusion-bluegreen-app

Amazon ECS cluster and ECR repository.

aws ecr delete-repository \
     --repository-name myrepo/cfusion –force \
     --region us-east-1

aws ecs delete-cluster --cluster coldfusion-ecs-cluster

Application Load Balancer and associated Target Groups

aws elbv2 delete-load-balancer \
     --load-balancer-arn <LOAD BALANCER ARN> \
     --region us-east-1

aws elbv2 delete-target-group \
     --target-group-arn <TARGET-GROUP1-ARN> \
     --region us-east-1

aws elbv2 delete-target-group \
     --target-group-arn <TARGET-GROUP2-ARN> \
     --region us-east-1

AWS Secrets Manager secrets.

aws secretsmanager delete-secret --secret-id cfusionpass \
     --region us-east-1

Security Groups.

aws ec2 delete-security-group --group-id <SECURTIY GROUP ID>

IAM roles and policies.

aws iam detach-role-policy \
       --role-name ecsTaskExecutionRole \
       --policy-arn arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy

aws iam delete-role-policy -\
       -role-name ecsTaskExecutionRole \
       --policy-name ecs-task-role-secret

aws iam delete-role --role-name ecsTaskExecutionRole \
       --region us-east-1

CloudWatch Logs and Log Groups.

aws logs delete-log-group \
       --log-group-name coldfusion \
       --region us-east-1 

Conclusion

Modernizing enterprise web applications can be complex and time consuming. With the approach from this post, you can modernize your applications with containers on AWS and blue/green deployments to speed up innovation at the same time as minimizing risks in your application deployments.

For further information on how to deploy applications on AWS, have a look at the AWS Developer Tools and Containers on AWS sites. You can also view the public AWS Containers Roadmap on GitHub.