How to use CodeDeploy with AWS Lightsail

Photo by Brett Jordan on Unsplash

Categories

  • Howto

Automating the deployment of your application is key for fast development cycles. CodeDeploy is a service offered by AWS to perform this task. Unfortunately, AWS Lightsail Instances are not supported by default by CodeDeploy. This post shows how CodeDeplpy can be set up to deploy to AWS Lightsail Instances nevertheless.

About CodeDeploy

CodeDeploy is a service offered by AWS as part of the DevOps toolchain. It provides a way for performing deployments in a structured way. It supports different ways of doing the deployment - one server at a time, all at once, etc. It also provides ways to verify the success of a deployment and do a rollback in case of an error. In this regard, it’s a pretty powerful tool for automating the deployment of applications to your servers.

In general, CodeDeploy supports multiple kinds of operation. One of these is for deploying to EC2 or on-premise instances. Although technical speaking a Lightsail instance is just an EC2 instance it has to be registered as an on-premise one to integrate with CodeDeploy.

Setting this up involves multiple steps which are outlined below. All of these can either be performed through the AWS Console or the AWS CLI. To make things easier to reproduce the CLI commands are shown in the remainder of this blog post. Additionally, various things can also be set up using CloudFormation. This way leveraging CodeDeploy can be set up much easier and faster.

The basics

The deployment packages rolled out to the servers by CodeDeploy need to be stored in a S3 bucket. Therefore, the first step is to create this bucket. In this example it is called com.example.codedeploy.eu-central-1.

As we learn later an agent is installed to each server instance to which deployments should take place. The instance needs to have the permissions to get the deployment artifacts from the S3 bucket we’ve just mentioned. Therefore, each instance will be associated with a dedicated IAM User. All of these technical users will be assigned to an IAM Group. In the examples of this post this group is called LightsailCodeDeployUsers. With this IAM Group an IAM Policy is assigned which allows access to the S3 bucket with the deployment packages:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "s3:Get*",
        "s3:List*"
      ],
      "Resource": [
        "arn:aws:s3:::com.example.codedeploy.eu-central-1/*",
        "arn:aws:s3:::aws-codedeploy-us-east-2/*",
        "arn:aws:s3:::aws-codedeploy-us-east-1/*",
        "arn:aws:s3:::aws-codedeploy-us-west-1/*",
        "arn:aws:s3:::aws-codedeploy-us-west-2/*",
        "arn:aws:s3:::aws-codedeploy-ca-central-1/*",
        "arn:aws:s3:::aws-codedeploy-eu-west-1/*",
        "arn:aws:s3:::aws-codedeploy-eu-west-2/*",
        "arn:aws:s3:::aws-codedeploy-eu-west-3/*",
        "arn:aws:s3:::aws-codedeploy-eu-central-1/*",
        "arn:aws:s3:::aws-codedeploy-ap-east-1/*",
        "arn:aws:s3:::aws-codedeploy-ap-northeast-1/*",
        "arn:aws:s3:::aws-codedeploy-ap-northeast-2/*",
        "arn:aws:s3:::aws-codedeploy-ap-southeast-1/*",
        "arn:aws:s3:::aws-codedeploy-ap-southeast-2/*",
        "arn:aws:s3:::aws-codedeploy-ap-south-1/*",
        "arn:aws:s3:::aws-codedeploy-sa-east-1/*"
      ]
    }
  ]
}

To create the technical users, assign them to the group and create the appropriate access credentials, the following commands can be used. Of course, they need to be executed for each user:

aws iam create-user --user-name "CodeDeployUser-a"
aws iam add-user-to-group --user-name "CodeDeployUser-a" \
                          --group-name "LightsailCodeDeployUsers"
aws iam create-access-key --user-name "CodeDeployUser-a"

Prepare the Instances

