Kubernetes

I’m managing applications inside Kubernetes for more than 2 years for MySocialApp a social news feed solution and recently Referlab, an impressive referral marketing solution. If you follow me, you certainly know that I’ve made multiple Helm charts on distributed technologies like:

After several years of experience on it, you can trust me when I say managing statefulset on Kubernetes is not the easiest thing to do. Moreover when you’re not on a cloud Kubernetes version with facilities like distributed shared storage. It’s one step harder. If you’re not able to test the behavior of your distributed application, you can be sure it’s born to fail.

That’s why I started to look at a testing framework made for Kubernetes to test the behavhior of the running applications in some circumstances. Unfortunately, I didn’t find anything. That’s why I decided to make something simple, maintainable, locally testable. I’ve create: K8s End User Funtional Testing, aka k8s-euft. In this blog post I’ll explain how it works and why I’ve made some choices.

Setup

Hands on! Next to a chart, simply create a tests folder and add this repository as a submodule (or clone it if you don’t want to use submodules):

mkdir tests && cd tests
git submodule add https://github.com/MySocialApp/k8s-euft.git
cat k8s-euft/examples/.gitignore >> ../.gitignore

Here is a common example on what the hierarchy of your helm chart with k8s-euft looks like:

.
├── kubernetes
│   ├── Chart.yaml
│   ├── templates
│   │   ├── configmap-configs.yaml
│   │   ├── configmap-scripts.yaml
│   │   ├── _helpers.tpl
│   │   ├── NOTES.txt
│   │   ├── pdb.yaml
│   │   ├── service.yaml
│   │   └── statefulset.yaml
│   └── values.yaml
├── LICENSE
├── OWNERS
├── README.md
└── tests
    ├── configs
    │   └── default.yaml
    ├── k8s-euft
    │   ├── bootstrap_k8s.sh
    │   ├── ...
    │   └── travis-exec.go
    ├── play
    │   ├── common.bash
    │   ├── deploy.bats
    │   ├── prepare.bats
    │   ├── remove_chart.bats
    │   └── upgrade.bats
    ├── README.md
    ├── run_tests.sh
    └── Vagrantfile

The kubernetes folder contains your helm chart and the test folder is next to it.

Bats tests

As you can see above, there are some *.bats files in the play folder. Most of my tests are in bash for simplicity (but you can chose any other language/solution for it) and I’m using bats (Bash Automated Testing System) to make it even more simple.

Bats is a TAP-compliant testing framework for Bash. It provides a simple way to verify that the UNIX programs you write behave as expected.

I’ve split bats files regading the functions I want to run. This make it easily reusable.

Local and Remote tests integration

Being able to quickly test locally and remotely in an isolated and reproductible environment is mandatory. That’s why I’ve decided to use:

  • For remote testing: Travis is one of the most used, free, CI solution. Linked to GitHub, is fast to setup automated tests.
  • For local testing: Using Vagrant is standard enough to easily launch a VM running all the required tests on your local computer.

I’ve made a simple Go script for local tests which is in a fact a really basic .travis.yml parser. Its goal is to look at environment variables it has to load and tasks it has to run. This way I don’t have to write 2 differents ways to execute the tests (one for local and one for remote) but only: the Travis one.

Taking as an example a Travis configuration file:

---
language: go
sudo: required

env:
  - K8S_VERSION=1.12 HELM_VERSION=2.12.3 NUM_NODES=3 SKIP_SNAPSHOT=y PATH="$HOME/.kubeadm-dind-cluster:/tmp/linux-amd64:$PATH"
  - K8S_VERSION=1.11 HELM_VERSION=2.12.3 NUM_NODES=3 SKIP_SNAPSHOT=y PATH="$HOME/.kubeadm-dind-cluster:/tmp/linux-amd64:$PATH"

install:
  - tests/k8s-euft/helm.sh local_install || exit 1
  - bats tests/k8s-euft/helm_lint.bats || exit 1
  - tests/k8s-euft/bootstrap_k8s.sh || exit 1
  - tests/k8s-euft/helm.sh install || exit 1

script:
  - tests/run_tests.sh

Here you can define (in the env section) the version of Kubernetes you want to run, the version of helm that should be installed, the number of kubernetes nodes (workers) you want to run etc…to finally run the tests script (run_tests.sh).

Not complicated right? How Kubernetes is installed? I’m using Kubeadm Dind Cluster, a script launching a quick install of Kubernetes in Docker, you get it ready in few min. Note: If you want to install older Kubernetes version, you can pin the Kubeadm Dind Cluster repo to a Tag/Branch/Git commit with GIT_REV variable in the env section.

Vagrant

The Vagrantfile will look like this:

Vagrant.configure("2") do |config|

  config.vm.box = "deimos_fr/debian-stretch"
  #config.vm.box = "geerlingguy/centos7"

  config.vm.synced_folder "..", "/vagrant_data"

  config.vm.provider "virtualbox" do |vb|
      vb.cpus = 4
      vb.memory = "4096"
  end

  config.vm.network "private_network", type: "dhcp"

  config.vm.provision "shell", inline: <<-SHELL
    cd /vagrant_data
    source tests/k8s-euft/env.bash
    tests/k8s-euft/prerequisites.sh
    go run tests/k8s-euft/travis-exec.go .travis.yml
  SHELL
end

Really basic, not a lot of operation to execute and repeatable. As I’m using Vagrant, the most common option to run the VM is Virtualbox. But Vagrant is also able to use other providers, so you can easily run those tests in the cloud for instance :)

Run your tests

Now you’re good to go, let’s go inside the tests folder and run vagrant:

$ vagrant up
Bringing machine 'default' up with 'virtualbox' provider...
[...]
    default: [+] Running command: tests/run_tests.sh
    default:
    default: ########################################
    default: Prepare cluster
    default: ########################################
    default: 1..2
    default: ok 1 Ensure number of nodes is set: 3
    default: ok 2 Ensure nodes has correct labels
    default:
    default: ########################################
    default: Regular Couchbase deploy
    default: ########################################
    default: 1..4
[...]
    default: All tests passed :)

To conclude, it’s KISS and agile, I’m using this on several projects with success. You can find some examples here:

I hope you’ll be happy to get something as well. Feel free to contribute :)