Set build pipeline with Amazon EC2

Walkthrough: Setting up build pipeline with Amazon EC2 Container Service and Jenkins

EC2 Container service
Amazon EC2 Container Service (Amazon ECS) is a highly scalable, fast, container management service that makes it easy to run, stop, and manage Docker containers on a cluster of Amazon EC2 instances.So how do we use ECS so as to have CICD, I dig out about this and foundvarious interesting things. From learnings I have created a CI pipeline with Jenkins and ECS.In this walkthrough, I’ll share the steps I performed to set up and configure a build pipeline using Jenkins and the

Amazon EC2 Container Service (ECS) .We’ll be using a sample Node.js application, available on GitHub *. The repository contains a simple Dockerfile that uses a node.js base image and runs our application:

FROM node:0.10-onbuild
EXPOSE 3000

This Dockerfile is used by the build pipeline to create a new Docker image upon pushing code to the repository. The built image will then be used to start a new service on an ECS cluster.

Setup the build environment

For our build environment we’ll launch an Amazon EC2 instance using the Amazon Linux AMI and install and configure the required packages. Make sure that the security group you select for your instance allows traffic on ports TCP/22 and TCP/80.

Install and configure Jenkins, Docker and Nginx

Connect to your instance using your private key and switch to the root user. First, let’s update the repositories and install Docker, Nginx and Git.

# yum update -y
# yum install -y docker nginx git

To install Jenkins on Amazon Linux, we need to add the Jenkins repository and install Jenkins from there.

# wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
# rpm –import http://pkg.jenkins-ci.org/redhat/jenkins-ci.org.key
# yum install Jenkins

As Jenkins typically uses port TCP/8080, we’ll configure Nginx as a proxy. Edit the Nginx config file (/etc/nginx/nginx.conf) and change the
server configuration to look like this:

server {
    listen       80;
    server_name  _;
    location / {
            proxy_pass http://127.0.0.1:8080;
    }
}

We’ll be using Jenkins to build our Docker images, so we need to add the jenkins user to the docker group. A reboot may be required for the changes to take effect.

# usermod -a -G docker jenkins

Start the Docker, Jenkins and Nginx services and make sure they will be running after a reboot:

# service docker start
# service jenkins start
# service nginx start
# chkconfig docker on
# chkconfig jenkins on
# chkconfig nginx on

You can launch the Jenkins instance complete with all the required plugins with this CloudFormation template .

Point your browser to the public DNS name of your EC2 instance (e.g. ec2-54-111-222-233.compute-1.amazonaws.com) and you should be able to see the Jenkins home page:

1 jenkins

The Jenkins installation is currently accessible through the Internet without any form of authentication. Before proceeding to the next step, let’s secure Jenkins. Select Manage Jenkins on the Jenkins home page, click Configure Global Security and then enable Jenkins security by selecting the Enable Security checkbox.

For the purpose of this walkthrough, select Jenkins’s Own User Database under Security realm and make sure to select the Allow users to sign up checkbox. Under Authorization, select Matrix-based security. Add a user (e.g. admin) and provide necessary privileges to this user.

2 security

After that’s complete, save your changes. Now you will be asked to provide a username and password for the user to login. Click on Create an account, provide your username- and fill in the user details. Now you will be able to log in securely to Jenkins.

Install and configure Jenkins plugins
The last step in setting up our build environment is to install and configure the Jenkins plugins required to build a Docker image and publish it to a Docker registry (DockerHub in our case). We’ll also need a plugin to interact with the code repository of our choice, GitHub in our case.

From the Jenkins dashboard select Manage Jenkins and click Manage Plugins. On the Available tab, search for and select the following plugins:

  1. CloudBees Docker Build and Publish plugin
  2. CloudBees Docker Hub Notification
  3. Github plugin

Then click the Install button. After the plugin installation is completed,

Configure Git :
Select Manage Jenkins and click Configure System, find GitHub Plugin Configuration and add GitHub Credentials.

Install and configure the Amazon ECS CLI
Now we are ready to setup and configure the ECS Command Line Interface (CLI).

Follow the Setting Up with Amazon ECS guide to get ready to use ECS. If you haven’t done so yet, make sure to start at least one container instance in your account and create the Amazon ECS service role in the AWS IAM console.

