Continuous Deployment

James Hetherington

18 May 2016

Systems Programming

Repetition

  • Repetition leads to boredom
  • Boredom leads to horrifying mistakes
  • Horrifying mistakes lead to God-I-wish-I-was-still-bored -- Will Larson

Doing it wrong

If you manually shell into a computer and run commands when you deploy you are doing it wrong.

Systems Programming

"Systems administration" is a programming task: we have Puppet.

This is systems programming. (Aka "dev ops".)

There is no distinction between development and operations any more: all programmers are sysadmins, and vice-versa.

Plan for the talk

In this talk, I'll explore some of the consequences of this convergence.

First, though, I'll recap some good things from modern programming practices, that we're going to borrow for systems programming.

Programming Practices

Version control

  • All changes to the code are kept, with a log message
  • Grabbing someone else's work is just a git clone away

Branching in Git

Unlike earlier version control tools, creating branches in Git is easy and cheap, and merges are clean and often completely automatic.

We create a branch for each new feature or bug-fix.

git checkout -b new_feature
# Do some Coding
git checkout master
git merge new_feature

This is integration

Pull Requests

Then we open a "Pull Request", which indicates that the feature is ready to be tested and merged.

https://github.com/UCL-RITS/RSD-Dashboard/pull/170

Automated Testing

myprog.py:

def some_clever_function(x):
  return x*2

test_myprog.py

def test_myprog():
  from myprog import some_clever_function
  assert(some_clever_function(3) == 6)
py.test

Continuous Testing Servers

It's a faff to run your tests for every platform you might want to run on, for every version of your language. So we have automated testing servers to run our tests, and email us when it goes wrong.

http://development.rc.ucl.ac.uk/jenkins/

Version control of automated test jobs

Jenkins expects the config for your automated tests to be specified in its GUI.

This is obviously suboptimal: we want to version control everything.

In RSDG, we're using a Jenkins plugin to manage our automated test configuration:

name: 'nammu'
 disabled: false
 node: OSX

 builders:
     - shell: |
         cd $WORKSPACE
         mvn clean install
 scm:
     - git:
         url: git@github.com:oracc/nammu
         branches:
             - '{mybranch}'

 triggers:
   - github
   - timed: "@midnight"

 publishers:
     - git:
         push-only-if-success: true

Travis

There's a nice cloud service that does this: we're moving our simpler non-supercomputing jobs over to Travis, instead of Jenkins.

Travis configuration just uses .travis.yml files in the repository to configure builds.

https://travis-ci.com/UCL-RITS/RSD-Dashboard/pull_requests

Continuous deployment

Automatic Deployment

If you trust your automated tests, you can do more: you can automatically deploy if the tests pass.

I'm using that for this talk:

https://github.com/UCL/rsd-talks

When the tests pass, the server is updated with the content from the master branch.

Safe automated deployment

We experimented with puppet for this. Our experience is that it's better to use puppet to set the machine's overall structure up, but then for active deployments:

  • Copy the code to the target machine with rsync (folder name based on git hash)
  • Repoint a symlink to the new folder
  • Service httpd restart

This can be better scripted with Fabric or Capistrano than bash.

Continuous integration

Simple continuous deployment from a master branch, however, breaks for multi-programmer projects.

We can use the magic of Git's branches to achieve something better: continuous integration.

Github CI

https://github.com/UCL-RITS/RSD-Dashboard/pull/170

Jenkins will automatically test each pull request. (Look at the green tick on commit 96eaac8 -- this represents a passing Jenkins build.)

When the tests pass, the branch can be safely merged to the master branch, and the deployment triggered.

(This can be manual after a code review, or automatic if tests pass.)

Local Puppet Development

Local development of puppet scripts

So, how can we develop puppet scripts locally, as if they were a computer program? Unless we can locally run it, we can't hack on it without running it on our test VM stack.

And we sure couldn't build automated tests for it.

We could, of course, use a local VM managed in a hypervisor's GUI.

Vagrant

But there's a better way, allowing us to treat virtual machines on our local laptop, just as if they were outputs of a programming exercise.

  • Run a local script to provision a VM, with all the information in a version controlled folder, alongside the code for the service.
  • (Ops-code and Dev-code in one place.)

Everything can be version controlled together, with no information locked in GUIs.

Vagrant file

Vagrant.configure("2") do |config|
  config.vm.box = "centos/7"
  config.vm.provision "puppet" do |puppet|
    puppet.manifests_path = "puppet/manifests"
    puppet.manifest_file = "server.pp"
  end
end

Vagrant usage

cd my_project
vagrant up

Consequences

This already kicks ass: we can keep our puppet manifest and vagrantfile alongside any application code. We never need to worry about a colleague setting up dependencies for a project to get started. They can just git clone and then vagrant up

Automated Testing of Servers

Automated Tests of Servers

Now we can commission local servers automatically, we can define automated tests for the machines we provision, asserting that they provide the services we expect.

  • Run the vagrantfile to trigger the same puppet manifest as we use in production
  • Only secrets and config info should differ
  • Run a test script which asserts against the provided services.

Complete Example

https://github.com/jamespjh/vagrant-puppet-example/

Further thoughts

  • Next step would be to automatically run the vagrant VM build inside Jenkins
  • Push to puppet master and prun if the tests pass
  • Vagrant multi-machine for an infrastructure of interacting services

Containers

Docker vs Vagrant

In the above example, our puppet scripts need a whole VM to work, so we use vagrant.

An alternative choice is to use container based automation, with Docker.

Here, instead of full VMs, we use a thinner layer of separation, to more quickly build and manage isolated environments.

Docker doesn't work with our puppet scripts though (e.g. it doesn't let you use systemd services in a container.)

Docker example

https://github.com/jamespjh/docker-example

Docker on Travis

https://travis-ci.org/jamespjh/docker-example/builds https://github.com/jamespjh/docker-example/blob/master/.travis.yml

Conclusions

Principles

  • Version control everything
  • Make it so you can do everything locally
  • Test automatically
  • Trust your tests
  • Tests + Deployed branches => Deployment