Continuous Delivery Pipeline for Amazon ECS Using Jenkins, GitHub, and Amazon ECR

Paul Zhao
Paul Zhao Projects
Published in
20 min readSep 8, 2020

How the architecture looks like

Diagram of CI/ CD

This getting started guide is intended to help you set up and configure a continuous delivery pipeline for Amazon EC2 Container Service (Amazon ECS) using Jenkins, GitHub, and the Amazon EC2 Container Registry (Amazon ECR). The pipeline builds Docker images from a GitHub repository, pushes those images to an ECR registry, creates an ECS task definition, and then uses that task definition to create a service on the ECS cluster. We use Jenkins to orchestrate the different steps in the workflow.

Prerequisites

In this project, you must have the following software components installed:

Note: Homebrew is the package manager for OS X. You will need homebrew to install jq.

Note: when installing Chocolatey, you might have to launch command window as Administrator. Once you have Chocolatey installed on your machine, you can use it to install the remaining prerequisites.

I am using a mac, so all the installation following will be exclusive for Mac user. For Windows user, you may go through instructions above to install required components.

Installing Homebrew

Open terminal and type in

$ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
.
.
.
==> Installation successful!

==> Homebrew has enabled anonymous aggregate formulae and cask analytics.
Read the analytics documentation (and how to opt-out) here:
https://docs.brew.sh/Analytics
No analytics data has been sent yet (or will be during this `install` run).

==> Homebrew is run entirely by unpaid volunteers. Please consider donating:
https://github.com/Homebrew/brew#donations

==> Next steps:
- Run `brew help` to get started
- Further documentation:
https://docs.brew.sh

Verify Homebrew installation

$ brew --version
Homebrew 2.4.16
Homebrew/homebrew-core (git revision 23bea; last commit 2020-09-04)
Homebrew/homebrew-cask (git revision 5beb1; last commit 2020-09-05)

Once you’ve installed Homebrew, insert the Homebrew directory at the top of your PATH environment variable. You can do this by adding the following line at the bottom of your ~/.profile file

export PATH="/usr/local/opt/python/libexec/bin:$PATH"

Installing Python3

Now, we can install Python 3:

$ brew install python
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 1 tap (homebrew/cask).
==> Updated Casks
session

Warning: python@3.8 3.8.5 is already installed and up-to-date
### my python was preinstalled, you may see different installation process. And it may take a while before python is fully installed

Verify is your python3 is installed

$ python3 --version
Python 3.8.5

Notes: you may set your default python as latest version by applying following code

$ unlink /usr/local/bin/python
$ ln -s /usr/local/bin/python3.8 /usr/local/bin/python

Installing pip

$ curl https://bootstrap.pypa.io/get-pip.py -o get-pip.py
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
0 0 0 0 0 0 0 0 --:--:-- --:--:-- --:--:-- 0
100 1840k 100 1840k 0 0 4672k 0 --:--:-- --:--:-- --:--:-- 4660k
$ python get-pip.py
Defaulting to user installation because normal site-packages is not writeable
Collecting pip
Downloading pip-20.2.2-py2.py3-none-any.whl (1.5 MB)
|████████████████████████████████| 1.5 MB 2.7 MB/s
Successfully installed pip-20.2.2

Verify pip installation

$ pip -V
pip 20.2.2 from /usr/local/lib/python3.8/site-packages/pip (python 3.8)

Installing AWS CLI

Visit here and download Mac packer

Download MacOS pkg installer
Install it successfully

To verify your aws cli installation

$ aws --version
aws-cli/2.0.46 Python/3.7.4 Darwin/19.6.0 exe/x86_64

Installing jq