Make sure that Jenkins is able to use the ECS CLI. Switch to the jenkins user and configure the AWS CLI, providing your credentials:

# sudo -su jenkins
> aws configure

.

Login to Docker Hub
The Jenkins user needs to login to Docker Hub before doing the first build:

# docker login

.

Create a task definition template
Create a task definition template for our application (note, you will replace the image name with your own repository):

{
    “family”: “demo-node-app”,
    “containerDefinitions”: [
        {
            “image”: “swapnildahiphale/demo-node-app:v_%BUILD_N
UMBER%”,
            “name”: “demo-node-app”,
            “cpu”: 100,
            “memory”: 256,
            “essential”: true,
            “portMappings”: [
                {
                    “containerPort”: 3000,
                    “hostPort”: 80
                }
            ]
        }
    ]
}

(You can find this task definition on GitHub )
Save your task definition template as task-definition.json. Since the image specified in the task definition template will be built in the Jenkins job, at this point we will create a dummy task definition. Substitute the %BUILD_NUMBER% parameter in your task definition template with a non-existent value (0) and register it with ECS:

#sed -e “s;%BUILD_NUMBER%;0;g” task-definition.json > task
-definition:v_0.json
#aws ecs register-task-definition –cli-input-json file
://task-definition:v_0.json

{

    “taskDefinition”: {
        “status”: “ACTIVE”,
        “family”: “demo-node-app”,
        “volumes”: [],
        “taskDefinitionArn”: “arn:aws:ecs:us-west-2:466932663
284:task-definition/demo-node-app:1″,
        “containerDefinitions”: [
            {
                “environment”: [],
                “name”: “demo-node-app”,
                “mountPoints”: [],
                “image”: “swapnildahiphale/demo-node-app:v_0”,
                “cpu”: 100,
                “portMappings”: [
                    {
                        “protocol”: “tcp”,
                        “containerPort”: 3000,
                        “hostPort”: 80
                    }
                ],
                “memory”: 256,
                “essential”: true,
                “volumesFrom”: []
            }
        ],
        “revision”: 1
    }
}

Make note of the family value (demo-node-app), as it will be needed when configuring the Execute shell step in the Jenkins job.

Create the ECS IAM Role, an ELB and your service definition

Create a new IAM role (e.g. ecs-service-role), select the Amazon EC2 Container Service Role type and attach the AmazonEC2ContainerServiceRole policy. This will allows ECS to create and manage AWS resources, such as an ELB, on your behalf. Create an Amazon Elastic Load Balancing (ELB) load balancer to be used in your service definition and note the ELB name (e.g. demo-node-app-elb). Create the demo-node-app-service service, specifying the task definition (e.g. demo-node-app) and the ELB name (e.g. demo-node-app-elb):

#aws ecs create-service –cluster demo-cluster –service-name demo-node-app-service –task-definition demo-node-app –load-balancers loadBalancerName=demo-node-app-elb,containerName=demo-node-app,containerPort=3000 –role ecs-service-demo-role –desired-count 0

{
    “service”: {
        “status”: “ACTIVE”,
        “taskDefinition”: “arn:aws:ecs:us-west-2:466932663284
:task-definition/demo-node-app:1″,
        “pendingCount”: 0,
        “loadBalancers”: [
            {
                “containerName”: “demo-node-app”,
                “containerPort”: 3000,
                “loadBalancerName”: “demo-node-app-elb”
            }
        ],
        “roleArn”: “arn:aws:iam::466932663284:role/ecs
-service-demo-role”,
        “desiredCount”: 0,
        “serviceName”: “demo-node-app-service”,
        “clusterArn”: “arn:aws:ecs:us-west-2:466932663284
:cluster/demo-cluster”,
        “serviceArn”: “arn:aws:ecs:us-west-2:466932663284
:service/demo-node-app-service”,
        “deployments”: [
            {
                “status”: “PRIMARY”,
                “pendingCount”: 0,
                “createdAt”: 1449650893.873,
                “desiredCount”: 0,
                “taskDefinition”: “arn:aws:ecs:us-west-2
:466932663284:task-definition/demo-node-app:1″,
                “updatedAt”: 1449650893.873,
                “id”: “ecs-svc/9223370587203881934”,
                “runningCount”: 0
            }
        ],
        “events”: [],
        “runningCount”: 0
    }
}