As mentioned before, an agent has to be installed on each instance - whether it’s and EC2 instance or an on-premise one. The CodeDeploy documentation contains detailed instructions how to do this for different kinds of operating systems. For example, for an instance running Amazon Linux in the region “Frankfurt” (eu-central-1) the following steps need to be performed:

sudo yum -y install ruby
sudo yum -y install wget
cd /home/ec2-user
wget https://aws-codedeploy-eu-central-1.s3.amazonaws.com/latest/install
chmod +x ./install
./install auto

It’s a good practice to add these setup procedures to the default installation instructions of an instance and should therefore be put into the user data script.

After installing the CodeDeploy agent a configuration file needs to be put onto each instance. It contains the ARN of the IAM User the instance is associated with as well as the corresponding Access Keys. The configuration file is placed in /etc/codedeploy-agent/conf/codedeploy.onpremises.yml and has the following content:

aws_access_key_id: <ACCESS_KEY>
aws_secret_access_key: <SECRET_ACCESS_KEY>
iam_user_arn: <IAM_USER_ARN>
region: <AWS_REGION>

After that the CodeDeploy agent needs to be restarted in order to pick up the configuration.

service codedeploy-agent restart

Register the Instances with CodeDeploy

Next, each instance needs to be registered with CodeDeploy. This can not be done through the Console and therefore, the AWS CLI must be used. When the instance is registered the ARN of the IAM User which is used by this instance is associated.

aws deploy register-on-premises-instance --instance-name example-web-server-a \
                                         --iam-user-arn arn:aws:iam::022876999554:user/CodeDeployUser-web-server-a \
                                         --region eu-central-1

Additionally, the registered instance has to be tagged in CodeDeploy. This step is important because these tags are used to determine to which instances a deployment should be rolled out.

aws deploy add-tags-to-on-premises-instances --instance-names example-web-server-a \
                                             --tags Key=Name,Value=example-web-server-a \
                                                    Key=Environment,Value=dev

In the example of this blog post the tag key Environment is used to determine to which instances a deployment should be rolled out.

Now, CodeDeploy knows about the instances and we can move on with setting up our application in CodeDeploy.

Setup CodeDeploy

If CodeDeploy is used for the first time a new IAM Role has to be created as the first step. This role is assumed by the CodeDeploy service to do its work. Commonly, it’s called CodeDeployServiceRole. It can be easily set up according to the documentation.

Next, a CodeDeploy Application needs to be created.

And finally, for this application at least one Deployment Group needs to be set up. The Deployment Group configures to which servers a deployment should be rolled out based on the tags that have been associated with the registered instances. Additionally, it can be configured how the deployment to multiple instances should take place (all at once, one at a time), if a failing deployment should cause a rollback, etc.

For settings up CodeDeploy a CloudFormation template to automate this step:

AWSTemplateFormatVersion: 2010-09-09

Parameters:

  ApplicationName:
    Type: String
    Description: The name of the application which will be deployed
  DeploymentGroupName:
    Type: String
    Description: The name of the Deployment Group
    Default: web-servers

Resources:

  Application:
    Type: AWS::CodeDeploy::Application
    Properties:
      ApplicationName:
        Ref: ApplicationName
      ComputePlatform: Server

  DeploymentGroup:
    Type: AWS::CodeDeploy::DeploymentGroup
    Properties:
      DeploymentGroupName:
        Ref: DeploymentGroupName
      ApplicationName:
        Ref: ApplicationName
      DeploymentStyle:
        DeploymentType: IN_PLACE
        DeploymentOption: WITHOUT_TRAFFIC_CONTROL
      DeploymentConfigName: CodeDeployDefault.OneAtATime
      OnPremisesInstanceTagFilters:
        - Key: Environment
          Value: dev
          Type: KEY_AND_VALUE
      ServiceRoleArn:
        Fn::GetAtt: CodeDeployServiceRole.Arn

  CodeDeployServiceRole:
    Type: AWS::IAM::Role
    Properties:
      AssumeRolePolicyDocument:
        Version: 2012-10-17
        Statement:
          - Effect: Allow
            Principal:
              Service:
                - codedeploy.amazonaws.com
            Action:
              - sts:AssumeRole
      ManagedPolicyArns:
        - arn:aws:iam::aws:policy/service-role/AWSCodeDeployRole

