Building Dynamic CI Pipeline with BuildKite

I was inspired by this BuildKite pipeline sample given by the support team:

# .buildkite/pipeline.yml
steps:
  - command: echo building a thing
  - block: Test the thing?
  - command: echo testing a thing
  - wait
  - command buildkite-agent pipeline upload .buildkite/pipeline.deploy.yml

# .buildkite/pipeline.deploy.yml
steps:
  - block: Deploy the thing?
  - command: echo deploy the thing

So in the above case, if the first 2 commands succeed, pipeline.deploy.yml will be loaded into the main CI pipeline. This implementation is just brilliant. I’m not sure if jenkinsfile can do dynamic pipeline like this, but at least jenkinsfile won’t look as elegant as yaml.

Since buildkite-agent pipeline upload .buildkite/pipeline.deploy.yml is just another bash command, I can even use it in a script to put more logic in it, such as git flow implementation like:

#!/bin/bash
export CHOICE=$(buildkite-agent meta-data get "next-section")

case $CHOICE in
deploy)
  buildkite-agent pipeline upload .buildkite/pipeline.qa.yml
  ;;
signoff)
  # feature finish
  if [[ $BUILDKITE_BRANCH == feature* ]]; then
    python .buildkite/scripts/github_ci.py \
      --action pr \
      --repo flow-work \
      --head $BUILDKITE_BRANCH \
      --base develop

  # release start
  elif [[ $BUILDKITE_BRANCH == develop ]]; then
    git checkout -b release/$FULL_VERSION
    git push --set-upstream origin release/$BUILDKITE_BUILD_NUMBER

  # release finish
  elif [[ $BUILDKITE_BRANCH == release* ]]; then
    buildkite-agent pipeline upload .buildkite/pipeline.pass.yml
  fi
  ;;
reject)
  #mark build as failure
  exit -1
  ;;

FYI. example tested with BuildKite agent version 3.2.0.

🙂

Notes: BuildKite and Kubernetes Rolling Update

This is kind of a textbook case that container is much more efficient than VM. The CI pipeline in comparison uses AWS CloudFormation to build new VMs and drain old VMs to do a rolling update, which takes around 10 minutes for everything even if it’s just 1 line of code changed. I did a new pipeline with BuildKite and Kubernetes and a deploy is done within 2 minutes.

The key points to make the pipeline fast are:

  1. In the Dockerfile, the part that changes more frequently should be put at the bottom of the file, so that docker can maximise its build speed by using cached intermediate images.
  2. Reload Kubernetes config maps with this:
    kubectl create configmap nginx-config --from-file path/to/nginx.conf -o yaml --dry-run |kubectl replace -f -
  3. Reload containers with this(I use ECR):
    $BUILDKITE_BUILD_NUMBER obviously is the build number environment variable provided by BuildKite.

    kubectl set image deployment/my_deployment \
     nginx=my.ecr.amazonaws.com/nginx:$BUILDKITE_BUILD_NUMBER \
     php=my.ecr.amazonaws.com/php:$BUILDKITE_BUILD_NUMBER
  4. Finally watch rolling update progress with this command:
    kubectl rollout status deployment/my_deployment

🙂

Why I like BuildKite

BuildKite is a relative new CI toolkit I would like to replace Jenkins with. Here are some pros and cons I thought I could share:

Pros:

  • Designed with containers(docker) in mind.
  • Hybrid architecture, console as a hosted service where agents can run anywhere with internet connectivity
  • Build pipeline as code, also very easy to write because it’s just yaml
  • Parallelism in pipeline
  • It’s made in Melbourne
  • Artifacts can be stored in own S3 bucket
  • Well organised UI with a neat github like style

Cons:

  • If there are lots of agents running, some orchestration is needed
  • Some features are in beta quality still

🙂