This Post is about creating a CI/CD pipeline on AWS using CodePipeline which deploys Infrastructure on AWS using Terraform.
Contents
- Project Overview
- Setting up Terraform
- Setting up AWS CodePipeline
- Source stage
- Terraform Plan step
- Manual Approval step
- Terraform Apply stage
- Deploy stage
- Final View Of the Pipeline
- Conclusion
1. Project Overview
This project is First part in the series #CloudGuruChallenge – Event-Driven Python on AWS. Here we deploy an s3 buckets and a lambda function. The lambda function will be part of an AWS Step Functions Workflow which will be developed in the next part of this series and the S3 bucket is used to store the lambda deployment.
To automate the process Terraform is used for IaC (Infrastructure as Code) and AWS CodePipeline is used for CI/CD.
2. Setting up Terraform
The following are the required steps to start working with Terraform on AWS:
- Create an S3 Bucket which will store the terraform state file.
- Create a dynamodb table with on demand capacity with a primary key of LockID.
The above steps will configure terraform with S3 as the backend. The provider.tf and backends.tf file is shown below.
provider.tf
terraform {
required_providers {
aws = {
source = "hashicorp/aws"
version = "~> 3.0"
}
}
}
# Configure the AWS Provider
provider "aws" {
region = "us-east-1"
}
backends.tf
terraform {
backend "s3" {
bucket = "YOUR-BUCKET-NAME"
key = "terraform.tfstate"
region = "YOUR-REGION-NAME"
dynamodb_table = "terraform-state-lock"
}
}
Create a sample lambda_function.py and zip it in the same directory as the .tf files with name lambda_function_payload.zip. After which the IaC for S3 and lambda could be written as shown below:
S3.tf
resource "aws_s3_bucket" "lambda_s3_buckets" {
bucket = "YOUR-BUCKET-NAME"
acl = "private"
force_destroy = true
}
resource "aws_s3_bucket_object" "object" {
bucket = "YOUR-BUCKET-NAME"
key = "YOUR-PATH-TO-STORE-LAMBDA-DEPLOYMENT"
source = "lambda_function_payload.zip"
depends_on = [
aws_s3_bucket.lambda_s3_buckets,
]
}
Lambda.tf
resource "aws_lambda_function" "state_machine_lambdas" {
function_name = "YOUR-FUNCTION-NAME"
role = "ROLE-ARN"
handler = "lambda_function.lambda_handler"
s3_bucket = "BUCKET-NAME_WITH-LAMBDA-DEPLOYMENT"
s3_key = "PATH-TO-LAMBDA-DEPLOYMENT"
runtime = "python3.8"
depends_on = [
aws_s3_bucket_object.object,
]
}
Store all the .tf files in a folder named terraform.
3. Setting up AWS CodePipeline
The AWS CodePipeline will be used for CI/CD (Continuous Integration/Continuous Delivery). The AWS free tier allows 1 free pipeline per month. Our pipeline consists of five stages viz source ,build (Terraform Plan), build (Manual Approval step), Terraform Apply and Deploy.
Source Stage
Here Github is used as a source repository for the pipeline. The configuration for this stage is shown below.
- Github version 2 is selected as the source code repository. You can choose others such as AWS codecommit etc. For github you need to connect to it which is a straight forward process through the console.
- The repository name and branch name must be set up which will trigger the pipeline whenever there is a code change in that specific branch in the repository.
Build Stage(Terraform Plan Step)
This stage consists of two builds, a manual approval step and a deploy step. The first build is for terraform plan. AWS Code Build is used for creating the build projects. To set this up, in the pipeline stages add a new action group under the build stage as shown below
Adding a build action to CodePipeline
The project must be AWS Code Build Project which can be created by clicking on create project which will open the wizard as shown below.
CodeBuild Project For Terraform Plan
The important configuration above is
- selecting 3gb memory with 2cpus (which is included in free tier)
- The service role arn (which gives terraform the permission to provision AWS resources). It must contain all permissions which terraform needs such as access to S3, lambda etc.
- The env variable TF_COMMAND_P which will be used in buildspec file.
- The path to buildspec.yml file which contains the build commands.
After Configuring the above the git repository it must contain a buildspec file which contains the necessary commands. It is shown below
Terraform_Plan.yml
version: 0.1
phases:
install:
commands:
- "apt install unzip -y"
- "wget
https://releases.hashicorp.com/terraform/0.14.10/terraform_0.14.10_linux_amd64.zip"
- "unzip terraform_0.14.10_linux_amd64.zip"
- "mv terraform /usr/local/bin/"
pre_build:
commands:
- terraform -chdir=Terraform init -input=false
build:
commands:
- terraform -chdir=Terraform $TF_COMMAND_P -input=false -no-color
post_build:
commands:
- echo terraform $TF_COMMAND_P completed on `date`
Manual Approval Step
Create a manual approval build action after the Terraform plan action as shown below.
You must create an SNS topic with a subscription to your email address to recieve notifications about approval and rejections.
Terraform Apply Stage
Follow a similar process as Terraform plan for Terraform Apply build stage with the following buildspec file.
Terraform_Apply.yml
version: 0.1
phases:
install:
commands:
- "apt install unzip -y"
- "wget https://releases.hashicorp.com/terraform/0.14.10/terraform_0.14.10_linux_amd64.zip"
- "unzip terraform_0.14.10_linux_amd64.zip"
- "mv terraform /usr/local/bin/"
pre_build:
commands:
- terraform -chdir=Terraform init -input=false
build:
commands:
- terraform -chdir=Terraform $TF_COMMAND_A -input=false -auto-approve
post_build:
commands:
- echo terraform $TF_COMMAND_A completed on `date`
Deploy Stage
Now the final stage would be deploy where code build is used to deploy code to lambda via s3 bucket. Create a folder in your repo called lambda_code and store your lambda_function.py in it. Create a new code build project. All the steps for code build project will be the same. The buildspec file for the project is shown below.
deploy_state_machine_code.yml
version: 0.1
phases:
pre_build:
commands:
- mkdir -p ./lambda_code/zipped
- zip -r -j lambda_code/zipped/lambda-function-payload.zip lambda_code/*
build:
commands:
- aws s3 sync ./lambda_code/zipped s3://YOUR-BUCKET-NAME/YOUR-S3-KEY --delete
- aws lambda update-function-code --function-name YOUR-FUNCTION-NAME --s3-bucket YOUR-BUCKET-NAME --s3-key YOUR-S3-KEY-TO-FILE
post_build:
commands:
- echo state_machine_code was deployed to lambda from S3 bucket on `date`
4. Final View Of the Pipeline
After the above steps the pipeline should look something like the one shown below.
Pushing to github will trigger the pipeline and the build process can be viewed through the details option on each build action.
5. Conclusion
In this post I have covered how to deploy infrastructure on AWS with terraform through AWS CodePipleine. In the next part of the series I'll show how to set up AWS Step Functions workflow which is triggered through a cloudwatch event. Stay Tuned!
Top comments (4)
This is very useful article. I am following the same steps, but I am getting below error.
"COMMAND_EXECUTION_ERROR: Error while executing command: terraform -chdir=Terraform init -input=false. Reason: exit status 1"
dev-to-uploads.s3.amazonaws.com/up...
Hey Ashish, I was able to discover the problem here. In your yml files, remove the -chdir=Terraform. It is attempting to change directory and is unable to find it. Removing this allowed me to run plan and apply successfully!
Hi,
I have multiple builds, In first build it the terraform files get deployed from my repo
When i create a new tf file and upload it in the git repo, the trigger get activated and the plan build gets successful while the apply gets failed due to the below error,
Error: creating Amazon S3 (Simple Storage) Bucket (pipeline-artifacts-gkr-13): BucketAlreadyOwnedByYou: Your previous request to create the named bucket succeeded and you already own it.
status code: 409, request id: FMVJKZJ5BSBQJHFH, host id: 8xzIzU1j5KQ7KjYVM6kDIB9vnOmFqIpMXqPjWyjN/DejK7jTi71j7LgfVPy56EPMYxrjVxCwsOM=
on g.tf line 1, in resource "aws_s3_bucket" "codepipeline_artifacmts_gkr_c":
1: resource "aws_s3_bucket" "codepipeline_artifacmts_gkr_c" {
Trying to follow this...
... Create a sample lambda_function.py and zip it
Is there a copy of this file "lambda_function.py" ?
Thanks