$ brew install jq
Updating Homebrew...
==> Auto-updated Homebrew!
Updated 2 taps (homebrew/core and homebrew/cask).
==> Updated Formulae
Updated 45 formulae.
==> New Casks
gotomeeting koodo-reader phd2
==> Updated Casks
aleph-one duo-connect pritunl
amd-power-gadget fm3-edit profind
appium free-download-manager screenflick
aspera-connect gns3 signal
aviatrix-vpn-client hydrogen swiftformat-for-xcode
axe-edit-iii jaxx-liberty universal-media-server
balenaetcher joplin vassal
betterzip keepingyouawake virtualbox
blizz kext-updater virtualbox-extension-pack
boostnote marathon vnote
burp-suite marathon-infinity xlink-kai
chromium marathon2 yinxiangbiji
cookie netron
digikam openphone
==> Deleted Casks
ghost

==> Downloading https://homebrew.bintray.com/bottles/oniguruma-6.9.5-rev1.catali
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/15241cccbb727a11200b6
######################################################################## 100.0%
==> Downloading https://homebrew.bintray.com/bottles/jq-1.6.catalina.bottle.1.ta
==> Downloading from https://d29vzk4ow07wi7.cloudfront.net/820a3c85fcbb63088b160
######################################################################## 100.0%
==> Installing dependencies for jq: oniguruma
==> Installing jq dependency: oniguruma
==> Pouring oniguruma-6.9.5-rev1.catalina.bottle.tar.gz
🍺 /usr/local/Cellar/oniguruma/6.9.5-rev1: 16 files, 1.3MB
==> Installing jq
==> Pouring jq-1.6.catalina.bottle.1.tar.gz
🍺 /usr/local/Cellar/jq/1.6: 18 files, 1MB

To verify jq installation

$ jq --version
jq-1.6

Step 1: Build an ECS Cluster

1 Create an AWS access key and secret key by opening a terminal window, and then typing the following:

$ aws iam create-access-key --user-name <user_name>

<user_name> is an IAM user with AdministratorAccess.

$ aws iam create-access-key --user-name adminuser
{
"AccessKey": {
"UserName": "adminuser",
"AccessKeyId": "AKIAXXXXXXXXDOWD",
"Status": "Active",
"SecretAccessKey": "etjWNQPyXXXXXXXXXXXXyf3ul",
"CreateDate": "2020-09-05T13:42:16+00:00"
}
}

Notes: You may have only limit of 2 for AcceessKeysPerUser

An error occurred (LimitExceeded) when calling the CreateAccessKey operation: Cannot exceed quota for AccessKeysPerUser: 2

2 Copy and paste the output from the previous command to a text file

Note: AdministratorAccess is a managed policy that allows attached entities to perform all actions against all resources. Although we’re using it here for convenience, you should remove the AdministratorAccess policy should be removed from your IAM user when it’s no longer needed.

3 Create an AWS profile on your local machine. From a command prompt, type:

$ aws configure
AWS Access Key ID [****************WX4W]: AKIAXXXXXXDOWD
AWS Secret Access Key [****************XL2m]: etjWNQXXXXXXXXyf3ul
Default region name [us-east-1]: us-east-1
Default output format [json]: json

At the prompts, paste your aws access key ID, aws secret key ID, the preferred region (us-east-1), and json as the output format.

4 Create an SSH key in the us-east-1 region. We will use this SSH key to login to the Jenkins server to retrieve the administrator password.

(1) Open the EC2 console

(2) Under the Networking & Security, choose Key Pairs

(3) Choose Create Key Pair

(4) Assign a name to the key pair by typing a name in the Key pair name field, then click the Create button

A file will be downloaded to your default download directory

Keypair under network & security
Create keypair
.pem file saved

5 (OS X only) Change the working directory to your download directory and change permission so only the current logged in user can read it. <file_name> is the name of the .pem file you downloaded earlier:

$ cd Downloads/
$ chmod 400 jenkinskeypair.pem

Verifying file privilege

$ ls -lah jenkinskeypair.pem 
-r--------@ 1 paulzhao staff 1.6K 6 Sep 10:28 jenkinskeypair.pem

6 Clone the git repository that contains the CloudFormation templates to create the infrastructure we’ll use to build our pipeline.

(1) Open a command prompt and clone the Github repository that has the template.

