Golden image creation using Packer and AWS CodePipeline

Hi All, we know that Packer can be used to create Golden images for multiple platforms. Here we will use Packer to create an Golden image of Amazon Linux OS in AWS. The created images are called as AMI which appear in AWS dashboard. The image creation is necessarry in situations when we want the OS to have pre set of packages installed to support our application. The custom created AMI can be used to spin up EC2 instances when we need to build large infrastructure frequently to support the applications.

In this tutorial I will be using AWS CodeCommit, CodeBuild and create a CodePipeline with these. The CodePipeline will automatically get triggered when a commit happens to the CodeCommit repo. The pipeline will run the CodeBuild which will trigger the buildspec.yml and use the packer build command mentioned in it to build the Golden image (AMI)

I will be commiting 2 files to CodeCommit – buildspec.yml and CreateAMI.json file

Below is the content of buildspec.yml

---
version: 0.2

phases:
  pre_build:
    commands:
      - echo "Installing HashiCorp Packer..."
      - curl -qL -o packer.zip https://releases.hashicorp.com/packer/0.12.3/packer_0.12.3_linux_amd64.zip && unzip packer.zip
      - echo "Installing jq..."
      - curl -qL -o jq https://stedolan.github.io/jq/download/linux64/jq && chmod +x ./jq
      - echo "Validating CreateAMI.json"
      - ./packer validate CreateAMI.json
  build:
    commands:
      ### HashiCorp Packer cannot currently obtain the AWS CodeBuild-assigned role and its credentials
      ### Manually capture and configure the AWS CLI to provide HashiCorp Packer with AWS credentials
      ### More info here: https://github.com/mitchellh/packer/issues/4279
      - echo "Configuring AWS credentials"
      - curl -qL -o aws_credentials.json http://169.254.170.2/$AWS_CONTAINER_CREDENTIALS_RELATIVE_URI > aws_credentials.json
      - aws configure set region $AWS_REGION
      - echo "AWS region set is:" $AWS_REGION
      - aws configure set aws_access_key_id `./jq -r '.AccessKeyId' aws_credentials.json`
      - aws configure set aws_secret_access_key `./jq -r '.SecretAccessKey' aws_credentials.json`
      - aws configure set aws_session_token `./jq -r '.Token' aws_credentials.json`
      - echo "Building HashiCorp Packer template, CreateAMI.json"
      - ./packer build CreateAMI.json
  post_build:
    commands:
      - echo "HashiCorp Packer build completed on `date`"

Below is the content of CreateAMI.json

{
    "variables": {
        "aws_region": "{{env `AWS_REGION`}}"
    },
  "builders": [
    {
      "type": "amazon-ebs",
      "region": "{{user `aws_region`}}",
      "instance_type": "t2.micro",
      "source_ami": "ami-0080e4c5bc078760e",
      "ssh_username": "ec2-user",
      "ami_name": "custom-Dev1",
      "ami_description": "Amazon Linux Image OS with pre-installed packages",
      "run_tags": {
        "Name": "custom-Dev1",
	"Env": "dev",
	"Project": "DevOps"
      }
    }
  ],
  "provisioners": [
    {
      "type": "shell",
      "inline": [
        "sudo yum install java python wget -y",
	"sudo yum install tomcat -y"
      ]
    }
  ]
}

1. Create an AWS CodeCommit Repository and add these 2 files into it.
2. Create AWS CodeBuild project and select CodeCommit repo and master branch
3. Create CodePipeline by selecting CodeCommit repo and CodeBuild project as stages.
Screenshot from 2018-12-19 12-06-05

Screenshot from 2018-12-19 12-07-56

Screenshot from 2018-12-19 12-08-52

Skip Deploy stage and create the pipleine.

4. Select the created CodePipeline and click on Release changes which will start running the pipeline.

5. After the pipeline finishes successfully, go to the EC2 dashboard and click on AMI in left side and you should see the created Golden image.

Advertisements

AWS Cloudformation – cfn-init and UserData

We use AWS Cloudformation to provision resources in AWS. There are lot of examples available in internet on different use cases.
The Cloudformation scripts can be written using yaml or json language.

Using AWS CloudFormation we can automatically install, configure, and start applications on Amazon EC2 instances. Doing so enables you to easily duplicate deployments and update existing installations without connecting directly to the instance, which can save you a lot of time and effort.

For installing packages automatically on EC2 instance upon boot up we need to use cfn-init and metadata in Cloudformation.

Below is an example on how cfn-init and metadata is defined in the Cloudformation script and how they work.Understanding of this is very important if you want to have packages installed on to EC2 instance upon boot up.