Create a Deployment

Finally, once everything is set up the first deployment can be created. Some additional preparations are necessary. The first step is to create a propper deployment package.

Deployment Package

We need to create a deployment package which contains the contents of our application. This deployment package basically is just a ZIP file. It can contain HTML pages, PHP scripts, Java WAR files, etc.

In addition to the application itself CodeDeploy uses a deployment descriptor called appspec.yaml in the root directory within the deployment package. This file contains the instructions what should actually be done during deployment. For example, it tells CodeDeploy which content within the deployment package should be copied to which place in the file system of the server onto which the deployment is performed. Furthermore, it can reference scripts contained in the package that should be run in the different phases of the deployment, e.g. before the content is copied to the file system or after the application has been started.

A simple appspec.yaml file looks like this:

version: 0.0
os: linux
files:
  - source: /content
    destination: /var/www/example/html
hooks:
  BeforeInstall:
    - location: scripts/cleanup.sh
      timeout: 300
      runas: root

In this example, the deployment package contains a direct contents into which all the resources for a web site - HTML pages, CSS files, images, etc. - are stored. The contents of this directory are copied to the folder /var/www/example/html on the server in the Install phase of the deployment. Additionally, it refers to the script cleanup.sh which is stored in the scripts folder within the deployment package. This script is run just before the installation and is used to clean things up.

A complete reference of the instructions that can be put into the appspec.yaml file can be found in the AWS CodeDeploy documentation.

Perform a Deployment

The actual deployment consists of three steps: creating the deployment package, pushing it to S3, trigger CodeDeploy to deploy it to the servers.

Creating the deployment package just means to zip up all the files into an archive. This is fairly easy to do. The upload to the appropriate S3 bucket is also no hurdle. Triggering CodeDeploy can either be done through the AWS Console or the AWS CLI.

Using the AWS CLI has the advantage that all three steps can be put into a script to actually execute it with just one command. The following script shows exactly how this can be done:

#!/usr/bin/env bash

APPLICATION=example-app
S3_BUCKET=com.example.codedeploy.eu-central-1
PACKAGE=deployment.zip

aws deploy push --application-name $APPLICATION \
                --s3-location s3://$S3_BUCKET/$PACKAGE

deployment_id=$(aws deploy create-deployment --application-name $APPLICATION \
                                             --deployment-group-name "web-servers" \
                                             --s3-location bucket=$S3_BUCKET,key=$PACKAGE,bundleType=zip \
                                             --query "deploymentId" \
                                             --output text)
aws deploy wait deployment-successful --deployment-id $deployment_id

The aws deploy push command creates the deployment package and pushes it to the S3 bucket. The aws deploy create-deployment command triggers CodeDeploy to roll out the package to all servers associated with the Deployment Group. Each deployment has a unique identifier, the deploymentId which can be used to get more information about the status of the deployment. This deploymentId is extracted from the create-deployment command and is used to ask the CLI to wait until the deployment is finished.

That’s it! This deployment process can be used over and over again to update applications running on servers in a standardized and repeatable fashion.

Summary

This blog post shows how to set up CodeDeploy to streamline the process of deploying applications to instances managed by AWS Lightsail. This way it is possible to automated the roll out of applications to a set of servers. Furthermore, it is also possible to integrate this deployment step into a more sophisticated build and deployment process. For example, such a process could involve other AWS services like CodePipeline and CodeBuild.

To make things easier to set up a CloudFormation template is available on GitHub to perform the initial setup outlined above: Create S3 Bucket, IAM Group, CodeDeploy Application and Deployment Group.