$ git clone https://github.com/jicowan/hello-world

(2) Change the working directory to the directory that was created when you cloned the repository. At the command prompt, type or paste the following. Where <key_name> is the name of an SSH key in the region where you're creating the ECS cluster:

$ cd hello-world/
$ aws cloudformation create-stack --template-body file://ecs-cluster.template --stack-name EcsClusterStack --capabilities CAPABILITY_IAM --tags Key=Name,Value=ECS --region us-west-2 --parameters ParameterKey=KeyName,ParameterValue=<key_name> ParameterKey=EcsCluster,ParameterValue=getting-started ParameterKey=AsgMaxSize,ParameterValue=2

Note: Do not proceed to the next step until the Stack Status shows CREATE_COMPLETE. To get the status of the stack at a command prompt, type aws cloudformation describe-stacks --stack-name EcsClusterStack --query 'Stacks[*].[StackId, StackStatus]'

$ aws cloudformation describe-stacks --stack-name EcsClusterStack --query 'Stacks[*].[StackId, StackStatus]'
[
[
"arn:aws:cloudformation:us-east-1:464392538707:stack/EcsClusterStack/275572b0-f051-11ea-8fa5-0a28f59d0ab4",
"CREATE_COMPLETE"
]
]

You can double check in AWS console

Ecsclusterstack created

Step 2: Create a Jenkins Server

Jenkins is a popular server for implementing continuous integration and continuous delivery pipelines. In this project, you’ll use Jenkins to build a Docker image from a Dockerfile, push that image to the Amazon ECR registry that you created earlier, and create a task definition for your container. Finally, you’ll deploy and update a service running on your ECS cluster.

1 Change the current working directory to the root of the cloned repository, and then execute the following command:

$ aws cloudformation create-stack --template-body file://ecs-jenkins-demo.template --stack-name JenkinsStack --capabilities CAPABILITY_IAM --tags Key=Name,Value=Jenkins --region us-east-1 --parameters ParameterKey=EcsStackName,ParameterValue=EcsClusterStack
{
"StackId": "arn:aws:cloudformation:us-east-1:464392538707:stack/JenkinsStack/baa65970-f052-11ea-8033-0e28043c5c55"
}

Note: Do not proceed to the next step until the Stack Status shows CREATE_COMPLETE. To get the status of the stack type aws cloudformation describe-stacks --stack-name JenkinsStack --query 'Stacks[*].[StackId, StackStatus]' at a command prompt.

$ aws cloudformation describe-stacks --stack-name JenkinsStack --query 'Stacks[*].[StackId, StackStatus]'
[
[
"arn:aws:cloudformation:us-east-1:464392538707:stack/JenkinsStack/baa65970-f052-11ea-8033-0e28043c5c55",
"CREATE_COMPLETE"
]
]

2 Retrieve the public hostname of the Jenkins server. Open a terminal window and type the following command:

$ aws ec2 describe-instances --filters "Name=tag-value","Values=JenkinsStack" | jq .Reservations[].Instances[].PublicDnsName

3 Copy the public hostname

"ec2-3-86-189-99.compute-1.amazonaws.com"

4 SSH into the instance, and then copy the temp password from /var/lib/jenkins/secrets/initialAdminPassword.

(1) On OS X, use the following command:

$ ssh -i <full_path_to_key_file> ec2-user@<public_hostname>
Last login: Sun Sep 6 15:19:49 2020 from 99.225.158.153

