Continuous delivery of infrastructure as code using Go.CD and Ansible

I’m fond of the CI/CD movement, mostly because I can quickly see the value in automating the build and deployment pipeline and getting a quick feedback and if all tests pass, a good feeling of reliability of the service I’m deploying. A few years ago I would’ve used Go.CD for both CI and CD pipelines and I have yet to see a project that does not benefit from this ideology in some way or form.

The history of Go.CD starts as CruisteControl, probably the first CI software that was built in this industry, long before Jenkins became popular. Born in ThoughtWorks, backed by Folwer & friends, originally named Cruise in homage to the original CI tool, but quickly renamed to “Go” to avoid the confusion.

The core idea was that if you could automate the build process of a software package, why not, through the concept of pipelines also automate the deployment of software packages in different environments. In fact, most CI tools have found a new life in being also CD machines and lately “cron” machines also, running “infrastructure pipelines” (eg. repeating or ad-hoc jobs) whenever needed. I will admit, I was and I’m still the type of guy that would use Go.CD for all the 3 value streams (CI, CD and crons) or so I did at a previous employer because it was easier.

Today, in my own home-cloud I tend to use Jenkins for the CI part, mostly because of the free AWS plugins which allow me to quickly spin-off agents whenever I need to build my software (which is rare as once per week) whereas in Go.CD as far as I remember, that was paid plugin (although free Docker/Openstack/Kubernetes plugins exist). But for the other uses of CD and “crons” I still find Go.CD a better fit than Jenkins.

So, how are we to deliver “infrastructure as code” with Go.CD and Ansible? Well, simple. First start from the concept of Pipeline As Code using the YAML plugin, which allows you to configure a Git-backed configuration repository for Go.CD to declare all your pipelines. Here’s an example of cruise-config.xml pointing to the proper pipeline repository holding YAML (or Groovy) declarative pipelines source code:

<cruise>
  <config-repos>
    <config-repo pluginId="yaml.config.plugin" id="gitlab">
      <git url="git@domain.com:/gocd-configuration/gocd-pipelines.git" />
    </config-repo>
  </config-repos>
</cruise>

Next, in that repository, define an pipeline that takes care to sync your Ansible inventory, from Git, to all of your agent instances that interest you. You could limit this selection to either an environment (a.k.a. and agent role). Ideally I would also declare a second pipeline taking care to sync the Ansible configuration also, this to also have the Ansible configuration versioned in Git and under control.

pipelines:
  Inventory:
    group: Synchronization
    label_template: "${Inventory[:8]}"
    materials:
      Inventory:
        git: git@domain.com:/ansible-configuration/inventory.git
        branch: master
    stages:
      - Sync:
          clean_workspace: true
          jobs:
            Inventory:
              run_instances: all
              tasks:
                - exec:
                    command: echo
                    arguments:
                      - "Inventory was synced!"

Now that you have your pipeline triggering at every Git commit, you’re going to have all your agents auto-magically sync the inventory to their disks in a pre-defined path, usually in /var/lib/go-agent. From there, you can either you edit the /etc/ansible/ansible.cfg file by hand or through some other mechanism or declare the ANSIBLE_CONFIG environment variable on any other pipeline or environment pointing to your synced configuration or to the default path in /etc/ansible.

[defaults]
inventory = /var/lib/go-agent/pipelines/Inventory/hosts.yml

As you can see from the above pipeline declaration, I took the liberty to put the “run_instances: all” to ensure that all agents of the assigned environments will download their inventory on each commit. Since the agents already have Ansible installed (as part of their provisioning) and the edited /etc/ansible/ansible.cfg or the ANSIBLE_CONFIG environment variable pointing to the proper path in the cloned Ansible configuration Git repository, we now have a fully functional integration between Go.CD and Ansible that gets all of its “facts” from a source-controlled Git repository.

As you probably already figured this out, the whole CD cluster is stateless. Other than “logs” which can be easily shipped with Filebeat or some other tooling to a centralized logging service, the cluster has no state. All of its stated is automatically generated from the Git source-control, the users may be configured with LDAP and other configurations can be easily edited through the Go.CD API (or through a configuration management tool from your own workstation, one-time when first deploying the cluster).

While most would comment I why I didn’t choose Jenkins and why have I limited my options, in my own interpretation, the fact that Go.CD is so less opinionated in how to do things lends itself to more freedom than the configuration and convention heavy Jenkins. Note that I say Go.CD is not opinionated except for the stages, jobs, tasks and pipeline inter-dependencies, a model that’s quite easy to model to most workloads but doesn’t necessarily impose you use it in any way.