/ Docker

Deploying a multi-container Docker instance in Elastic Beanstalk with Jenkins

One of the simplest ways of reducing the cost of your ec2 instances is to put all your applications in one box. With docker, this has become so much easier.

Also with elastic beanstalk, the application-deployment-specific dependencies are copied across environments of an application. For example, for a Node.js application, Nginx would be installed on all environments (ec2 instances) of the application. So if you have multiple Node.js applications running in a staging environment that becomes an overkill. Moving to a multi-container docker instance helped me to run an application that consumed around 900mb in a t2-small (2gb RAM) instance to consume only 300mb in a t2-medium (4gb RAM) instance. And that blew my mind! Imagine me stuffing 6-7 of such applications in a t2 medium machine. The dollars we saved!

Anyway, Let's get you the same savings :)

This tutorial covers the entire deployment process of a Node.js application via docker on elastic beanstalk that is automated with Jenkins right after your git push.
We'll be using EC2 Container Service (ECS) to store your application images. You can use any container service you like to pull and push your images.

Requirements
  1. Access to AWS services
  2. A running Jenkins machine

Since this is an advanced deployment tutorial, I will also assume you know a little bit about -

The flow

Phase I - Building & Pushing docker images for your application

building & pushing docker images for your application

Phase II - Deploying the multi-container docker application on Elastic Beanstalk

Deploying the multi-container docker application on Elastic Beanstalk

The bottom line is to create an extra application for your multi-container docker deployments. This will contain a configuration file named Dockerrun.aws.json. This application, when deployed to elastic beanstalk, shall pull all the latest docker images mentioned in the Dockerrun.aws.json file and run them.

Diving Deep

Phase I - Node.js Application
  1. Setup a Jenkins job that pulls code on git push by enabling polling or a webhook.
  2. For building and pushing the image use the CloudBees Docker Build and Publish plugin. Do tag the image with the environment name.
  3. You must also install the Amazon ECR plugin to feed-in the right credentials into the CloudBees plugin.
  4. Add a post build trigger to build the Multi-Container Application job.
Phase II - Multi-Container Application
Setting up the multi-container application code

This application shall pull code from your ECS Repository for the Node.js application, and from the official repository on Docker Hub for Nginx.
1 - Setup up a new git repository for this application.
2 - write a Dockerrun.aws.json file similar to mine -

{
  "AWSEBDockerrunVersion": 2,
  "volumes": [
    {
      "name": "nodejs-application",
      "host": {
        "sourcePath": "/var/app/current/nodejs-application"
      }
    },
    {
      "name": "nginx-proxy-conf",
      "host": {
        "sourcePath": "/var/app/current/proxy/conf.d"
      }
    }
  ],
  "containerDefinitions": [
    {
      "name": "nodejs-application",
      "image": "*insert-url-to-ecs-repository-for-nodejs-appliation*:beta",
      "environment": [
        {
          "name": "NODE_ENV",
          "value": "beta"
        },
        {
          "name": "PORT",
          "value": 2999
        }
      ],
      "essential": true,
      "memory": 512,
      "mountPoints": [
        {
          "sourceVolume": "awseb-logs-nodejs-application-beta",
          "containerPath": "/var/log/nodejs-application-beta"
        }
      ],
      "portMappings": [
        {
          "hostPort": 2999,
          "containerPort": 2999
        }
      ]
    },
    {
      "name": "nginx-proxy",
      "image": "nginx",
      "essential": true,
      "memory": 128,
      "portMappings": [
        {
          "hostPort": 80,
          "containerPort": 80
        }
      ],
      "links": [
        "nodejs-application"
      ],
      "mountPoints": [
        {
          "sourceVolume": "awseb-logs-nginx-proxy",
          "containerPath": "/var/log/nginx"
        },
        {
          "sourceVolume": "nginx-proxy-conf",
          "containerPath": "/etc/nginx/conf.d",
          "readOnly": true
        }
      ]
    }
  ]
}

3 - write a default.conf file at project-root/proxy/conf.d to override the nginx conf -

# Node.js Application - Beta
upstream nodejs-application-upstream {
    server nodejs-application:2999;
    keepalive 256;
}

server {
    listen 80;
    server_name *insert-host-name-for-docker-instance*;
    location / {
        proxy_pass http://nodejs-application-upstream;
        proxy_set_header   Connection "";
        proxy_http_version 1.1;
        proxy_set_header        Host            $host;
        proxy_set_header        X-Real-IP       $remote_addr;
        proxy_set_header        X-Forwarded-For $proxy_add_x_forwarded_for;
    }
    gzip on;
    gzip_comp_level 4;
    gzip_types text/plain text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript;
}

For more details refer Amazon Web Services - Labs code for nginx proxy on github.
4 - In case of a private repository like the AWS ECS, make sure you add the AmazonEC2ContainerRegistryReadOnly policy to the elastic beanstalk IAM role.

Setting up the multi-container application job on jenkins
  1. Setup a Jenkins job that pulls code on git push by enabling polling or a webhook.
    By the way, this application's jenkins job shall be triggered by code changes in both the git repositories.
  2. Deploy the application by using the AWSEB Deployment Plugin.

And that completes the tutorial.
Thank you for bearing with me till the end.


P.S. - There's an issue with the Amazon ECR plugin that caches credentials.
You might see an error like denied: Your Authorization Token has expired. Please run 'aws ecr get-login' to fetch a new one.

The fix is to add a build step before the CloudBees plugin's push step.
Add a Execute Shell step in your build with the following commands

DOCKER_LOGIN=`aws ecr get-login --region ap-southeast-1`
replace_string="-e none"
${DOCKER_LOGIN/$replace_string/}

More cookies for me!

Umang Ganvir

Umang Ganvir

Techie. Javascript Evangelist. Passionate Learner. Talks about Motivation and Value Creation.

Read More