__| __|_ )
_| ( / Amazon Linux AMI
___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2017.09-release-notes/
26 package(s) needed for security, out of 64 available
Run "sudo yum update" to apply all updates.
Amazon Linux version 2018.03 is available.

For Windows instructions, see Connecting to your Linux Instance from Windows Using PuTTY

(2) Run the following command:

$ sudo cat /var/lib/jenkins/secrets/initialAdminPassword
ff5badXXXXXXXX470a211

(3) Copy the output and logout of the instance by typing the following command:

$ logout

Step 3: Create an ECR Registry

Amazon ECR is a private Docker container registry that you’ll use to store your container images. For this example, we’ll create a repository named hello-world in the us-east-1 (N. Virginia) region.

1 Create a ECR registry by running the following command:

$ aws ecr create-repository --repository-name hello-world --region us-east-1
{
"repository": {
"repositoryArn": "arn:aws:ecr:us-east-1:464392538707:repository/hello-world",
"registryId": "464392538707",
"repositoryName": "hello-world",
"repositoryUri": "464392538707.dkr.ecr.us-east-1.amazonaws.com/hello-world",
"createdAt": "2020-09-06T11:26:43-04:00",
"imageTagMutability": "MUTABLE",
"imageScanningConfiguration": {
"scanOnPush": false
},
"encryptionConfiguration": {
"encryptionType": "AES256"
}
}
}

2 Record the value of the URL of this repository because you will need it later.

464392538707.dkr.ecr.us-east-1.amazonaws.com/hello-world

3 Verify that you can log in to the repository you created (optional).

Because the Docker CLI doesn’t support the standard AWS authentication methods, you need to authenticate the Docker client in another way so ECR knows who is trying to push an image. Using the AWS CLI, you generate an authorization token that you pass into the Docker login command.

  • If you’re using OS X, type: $(aws ecr get-login)

Notes: If you’re using AWS CLI 2, aws ecr get-login-password replaces aws ecr get-login

  • If you’re running Windows, type: aws ecr get-login | cmd
$ aws ecr get-login-password \
> --region us-east-1 | docker login \
> --username AWS \
> --password-stdin 464392538707.dkr.ecr.us-east-1.amazonaws.com
Login Succeeded

Note: This command will not succeed unless you have the Docker client tools installed on your machine and the Docker Virtual Machine is running. The output should say Login Succeeded.

Step 4: Install Jenkins in AWS EC2

1 Login to AWS EC2 Server

$ ssh -i ~/Downloads/jenkinskeypair.pem ec2-user@ec2-3-86-189-99.compute-1.amazonaws.com
Last login: Sun Sep 6 17:45:59 2020 from 99.225.158.153

__| __|_ )
_| ( / Amazon Linux AMI
___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2017.09-release-notes/
26 package(s) needed for security, out of 64 available
Run "sudo yum update" to apply all updates.
Amazon Linux version 2018.03 is available.

2 To ensure that your software packages are up to date on your instance, use the following command to perform a quick software update:

$ sudo yum update –y
Loaded plugins: priorities, update-motd, upgrade-helper
amzn-main | 2.1 kB 00:00
amzn-updates | 3.8 kB 00:00
No Match for argument: –y
No packages marked for update

3 Add the Jenkins repo using the following command:

$ sudo wget -O /etc/yum.repos.d/jenkins.repo http://pkg.jenkins-ci.org/redhat/jenkins.repo
--2020-09-06 18:11:51-- http://pkg.jenkins-ci.org/redhat/jenkins.repo
Resolving pkg.jenkins-ci.org (pkg.jenkins-ci.org)... 52.202.51.185
Connecting to pkg.jenkins-ci.org (pkg.jenkins-ci.org)|52.202.51.185|:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 71
Saving to: ‘/etc/yum.repos.d/jenkins.repo’

/etc/yum.repos.d/je 100%[===================>] 71 --.-KB/s in 0s

2020-09-06 18:11:51 (17.0 MB/s) - ‘/etc/yum.repos.d/jenkins.repo’ saved [71/71]

4 Import a key file from Jenkins-CI to enable installation from the package:

$ sudo rpm --import https://pkg.jenkins.io/redhat/jenkins.io.key

5 Install Jenkins:

$ sudo yum install jenkins -y 
Loaded plugins: priorities, update-motd, upgrade-helper
Resolving Dependencies
--> Running transaction check
---> Package jenkins.noarch 0:2.255-1.1 will be installed
--> Finished Dependency Resolution

Dependencies Resolved

================================================================================
Package Arch Version Repository Size
================================================================================
Installing:
jenkins noarch 2.255-1.1 jenkins 64 M

Transaction Summary
================================================================================
Install 1 Package

Total size: 64 M
Installed size: 64 M
Downloading packages:
Running transaction check
Running transaction test
Transaction test succeeded
Running transaction
Installing : jenkins-2.255-1.1.noarch 1/1
Verifying : jenkins-2.255-1.1.noarch 1/1

Installed:
jenkins.noarch 0:2.255-1.1

Complete!

6 Start Jenkins as a service:

$ sudo service jenkins start 
Starting Jenkins [ OK ]

Step 5: Configure Jenkins First Run

1 Paste the public hostname of the Jenkins server from step 2.3 into a browser.

Unlock jenkins

2 Paste the password you copied from the /var/lib/jenkins/secrets directory from Step 2: Create a Jenkins Server (step 2.4) in the password field, and then choose Next.

Next after inputting your password

3 Choose Install suggested plugins.

Click install sugguested plugins

4 Create your first admin user by providing the following information:

Profile created

5 Instance Configuration page

Instance configuration page

6 Jenkins is ready!

Jenkins ready

7 Jenkins interface

Jenkins inerface

8 Install Jenkins plugins.

In this step, you install the Amazon ECR plugin and the Cloudbees Docker build and publish plugin. You use the Amazon ECR plugin to push Docker images to an Amazon ECR repository. You use the Cloudbees Docker build and publish plugin to build Docker images.

(1) Log in to Jenkins with your username and password.

(2) On the main dashboard, click Manage Jenkins.

(3) Choose the Manage plugins tab.

(4) Choose the Available tab.

(5) Select the Cloudbees Docker build and publish plugin and the Amazon ECR plugin.

(6) Choose Download now and install after restart.

(7) Choose Restart Jenkins when installation is complete and no jobs are running.

Download and install plugins
Plugins required Installed

Step 6: Create and import SSH keys for Github

In this step, you create an SSH key and import it into GitHub so we can login into Github over SSH.

1 If you’re running OS X, open terminal window. If you’re running Windows open a Git Bash shell. Run the following command:

$ ssh-keygen -t rsa -b 4069 -C your_email@company.comEnter file in which to save the key (/Users/paulzhao/.ssh/id_rsa):Your identification has been saved in /Users/paulzhao/.ssh/id_rsa.
Your public key has been saved in /Users/paulzhao/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:4uCnqjOEX/o21KiquDAYDtuxr4v4vIPYjdu1gGA5p8c your_email@company.com
The key's randomart image is:
+---[RSA 4069]----+
| |
| |
| |
| . |
|=+...o. S |
|**==+o.. |
|B*+Eo + |
|O+Oo+= . |
|XBOX=.. |
+----[SHA256]-----+

2 Accept the file location and type a passphrase.

3 Ensure ssh-agent is enabled by running the following command:

$ eval "$(ssh-agent -s)"
Agent pid 64094

4 Add the SSH key to agent:

$ ssh-add ~/.ssh/id_rsa
Identity added: /Users/paulzhao/.ssh/id_rsa (your_email@company.com)

Note: If you already have a key called id_rsa, choose another name.

5 Copy the contents of the id_rsa.pub file to the clipboard. On OS X you can use the following command:

$ pbcopy < ~/.ssh/id_rsa.pub

Notes: This command enables you to copy the credentials. (Like command + c)

6 Login to Github. If you don't have a Github account, follow the instructions posted here, https://help.github.com/articles/signing-up-for-a-new-github-account/.

(1) In the top right corner of any page, choose your profile picture, then choose settings.

Settings

(2) In the user settings sidebar, choose SSH and GPG keys.

New ssh key

(3) Choose New SSH key or Add SSH key.

(4) Type a title for the key.

Create ssh key

(5) Paste your key in the key field.

(6) Click Add SSH key.

(7) If prompted, confirm your GitHub password.

Step 7: Create a Github Repository

In this step you create a repository to store your dockerfile and all its dependencies.

1 Create a repository

Create a repo in github

2 Push code to your repository

(1) change to the root of hello-world directory

$ cd hello-world/

(2) Delete the hidden .git directory If you’re running OSX, type rm -fR .git. Otherwise, type del /S /F /Q .git

$ rm -fR .git

(3) Reinitialize the repository and push the contents to your new GitHub repository using SSH by running the following command:

$ git init
Initialized empty Git repository in /Users/paulzhao/hello-world/.git/

(4) Stage your files:

$ git add .
$ git commit -m "Initial commit"
[master (root-commit) b0a167b] Initial commit
14 files changed, 2517 insertions(+)
create mode 100644 Dockerfile
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 docker-cloud.yml
create mode 100644 docker-compose.yml
create mode 100644 ecs-cluster.template
create mode 100644 ecs-jenkins-demo.template
create mode 100644 ecs-jenkins-demo.template.old
create mode 100644 newfile.txt
create mode 100644 nginx.conf
create mode 100644 php-fpm.conf
create mode 100644 taskdef.json
create mode 100644 www/index.php
create mode 100644 www/logo.png

(5) Set your remote origin.

If you are using SSH, run the following command:

$ git remote add origin 'git@github.com:<your_repo>.git'

If you are using HTTPS, run the following command:

$ git remote add origin 'https://github.com/<your_repo>.git'

e.g.

$ git remote add origin 'https://github.com/lightninglife/jenkinsproject.git'

Note: If you created the SSH key for GitHub on your machine, you can use either method. The HTTPS method requires that you to enter your GitHub username and password at the prompts.

Push your code to GitHub by running the following command:

$ git push -u origin master
Username for 'https://github.com/lightninglife/jenkinsproject.git': lightninglife
Password for 'https://lightninglife@github.com/lightninglife/jenkinsproject.git':
Enumerating objects: 17, done.
Counting objects: 100% (17/17), done.
Delta compression using up to 8 threads
Compressing objects: 100% (15/15), done.
Writing objects: 100% (17/17), 33.36 KiB | 6.67 MiB/s, done.
Total 17 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), done.
To https://github.com/lightninglife/jenkinsproject.git
* [new branch] master -> master
Branch 'master' set up to track remote branch 'master' from 'origin'.

