Infrastructure Change - Enter CircleCI

Wednesday, June 28, 2017

In my never ending quest to try all the tools, and also to get things as automated as possible, I have recently started playing with CircleCI. I have used Jenkins quite a bit, and it is really the kitchen-sink when it comes to CI with plug-ins and tools for just about everything. But just a few days ago I had a really weird break within Jenkins that was stopping me from deploying new changes. This, plus the fact that one of the engineers at my current gig was showering CicleCI with praise, made me take a second look at it.

Initial Impressions

First, this isn’t a self-hosted solution. This is both a good and a bad thing in my opinion. Generally I like to own my own data and not put it in the hands of any third party, which included hosting Git servers, Jenkins servers, and the infrastrcuture in general. Unfortunately this also meant that unless I wanted to expose it to the internet as a whole, it was very hard to set up hooks between the various pieces. So by moving to a hosted solution, this becomes a non-issue, which is nice for overall automation.

Second, this only has support for a small range of Git providers: GitHub, GitHub Enterprise, and BitBucket. Personally I like using GitLab or even Gogs as a self-hosted solution, but this is simply not possible. Luckily BitBucket allows for unlimited private repositories (with the catch being that your total account storage has to stay under 1GB). Since this is the only project I want to build this way, I see no reason to pay the $7/month for unlimited private GitHub repos, though I may change my mind at some point.

Third, CircleCI supports the use of enviroment variables that are set up in the Project UI. Now, let me explain this one a little bit. Everyone knows (or at least should know) that you should not store secrets in your code repositories. What CircleCI allows you to do is to reference them in the configuration file as environment variables, and set them in the CircleCI UI configuration. This allows for things like, logging into Docker Hub repositories for pushing images without having to put your username and password directly into your code repository.

Lastly, it’s free! Obviously this can only be a good thing, but again there is a catch: you only get one container. What this means is that you get one build or one test (at a time). To add containers to this is, in my opinion, expensive at $50 a container. For larger enterprises this seems ridiculous to me, as that cost structure just doesn’t scale when compared to adding a single Jenkins build slave (or even using spare cycles/space on a Mesos/Swarm/Kubernetes cluster). But again, for my small project, this is just perfect.

The actual code

Setting up the actual build process was pretty painless. The only catch that I could see was that because CircleCI originally used (and still uses) Docker containers for their testing, in order to build actual Docker containers for use it is required to use the 2.0 Version of the service, which is currently in beta. I’ve been using it for a bit now and it seems fairly stable. Really all that is needed to use the new 2.0 version is to create the following file structure .circleci/config.yml. Below is my current config.yml for this website:

version: 2
jobs:
  build:
    working_directory: /website
    docker:
      - image: docker:17.05.0-ce-git
    steps:
      - checkout
      - setup_remote_docker
      - restore_cache:
          keys:
            - v1-{{ .Branch }}
          paths:
            - /caches/website.tar
      - run:
          name: Load Docker image layer cache
          command: |
            set +o pipefail
            docker load -i /caches/website.tar | true
      - run:
          name: Build application Docker image
          command: |
            docker build --cache-from=website -t user/website .
      - run:
          name: Save Docker image layer cache
          command: |
            mkdir -p /caches
            docker save -o /caches/website.tar user/website
      - save_cache:
          key: v1-{{ .Branch }}-{{ epoch }}
          paths:
            - /caches/website.tar
      - deploy:
          name: Push application Docker image
          command: |
            docker login -u $DOCKER_USER -p $DOCKER_PASS
            docker push user/website
            ssh -o "StrictHostKeyChecking no" user@binaryronin.io '/home/user/deploy.sh'

There are a ton of resources to actually walk through the steps performed here, which I am not going to do. The only thing that is extra is using a tarball for the cache which can speed up the subsequent builds. Besides that the deploy step includes logging into Docker hub (with the credentials as variables from the CircleCI UI), pushing the images, and then running my script remotely on the host box using a deploy key (also set in the CircleCI UI).

site-updateinfrastructureupdate

Using CircleCI workflows to deploy to Kubernetes

libvirt and Terraform - Finally!