Back to top

Automating CI/CD pipeline creation with AWS

A simple way to create blueprints for your microservice and your deployment pipeline.

Problem

With advent of microsevices, we create many smaller logical chunks and are kept either in mono repo or individual repos. Because of the sheer number of services, It is difficult to manage the commonalities and bring some standards whether it is deployment or code structure or code reuse. This is an effort to solve the problem of creating blueprints so that all your services share common structure and deployment approaches. Imagine, you have three microservices in three different repo and all of them have CI/CD pipelines to deploy to your cloud infrastructure. If you are the only one person in the team, You might organize them similarly. When you have multiple people working on the team and many microservices created during the development, you need to create a process which can easily be used by any of your developer to create a repo with the skeleton structure, create a pipeline so that CI/CD happens as your developer merges PR.

I assume you have some familiarity with AWS Services.

Solution Architecture

We will be creating an AWS Service Catalog product. This product will let your developer to spin up an AWS Cloudformation stack which will then create a source control repository in AWS CodeCommit with the skeleton code from S3 bucket. It will also create a AWS CodeBuild project which will automatically be invoked as the developer merges code to the repo. You will also be creating a AWS Lambda function separately that will trigger the codebuild job.

Trigger

Create a lambda function with the following code. This lambda will trigger the codebuild as soon as a pull request is merged into the repository.

const codebuild =  new AWS.CodeBuild();
exports.handler = async(event, context) => {
  let record = event.Records[0];
  let cb = record.customData;
  let reference = record.codecommit.references[0];
  let env = "stage";
  if(reference.ref === 'refs/head/master')
    env = "prod";

  
  let params = {
    projectName: cb,
    environmentVariablesOverride:[{
      name: "DEPLOYMENT_ENVIRONMENT",
      value: env
    }],
    sourceVersion: reference.commit

  };
  await codebuild.startBuild(params).promise();
}

Blueprint

Create a base code for your microservice. Consider, you use serverless to create your lambda function and your team uses nodeJS as the language, You can create a following base code

src
├── index.js
|── package.json
|── Serverless.yml
|── test
|   ├── index.test.js
buildspec.yml
env.prod.yml
env.dev.yml
.gitignore
README.md

create a zip of your base code and upload to the S3 bucket. We will need the location of the S3 in the template we are going to create.

Cloudformation Templates

We are going to create a cloudformation template with the following resources

Codecommit

CodeCommitRepo:
  Type: AWS::CodeCommit::Repository
  Properties:
    RepositoryName: !Ref CodeCommitRepoName
    RepositoryDescription: !Sub 'Repo created through Service catalog'
    Code:
      S3:
        Bucket: !Ref CodeBucket
        Key: !Ref CodePath
    Triggers:
    - Name: !Join ["-", [!Ref CodeCommitRepoName, !Ref BuildTriggerLambdaName]]
      CustomData: !Sub '${CodeCommitRepoName}-codebuild'
      DestinationArn: !Sub 'arn:aws:lambda:${AWS::Region}:${AWS::AccountId}:function:${BuildTriggerLambdaName}'
      Branches:
      - Master
      - Stage
      Events:
      - updateReference

Create permission for CodeCommit to trigger lambda

CodeCommitRepoLambdaPermission:
  Type: AWS::Lambda::Permission
  DependsOn:
     - CodeCommitRepo
  Properties: 
    Action: lambda:InvokeFunction
    FunctionName: !Ref BuildTriggerLambdaName
    Principal: 'codecommit.amazonaws.com'
    SourceArn: !GetAtt CodeCommitRepo.Arn

Create a role and policy for CodeBuild to assume

  CodeBuildRole:
    Description: CodeBuild Service role
    Properties:
      AssumeRolePolicyDocument:
        Statement:
        - Action: sts:AssumeRole
          Effect: Allow
          Principal:
            Service: codebuild.amazonaws.com
      Path: /
      RoleName: !Join
        - '-'
        - - !Ref 'AWS::StackName'
          - CodeBuild
    Type: AWS::IAM::Role

Create a policy with all the permission that would require for your codebuild and attach to the codebuild role.

Create a CodeBuild Project

  CodeBuildProject:
    Type: AWS::CodeBuild::Project
    DependsOn:
    - CodeBuildPolicy
    Properties:
      Artifacts:
        Type: NO_ARTIFACTS
      Description: !Join
        - ''
        - - 'CodeBuild Project for '
          - !Ref 'AWS::StackName'
      Environment:
        ComputeType: BUILD_GENERAL1_SMALL
        Image: aws/codebuild/nodejs:8.11.0
        Type: LINUX_CONTAINER
        EnvironmentVariables:
          - Name: DEPLOYMENT_ENVIRONMENT
            Type: PlainText
            Value: !Ref 'Environment'
      Name: !Sub '${CodeCommitRepoName}-codebuild'
      ServiceRole: !Ref 'CodeBuildRole'
      Source:
        Type: CODECOMMIT
        Location: !GetAtt [CodeCommitRepo, CloneUrlHttp]
        BuildSpec: 'buildspec.yml'

Product in AWS Service Catalog

Create your product in AWS Service catalog and add it to a portfolio. Refer the documentation here. Make sure your version of the product is active and portfolio is shared with the IAM users. You can do it from the portfolio panel of AWS Service Catalog. IAM users can login and view the product and launch the product. When they launch the product, It creates all the resources outlined above.

References