This project includes a file called taskdef.json. You can view it in the GitHub interface or with a text editor on your local machine. This file is the JSON representation of your ECS task definition.

Github

Note: You must supply values for the family and name keys. These are used used later in the Jenkins execution scripts. You have to set the value of the image key to %REPOSITORY_URI%:v_%BUILD_NUMBER%. You will use this mechanism to add the Jenkins build number as a tag to the Docker image.

3 Enable webhooks on your repo so Jenkins is notified when files are pushed

(1) In GitHub repo, click settings

GitHub repo

(2) Under settings, select Webhooks

Webhooks selected

(3) Add webhook

Create a new webhook

(4) Check pushes and pull requests under let me sleect individual events to trigger this webhook

Pushes and pull requests checked

(5) Check active box before click add webhook

Active selected and add webhook

(6) Webhook added successfully

Added webhook

Step 8: Grant permissions to Jenkins to gain access to docker

1 Login to your AWS console and find EC2 instance named Jenkins that we previously provisioned

Locate jenkins instance

2 Click connect to connect to the instance from your Mac terminal

Connect to instance

3 Type in following command line to access to Jenkins instance

$ ssh -i "jenkinskeypair.pem" ec2-user@ec2-3-86-189-99.compute-1.amazonaws.com
Last login: Tue Sep 8 12:20:40 2020 from 99.225.158.153