Since we have not yet build a Docker image for our task, make sure to set the –desired-count flag to 0.

Configure the Jenkins build

On the Jenkins dashboard, click on New Item, select the Freestyle project job, add a name for the job, and click OK. Configure the Jenkins job:

  1. Under GitHub Project, add the path of your GitHub repository – e.g. https://github.com/swapnildahiphale/demo-node-app.git. In addition to the application source code, the repository contains the Dockerfile used to build the image, as explained at the beginning of this walkthrough.
  2. Under Source Code Management provide the Repository URL for Git, e.g. https://github.com/swapnildahiphale/demo-node-app.git.
  3. In the Build Triggers section, select Build when a change is pushed to GitHub.

In the Build section, add a Docker build and publish step to the job and configure it to publish to your Docker registry repository (e.g. DockerHub) and add a tag to identify the image (e.g. v_$BUILD_NUMBER).

3 docker build and publish_jenkins

The Repository Name specifies the name of the Docker repository where the image will be published; this is composed of a user name (swapnildahiphale) and an image name (demo-node-app). In our case, the Dockerfile sits in the root path of our repository, so we won’t specify any path in the Directory Dockerfile is in field. Note, the repository name needs to be the same as what is used in the task definition template in task-definition.json.
Add a Execute Shell step and add the ECS CLI commands to start a new task on your ECS cluster.

4 execute shell

The script for the Execute shell step will look like this:

#!/bin/bash
SERVICE_NAME=”demo-node-app-service”
IMAGE_VERSION=”v_”${BUILD_NUMBER}
TASK_FAMILY=”demo-node-app”
CLUSTER_NAME=”demo-cluster”

 

# Create a new task definition for this build
sed -e “s;%BUILD_NUMBER%;${BUILD_NUMBER};g” task-definition.json > task-definition:v_${BUILD_NUMBER}.json
aws ecs register-task-definition –cli-input-json file://task-definition:v_${BUILD_NUMBER}.json

# Update the service with the new task definition and desired count
NEW_TASK_REVISION=`aws ecs describe-task-definition –task-definition $TASK_FAMILY –region us-west-2 | egrep “revision” | tr “/” ” ” | awk ‘{print $2}’ | sed ‘s/”$//’`

DESIRED_COUNT=`aws ecs describe-services –cluster $CLUSTER_NAME –services $SERVICE_NAME –region us-west-2 | egrep -m 1 “desiredCount” | tr “/” ” ” | awk ‘{print $2}’ | sed ‘s/,$//’`

if [ ${DESIRED_COUNT} = “0” ]; then
DESIRED_COUNT=”1″
fi

aws ecs update-service –cluster ${CLUSTER_NAME} –service ${SERVICE_NAME} –task-definition ${TASK_FAMILY}:${NEW_TASK_REVISION} –desired-count ${DESIRED_COUNT} –region us-west-2

To trigger the build process on Jenkins upon pushing to the GitHub repository we need to configure a service hook on GitHub. Go to the GitHub repository settings page, select Webhooks and Services and add a service hook for Jenkins (GitHub plugin). Add the Jenkins hook url: http://<username>:<password>@<EC2-DNS-Name>/github-webhook/.

5 github services

Now we have configured a Jenkins job in such a way that whenever a change is committed to GitHub repository it will trigger the build process on Jenkins.

Happy building
From your local repository, push the application code to GitHub:

# git add *
# git commit -m “First Jenkins build”
# git push origin master

This will trigger the Jenkins job. After the job is completed, point your browser to the public DNS name for your EC2 container instance and verify that the application is correctly running.

Conclusion
In this walkthrough we demonstrated how to use Jenkins to automate the deployment of an ECS service.

Written by Swapnil Dahiphale – WhiteHedge DevOps Team

“You can write to us at devops@whitehedge.in

wh-webadmin


Join our Resources and get the latest activity update into your inbox monthly.

image

CONNECT WITH US

Get in touch with us and start your journey towards success.

Contact us