The cfn-init helper script reads template metadata from the AWS::CloudFormation::Init key and acts accordingly to:
Fetch and parse metadata from AWS CloudFormation
Install packages
Write files to disk
Enable/disable and start/stop services

cfn-init does not require credentials, so you do not need to use the –access-key, –secret-key, –role, or –credential-file options.
In this case we are connecting to S3 to download the scripts. So we need to create a role which allows access to get objects in S3 bucket, and then attach this role to EC2 instance.

Put your commands and scripts to be run on EC2 instance in the commands section under AWS::CloudFormation::Init

This will be invoked in the UserData section
/opt/aws/bin/cfn-init –verbose –stack –region us-east-1 –resource Create_Instance

We also have to install cfn as give in UserData section and start cfn service before invoking the AWS::CloudFormation::Init

Note: Please use json editor to format the below code before using it.

“Resources”: {
“Create_Instance”: {
“Type”: “AWS::EC2::Instance”,
“Metadata”: {
“AWS::CloudFormation::Init”: {
“config”: {
“commands”: {
“01_mkdir_scripts”: {
“command”: “if [ ! -d \”/home/ec2-user/scripts \” ] ; then mkdir -p \”/home/ec2-user/scripts\” ; fi;”
},
“02_copy_scripts_from_s3”: {
“command”: “/usr/bin/aws s3 cp s3://testBucket19/installScripts /home/ec2-user/scripts –recursive”
},
“03_Install_java”: {
“command”: “/bin/bash -x /home/ec2-user/scripts/installJava.sh”,
“waitAfterCompletion”: “50”
},
“04_Install_Tomcat”: {
“command”: “/bin/bash -x /home/ec2-user/scripts/installTomcat.sh”,
“waitAfterCompletion”: “50”
}
}
}
}
}
},

“Properties”: {
“UserData”: {
“Fn::Base64”: {
“Fn::Join”: [
“”,
[
“#!/bin/bash\n”,
“yum install -y python-pip\n”,
“pip install awscli\n”,
“/usr/bin/easy_install –script-dir /opt/aws/bin https://s3.amazonaws.com/cloudformation-examples/aws-cfn-bootstrap-latest.tar.gz\n”,
“cp -v /usr/lib/python2*/site-packages/aws_cfn_bootstrap*/init/redhat/cfn-hup /etc/init.d \n”,
“chmod +x /etc/init.d/cfn-hup \n”,
“/etc/init.d/cfn-hup start \n”,
“/opt/aws/bin/cfn-init –verbose –stack “,
{
“Ref”: “AWS::StackId”
},
” –resource Create_Instance –region “,
{
“Ref”: “AWS::Region”
},
“\n”,
“/opt/aws/bin/cfn-signal -e 0 –stack “,
{
“Ref”: “AWS::StackName”
},
” –resource Create_Instance “,
” –region “,
{
“Ref”: “AWS::Region”
},
“\n”
]
]
}
}
}

Delete file in sub-directory of S3 using Python

Hi All,
We use boto3 libraries to connect to S3 and do actions on bucket for objects to  upload, download, copy, delete. But let’s say if you want to download a specific object which is under a sub directory in the bucket then it becomes difficult to its less known on how to do this.

Below are few python script examples on using prefix of the subdirectory with boto and boto3 libraries

Example 1: Copy a file/object which is residing in a subdiretory of Bucket1 to Bucket2

import boto
conn = boto.connect_s3()

srcBucket = conn.get_bucket('testProjBucket-1') #Source Bucket name
dstBucket = conn.get_bucket('testProjBucket-2') #Destination Bucket name
fileName='test.txt'

dstBucket.copy_key('Dir2/subDir2/'+fileName,srcBucket,'Dir1/subDir1/'+fileName)

Example 2: Downloads the test.txt from bucket ‘testProjBucket-1’ to the local system path /home/ec2-user/mydownloads/
Here the downloaded file name will be as hai.txt

import boto3
s3 = boto3.resource('s3')

fileName="test.txt"
prefix1=('Dir1/subDir1/'+fileName)

s3.meta.client.download_file('testProjBucket-1', prefix1, '/home/ec2-user/mydownloads/hai.txt')

Example 3: Delete a specific object from a specific sub-directory inside a bucket (Using boto libraries)

import boto
conn = boto.connect_s3(region_name='', aws_access_key_id = '', aws_secret_access_key = '')

fileName = "test.py"

srcBucket = conn.get_bucket('testProjBucket-1')
srcBucket.delete_key('Dir1/subDir1/'+fileName)

Example 4: Delete a specific object from a specific sub-directory inside a bucket (Using boto3 libraries)

import boto3
client = boto3.client('s3', region_name='us-east-1', aws_access_key_id = '', aws_secret_access_key = '')
fileName="test.txt"

prefix1=('Dir1/subDir1/'+fileName)

response = client.delete_object(
Bucket='testProjBucket-1',
Key=prefix1
)

Note: There is no move command for object in boto3 library. We can only use copy command. But we can use move in the aws-cli

How to upload and run Nodejs package using AWS Lambda

Hey guys.. If you have tried using Nodejs code to run in AWS Lambda you know how painful it is to package the node modules with needed libraries to make it work in Lambda function. Yes it is difficult in begining but once you start exploring and understanding it becomes so much interesting as what all you can achieve using nodejs.

Here I will using nodejs UUID module to generate a unique id which can be used in application or in database. The AWS documentation tells that “You can create a deployment package yourself or write your code directly in the Lambda console, in which case the console creates the deployment package for you and uploads it, creating your Lambda function.” but there is no step-by-step instructions and screenshots to show how to do it. And also you won’t get much information in other blogs as I have tried exploring and ended up without proper steps. So, I like to show you here how to do.

The best way is to install nodejs and test the code on your local linux or windows environment and then package and upload the zip file to Lambda function.

Install nodejs using this command (the OS is RedHat)


curl --silent --location https://rpm.nodesource.com/setup_6.x | sudo bash -
sudo yum -y install nodejs

You can verify the installation by checking the node version (node -v) and npm version (npm -v)

 

Install the UUID module using below command


npm install uuid

 

You will get node_modules directory under /home/ec2-user

Navigate to directory /home/ec2-user/node_modules/uuid

and zip the all the files under this
zip -r TestnpmLambda1.zip *

Go to AWS Lambda and create a function
Select nodejs

1

2

Upload the zip file to Lambda function

Next copy the below code to the “edit code inline”


exports.handler = (event, context, callback) => {
var uuid = require('uuid');
console.log(uuid.v4());
};

Next save and test the function by creating a event

3

Then you should see new random UniqueID generated every time when you test this function.

 

Fetch the Elastic Beanstalk environment details using python script

Hi there!!

Its been quite sometime and have been busy working on multiple technologies. Recently my lead asked me to create a python script to fetch the minimum and maximum instances count of all the Elastic beanstalk environments. It was great to work on this requirement. Below is the python script

def get_details():
	row1=['Application Name','Environment Name','Min Count','Max count']
	with open('EB-instances-count',"a") as csvDataFile:
		writer = csv.writer(csvDataFile)
		writer.writerow(row1)
	try:
		eb = boto3.client('elasticbeanstalk',"us-east-1")
		NameInfo=eb.describe_environments()
		for names in NameInfo['Environments']:
			app_name=(names['ApplicationName'])
			env_name=(names['EnvironmentName'])  
			response = eb.describe_configuration_settings(
				EnvironmentName=env_name,
				ApplicationName=app_name
			 )
			minCount=response['ConfigurationSettings'][0]['OptionSettings'][4]
			maxCount=response['ConfigurationSettings'][0]['OptionSettings'][3]
			minVal=minCount['Value']
			maxVal=maxCount['Value']
			print "Gathering count for Environment: " + env_name
			fields=[app_name,env_name,minVal,maxVal]
			with open('EB-instances-count.csv',"a") as csvDataFile:
				writer = csv.writer(csvDataFile)
				writer.writerow(fields)
	except ClientError as e:
		if e.response['Error']['Code'] == "InvalidParameterValue":
			print env_name + " Environment not found, so skipping it"
		pass
	
if __name__ == '__main__':
	get_details()

In this I am getting the application names and environment names of Elastic beanstalk in a region and parsing through them and fetching the min and max instances count. Also after fetching the counts I am writing them to .csv (spreadsheet) file. We can run this script at any time to know the present count of instances being used. This script can be further updated/modified to fetch different information of the environments in Elastic beanstalk.

The interesting part would be to filter the required information from the response. And other thing is lets say if the environment is deleted it will take sometime to disappear from the console and we might see error as we can still get the environment name but not its settings as its already deleted right.
So in this case we have to capture the particular error the exception part and ignore it.

Note: Be careful about the indentation 🙂

Let me know for any questions. Thanks

EBS snapshots deletion by filtering tags

Hey guys!!

Having daily backups of your data is the most important thing in IT industry. EBS snapshots are used to backup Amazon EBS volume with data. Taking regular backup of the volumes decreases the risk of disaster incase of failures. For more detail refer to this post here

Here we taking EBS snapshots for Production environment daily and its not required to have many snapshots as the cost will increase. So in such cases we will be deleting the snapshot after 10 days from the backup date, so that we will endup having 10 snapshots at any given point of time.

The below python script will uses the boto3 library to connect to AWS and fetch the details of services. When a EBS snapshot is created for a EC2 instance, there will be a tag created for snapshot with instanceId details and DateToDelete key with value of future 10th day date.

We will be using two arrays to filter the snapshot tags with key ebsSnaphots_clean:true and instance tags with Environment:Prod
Next we will use for loop to parse through all the ec2 instance details which have tag value and key as Environment:Prod

Similarly we will parse through the EBS snapshots with ebsSnaphots_clean:true and Deletion_date having today’s date.
Next we will fetch the tags and compare the snapshot instanceID with the respective EC2 instanceID of production environment and if they match then that respective snapshot will be deleted.

import boto3
import datetime
import dateutil
from dateutil import parser
from boto3 import ec2

ec = boto3.client('ec2')

def lambda_handler(event, context):
    Deletion_date = datetime.date.today().strftime('%Y-%m-%d')
    firstFilter = [
        {'Name': 'tag-key', 'Values': ['DateToDelete']},
        {'Name': 'tag-value', 'Values': [Deletion_date]},
		{'Name': 'tag-key', 'Values': ['ebsSnaphots_clean']},
		{'Name': 'tag-value', 'Values': ['true']},
    ]

    secondFilter = [
        {'Name': 'tag-key', 'Values': ['Environment']},
		{'Name': 'tag-value', 'Values': ['Sandbox']},
    ]	

    snapshot_details = ec.describe_snapshots(Filters=firstFilter)
    ec2_details = ec.describe_instances(Filters=secondFilter)
	
    for myinst in ec2_details['Reservations']:
        for instID in myinst['Instances']:
            print "The instanceID is %s" % instID['InstanceId']
            Instance_ID = instID['InstanceId']
            for snap in snapshot_details['Snapshots']:
                print "Checking Snapshot %s" % snap['Snapshot_Id']
                for tag in snap['Tags']:
                    if tag['Key'] == 'snap_InstanceID':
                        match_instance = tag['Value']
                        if Instance_ID == match_instance:
                            print "Checking Snapshot %s" % snap['Snapshot_Id']
                            print "the instanceID " +Instance_ID+ " matches with the Snapshot assigned instanceID tag " +match_instance+ " for snapshot %s" % snap['Snapshot_Id']
                            print "Deleting snapshot %s" % snap['Snapshot_Id']
                            ec.delete_snapshot(SnapshotId=snap['Snapshot_Id'])
                        else:
                            print "The instance " +Instance_ID+" is of different environment and do not match with snapshot "+match_instance
                    else:
                        print "no matches"

Note: Please check and take care of indentation

Thanks!!

CloudFormation template to create CodeDeploy application

The requirement is to use cloudformation stacks to create CodeDeploy application and deployment group with required configuration. Although we get the information about which resources to use its all bits and pieces. It took me couple of hours to understand and write the CloudFormation template and use it to create codedeploy application.

Basically we have to create two cloudformation stacks.
1. stack1 – to create codedeploy application
2. stack2 – to create deployment group and other parameters and associate it with the codedeploy application created.
The AWS::CodeDeploy::Application resource creates an AWS CodeDeploy application. You can use the AWS::CodeDeploy::DeploymentGroup resource to associate the application with an AWS CodeDeploy deployment group.
stack1 – codedeploy-appName.json
creates codedeploy application
 
stack2 – codedeploy-deployGrp.json
associates with codedeploy application, creates deployment group, deployment config name, adds ec2 tag filters and ServiceRoleArn
Below is the template
codedploy-appName.json

{
“AWSTemplateFormatVersion”: “2010-09-09”,
“Resources”: {
“MyCodeDeployApp”: {
“Type” : “AWS::CodeDeploy::Application”,
“Properties” : {
“ApplicationName” : “App11”
}
}
}
}

codedploy-deployGrp.json
{
“AWSTemplateFormatVersion”: “2010-09-09”,
“Parameters”: {
“DeploymentConfigurationName”: {
“Description”: “With predefined configurations, you can deploy application revisions to one instance at a time, half of the instances at a time, or all the instances at once.”,
“Type”: “String”,
“Default”: “CodeDeployDefault.OneAtATime”,
“ConstraintDescription”: “Must be a valid Deployment configuration name”
}
},

“Resources”: {
“MyCodeDeploy” : {
“Type” : “AWS::CodeDeploy::DeploymentGroup”,
“Properties” : {
“ApplicationName” : “App11”,
“DeploymentConfigName” : {
“Ref”: “DeploymentConfigurationName”
},
“DeploymentGroupName” : “DeployGrp11”,
“Ec2TagFilters” : [{
“Key” : “Name”,
“Type” : “KEY_AND_VALUE”,
“Value” : “App1-env”
}],
“ServiceRoleArn” : “arn:aws:iam::326840742362:role/deployRole”
}
}
}
}