__| __|_ )
_| ( / Amazon Linux AMI
___|\___|___|

https://aws.amazon.com/amazon-linux-ami/2017.09-release-notes/
26 package(s) needed for security, out of 64 available
Run "sudo yum update" to apply all updates.
Amazon Linux version 2018.03 is available.

4 Grant permissions to Jenkins to gain access to docker

$ sudo groupadd docker
$ sudo usermod -aG docker $USER
$ chmod 777 /var/run/docker.sock

Step 9: Configure Jenkins

In this step you will create a Jenkins Freestyle project to automate the tasks in your pipeline.

In this step you will create a Jenkins Freestyle project to automate the tasks in your pipeline.

1 Create a freestyle project in Jenkins

Freestyle project

2 Under source code management, select git and type the name of your GitHub repository, https://github.com/<repo>.git . Also GitHub credentials need to be added as well with */master provided for branch

Source code management

3 Under build triggers, select Github hook trigger for GITScm polling in order to connect with Github webhook (as soon as we push our script from local environment to Github, jenkins will be triggered sponteneously). Besides, delete workspace before build starts checked under build environment

Build triggers and build environment

4 Select execute shell under add a build step. In the command field, type or paste the following text:

#!/bin/bash
set -x
sudo groupadd docker
sudo usermod -aG docker $USER
chmod 777 /var/run/docker.sock
PATH=$PATH:/usr/local/bin; export PATH
REGION=us-east-1
ECR_REPO="464392538707.dkr.ecr.us-east-1.amazonaws.com/hello-world"
#$(aws ecr get-login --region ${REGION})
aws ecr get-login --no-include-email --region ${REGION}>>login.sh
sh login.sh
Execute shell

5 Select docker build and publish under add a build step. In the repository name field enter the name of your ECR repository. In the tag field, enter v_$BUILD_NUMBER In the Docker registry URL, type the URL of your Docker registry. Use only the fully qualified domain name (FQDN) of the ECR repository you created earlier in Step 3: Create an ECR Registry. http://464392538707.dkr.ecr.us-east-1.amazonaws.com/hello-world

Docker build and publish

6 Select execute shell under add a build step. In the command field, type or paste the following text:

#!/bin/bash
set -x
#Constants
PATH=$PATH:/usr/local/bin; export PATH
REGION=us-east-1
REPOSITORY_NAME=hello-world
CLUSTER=getting-started
FAMILY=`sed -n 's/.*"family": "\(.*\)",/\1/p' taskdef.json`
NAME=`sed -n 's/.*"name": "\(.*\)",/\1/p' taskdef.json`
SERVICE_NAME=${NAME}-service
env
aws configure list
echo $HOME
#Store the repositoryUri as a variable
REPOSITORY_URI=`aws ecr describe-repositories --repository-names ${REPOSITORY_NAME} --region ${REGION} | jq .repositories[].repositoryUri | tr -d '"'`
#Replace the build number and respository URI placeholders with the constants above
sed -e "s;%BUILD_NUMBER%;${BUILD_NUMBER};g" -e "s;%REPOSITORY_URI%;${REPOSITORY_URI};g" taskdef.json > ${NAME}-v_${BUILD_NUMBER}.json
#Register the task definition in the repository
aws ecs register-task-definition --family ${FAMILY} --cli-input-json file://${WORKSPACE}/${NAME}-v_${BUILD_NUMBER}.json --region ${REGION}
SERVICES=`aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ${REGION} | jq .failures[]`
#Get latest revision
REVISION=`aws ecs describe-task-definition --task-definition ${NAME} --region ${REGION} | jq .taskDefinition.revision`
#Create or update service
if [ "$SERVICES" == "" ]; then
echo "entered existing service"
DESIRED_COUNT=`aws ecs describe-services --services ${SERVICE_NAME} --cluster ${CLUSTER} --region ${REGION} | jq .services[].desiredCount`
if [ ${DESIRED_COUNT} = "0" ]; then
DESIRED_COUNT="1"
fi
aws ecs update-service --cluster ${CLUSTER} --region ${REGION} --service ${SERVICE_NAME} --task-definition ${FAMILY}:${REVISION} --desired-count ${DESIRED_COUNT}
else
echo "entered new service"
aws ecs create-service --service-name ${SERVICE_NAME} --desired-count 1 --task-definition ${FAMILY} --cluster ${CLUSTER} --region ${REGION}
fi
Execute shell

7 Now we’re almost there

Before we make it, make sure you have your AWS credentials in your local machine

Firstly, check out config

$ ~/.aws/config

config file shows below

[default]
region = us-east-1
output = json

Secondly, check out credentials

[default]
aws_access_key_id = AKIAWYH7TZJJ34OXDOWD
aws_secret_access_key = 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

If you don’t have aws credentials configured, please type in following texts to configure it

$ aws configure

Step 10: Moment of truth

1 Type following git command to push it to Github

$ git add .
$ git commit -m "initial commit"
[master 545bde6] initial commit
1 file changed, 0 insertions(+), 0 deletions(-)
rename READ.md => README.md (100%)
$ git push
Enumerating objects: 3, done.
Counting objects: 100% (3/3), done.
Delta compression using up to 8 threads
Compressing objects: 100% (2/2), done.
Writing objects: 100% (2/2), 232 bytes | 232.00 KiB/s, done.
Total 2 (delta 1), reused 0 (delta 0), pack-reused 0
remote: Resolving deltas: 100% (1/1), completed with 1 local object.
To https://github.com/lightninglife/jenkinsproject.git
63a9b85..545bde6 master -> master

Notes: In case you did not set up your git remote, please following texts to set it up

$ git remote add my_awesome_new_remote_repo git@git.assembla.com:portfolio/space.space_name.git

my_awesome_new_remote_repo: name of the repo

git@git.assembla.com:portfolio/space.spacee_name.git : your GitHub repo

Step 11: Glory to be

1 Let me check out our Jenkins build

Console output
Successful deployment

2 Check out in AWS console

(1) Log in to the AWS Management Console

(2) Under compute, choose EC2 Container Service.

(3) Choose the name of the cluster your created earlier, for example, getting-started

(4) On the services tab, choose the name of the service your created, for example, hello-world-service

(5) On the task tab, choose the RUNNING task

(6) Under Containers, click on the twisty next to the container name

(7) Under the Container Instance, choose the IP address in the External Link column

Locate your public dns

Voila!

Mission accomplished

Conclusion:

Diagram of CI/ CD

To recap our project, it’s vital to reiterate our insfrastructure using Jenkins as the CI/CD tools. As push our script from our local environment (in my case, mac) to Github, Jenkins will be triggered to push script from Github to AWS ECR registry, then ECS task difinition starts to build up our ECS cluster.

At the very beginning of this project, we went through installations, it’s tedious and dry, but it’s essential to install tools required before we even get started to build up our project.

Then, we moved on to accomplish 10 steps including

Step 1: Build an ECS Cluster

Step 2: Create a Jenkins Server

Step 3: Create an ECR Registry

Step 4: Install Jenkins in AWS EC2

Step 5: Configure Jenkins First Run

Step 6: Create and import SSH keys for Github

Step 7: Create a Github Repository

Step 8: Grant permissions to Jenkins to gain access to docker

Step 9: Configure Jenkins

Step 10: Moment of truth

In the end, I’d like to share a little bit of my troubleshooting experience in regards to this project.

One of the biggest challenges I faced in this project was this error below

Got permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post http://%2Fvar%2Frun%2Fdocker.sock/v1.40/auth: dial unix /var/run/docker.sock: connect: permission denied

I did plenty of research on this error, and attempted more than 30 times in Jenkins. I thought it was permission on my local machine. Ultimately, I figured out it was rather AWS EC2 instance needs to have permission to allow jenkins to have access to docker. This mystery was then resolved in no time.

From this troubleshooting, I learned we must be clear about the infrastructure we were utilizing. Say, in this case, the jenkins is not built locally but in AWS EC2. Therefore, permission should be given to jenkins in AWS EC2.

Last but not least, this jenkins project can be used as a template to deploy any scripting to ECS.

--

--

Paul Zhao
Paul Zhao Projects

Amazon Web Service Certified Solutions Architect Professional & Devops Engineer