Not too long ago, we’ve got turn out to be more and more dissatisfied with the time our AWS CodePipeline pipeline takes to deploy a change to manufacturing. The pipeline builds the code, runs unit checks, deploys to a check setting, runs acceptance checks within the check setting, and deploys to manufacturing. It takes 27 minutes for a full run of our pipeline—too lengthy for impatient builders like me. We analyzed the efficiency intimately and determined emigrate to GitHub Actions.
Learn on to study in regards to the pitfalls and causes for the migration.
Pipeline
Earlier than we begin, right here is an summary of the pipeline (click on on the picture for an enormous CodePipeline screenshot). The pipeline deploys our serverless utility: ChatOps for AWS – marbot.
Why CodePipeline and CodeBuild are sluggish
The next desk lists the runtime of every stage of our CodePipeline pipeline.
Stage | Runtime (mm:ss) |
---|---|
Commit | 07:01 |
Acceptance | 09:35 |
Manufacturing | 09:56 |
Complete | 26:32 |
We recognized the next efficiency points:
- CodePipeline provides a tiny however noticeable overhead with every motion. E.g., deploying a CloudFormation stack is normally a second slower than calling the CloudFormation API straight. This provides up if you happen to run actions in sequence due to dependencies (e.g., database deployment should occur earlier than app deployment).
- CodePipeline solely orchestrates the pipeline. To run a script, you want to combine CodePipeline with CodeBuild. CodeBuild provides important overhead. Queuing instances of 60 seconds and provisioning instances of 10 seconds aren’t uncommon (us-east-1). We use seven CodeBuild actions and see greater than 7 minutes of overhead due to that.
- We use
aws cloudformation bundle
and the CloudFormation remodelAWS::Serverless-2016-10-31
, aka SAM, to deploy our utility.aws cloudformation bundle
makes use of a easy implementation to detect code modifications that set off a deployment of Lambda features. We useesbuild
to construct our supply code. Even when the content material of the output information keep the identical, the altering file creation date triggers a deployment.
Points 1 and a couple of are simpler to repair with migrating to a distinct know-how. Situation 2 may very well be partly addressed by combining a number of CodeBuild tasks into one, making it tougher to establish what went mistaken rapidly (downloading dependencies failed, checks failed, bundle failed, …) with out checking the logs. Situation 3 has nothing to do with CodePipeline. To deal with points 1 and a couple of, we determined emigrate to GitHub Actions. I additionally discovered a workaround for subject 3.
After we determined to deploy our AWS infrastructure and serverless app with GitHub Actions as a substitute of CodePipeline, we realized just a few issues we wish to share with you.
Artifacts
In CodePipeline, every motion takes enter artifacts (exceptions are the supply actions on the very starting of the pipeline) and optionally produces output artifacts. It’s essential to guarantee (by way of runOrder
) that motion output artifacts are created earlier than they’re used as enter artifacts in different actions.
GitHub Actions works otherwise. First, there’s a distinction between a job and a step. A job usually consists of a number of steps. All steps share the identical runner setting and may entry information created by earlier steps (like in a CodeBuild construct). If you wish to go artifacts from one job to a different, you have to use the GitHub Motion upload-artifact and download-artifact.
AWS Authentication
Our pipeline deploys each: our serverless utility and the required AWS infrastructure. Subsequently, the pipeline requires entry to the AWS APIs to configure the API Gateway or Lambda. Consequently, we’re utilizing AWS IAM to configure entry permissions by way of insurance policies.
In CodePipeline, you outline the IAM function (by way of roleArn
) that CodePipeline assumes to run your pipeline. Understand that every CodeBuild mission requires its personal IAM Position.
In GitHub Actions, you additionally outline the IAM function, however you want a small piece of AWS infrastructure to make it work known as an OpenID Join (OIDC) identification supplier, a part of the IAM service.
-
Take a look at our CloudFormation template to set every thing up in a minute.
-
Add the next permissions to your workflows on the high stage:
permissions:
id-token: write
contents: learn -
Use the GitHub Motion aws-actions/configure-aws-credentials to imagine the function in your workflow like this:
permissions: {}
jobs:
demo:
runs-on: ubuntu-22.04
steps:
- makes use of: actions/checkout@v4
- makes use of: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:function/github-openid-connect
role-session-name: github-actions
aws-region: us-east-1
Working a script
I already talked about that CodePipeline solely orchestrates the pipeline. To run a script, you want to combine CodePipeline with CodeBuild. The next CloudFormation snippet reveals the required AWS assets and the mind-blowing complexity:
Assets: |
GitHub Actions has native assist for working scripts. The next workflow installs Node.js dependencies by way of npm
and runs unit checks on every push to grasp. The check studies are archived as an artifact and could be inspected manually:
title: unit-test |
The default shell for CodeBuild and GitHub Actions
CodeBuild defaults to sh
and stops when a “command” from the instructions record fails.
GitHub Actions defaults to bash
with the next settings:
-e
: Exit instantly if a command exits with a non-zero standing.
I normally set the default shell to bash in my workflows on the high stage:
defaults: |
GitHub Actions now runs bash
with the next settings:
--noprofile: Don't learn both the system-wide startup file
/and many others/profileor any of the private initialization information
/.bash_profile/.bash_login,
, or
~/.profile`.--norc
: Don’t learn and execute the private initialization file ~/.bashrc if the shell is interactive.-e
: Exit instantly if a command exits with a non-zero standing.-o pipefail
: The return worth of a pipeline is the standing of the final command to exit with a non-zero standing, or zero if no command exited with a non-zero standing
AWS Integrations
CodePiepline integrates with a bunch of AWS companies natively:
- Amazon ECR
- Amazon ECS
- Amazon S3
- AWS CloudFormation
- AWS CodeBuild
- AWS CodeCommit
- AWS CodeDeploy
- AWS System Farm
- AWS Elastic Beanstalk
- AWS Lambda
- AWS OpsWorks Stacks
- AWS Service
AWS presents the next actions for GitHub Actions (a few of them aren’t maintained):
On high of that, you could find many third celebration actions within the GitHub Market.
For instance, we forked the official AWS CloudFormation motion and added parallel stack deployments.
Parallelization
We realized that executing steps in parallel just isn’t really easy in GitHub Actions. CodePipline supplies significantly better assist for that! That’s why we added parallel stack deployments to our fork of the official AWS CloudFormation motion.
Reusability
In CodePipeline, there isn’t a solution to reuse a stage. My Acceptance
and Manufacturing
phases are very related. If I make a change to one among them, I’ve to recollect to make the change to the opposite stage as properly. There isn’t any solution to reuse a stage or a pipeline.
GitHub Actions supplies a solution to reuse workflows.
I’ve created a deploy workflow like this with an enter to point if dev or prod must be deployed:
|
Handbook approval
CodePipeline helps guide approvals.
Sadly, GitHub Actions helps guide approvals just for organizations subscribed to the Enterprise plan.
Our workaround for marbot is that this:
- We deploy to dev when the grasp department modifications.
- We create a tag v1.y.z to deploy to prod.
The primary limitation of this workaround is that we’ve got to make use of two workflows. There isn’t any simple solution to share artifacts between workflows (workarounds exist). For now, we run npm ci && npm run construct
twice and hope that the result is identical.
Fixing aws cloudformation bundle
In the event you agree with me that solely a change within the content material of a file/information is related to resolve if a brand new deployment of a Lambda perform is required, you’ll be able to add the next line earlier than your aws cloudformation bundle
command (assuming your construct output is saved within the construct
folder):
discover construct/ -exec contact -m --date="2020-01-01" {} ; |
The above command will set the creation time of all information contained in the construct
folder to 2020-01-01 (the date doesn’t matter so long as it stays fixed). Subsequently, aws cloudformation bundle
will solely set off a deployment of the Lambda perform in case you made modifications to your code.
Final result
Migrating from CodePipeline to GitHub Actions and optimizing the deployment course of diminished the deployment time from 27 minutes to 9 minutes (down 66%). Optimizing the present CodePipeline pipeline would have yielded 20-50% efficiency enhancements. We’re delighted with the result of the migration.