Setting up tests in Gitlab CI for Django project with Docker Engine

Hello!

In this article i will describe how we set up Gitlab CI to run tests for Django project.

But first couple of words about what tools we were using.

What we had before Gitlab – Atlassian

For few years already we were using Atlassian stack for our development process. We had JIRA, Confluence, Stash(now it is called BitBucket Server) and Bamboo.

At first we were happy with that setup, because all apps were having good integration with each other.

We could see all commits related to currently open issue, we could see what issues and commits were included in build in Bamboo.

It looked wonderful at first, but after some time we noticed that:

  • JIRA began consuming all our time even with Agile Board
  • keeping whole this stack up-to-date was a huge pain. Last time I’ve spend whole weekend to update and repair all four apps, because they’ve changed requirements for Mysql settings, logs in Atlassian products are buried deep in various directories and that were not helping at all),
  • Every new version of atlassian apps was introducing numerous bugs and interface changes(which with obvious lack of QA was causing even more bugs)

Plus we have 16GB RAM server just for all this tools.

So instead of doing our job, we were spending time to deal with JIRA.

New developers who were joining our team were frustrated with JIRA interface.

At the end of last year we thought: “That’s enough, we need to replace this enterprise-focused monster with something easier and productive tool”.

At first, we started looking for replacement for task tracking functionality. We tried many apps and services for that, but all of them while having some strong features, also had issues or lack of functionality which prevented us to be productive or ruined any productivity at all.

Then i tried Gitlab and suddenly we not only found solution for convenient task tracking tool, but we found how to replace the whole Atlassian stack.

What is also amazing we get everything for free! (Except worker, but i have unused linux box, which now serves as worker).

Gitlab has clean and simple interface, instead of enterprise-crazy workflows of Jira, issues have labels, there is CI pipelines, Wiki, markdown for issue descriptions, comments and wiki articles, and many other great things.

We have switched to Gitlab.com

Gitlab projects solved all our problems — no more maintenance, no pain with productivity-killing interface, all in one solution.

So gitlab project provides git repository hosting, issue tracker, wiki, CI/CD management system. But what we need is CI runner. Runner does actual job, executing building, testing and deployment jobs.

As i already said, i had unused linux box, which now is used for runner.

Runner installation for ubuntu described here:
https://docs.gitlab.com/runner/install/linux-repository.html

We used docker for running our builds.

Migration from Bamboo to Gitlab CI/CD

In Bamboo build and deploy plans are setup from GUI.

For gitlab .gitlab-ci.yml must be created in root of git repository.

Before i provide example of this file, i must point out that we use Postgres as database. Thanks to docker we can require runner to run it as service with credentials from this file.

.gitlab-ci.yml

image: kpavlovsky/backoffice:latest

services:
- postgres:latest
stages:
- test
- deploy
variables:
POSTGRES_DB:
dev
POSTGRES_USER: dev
POSTGRES_PASSWORD: dev

all_tests:
stage: test
script:
- bash ./tests_gitlab.sh

Line by line:

image: kpavlovsky/backoffice:latest is docker image we use to run container.

This image is based on python:3-onbuild. We moved all long-running pip install and apt-get tasks there. By doing this we achieved two things: 1) each build runs faster, because it doesn’t involve packages installation 2) we do not abuse apt and pip repositories by downloading packages 10s or 100s time per day 3) we decrease time of each build so we get results of tests much faster (who wants to wait for 15 minutes after each git push ?).

In services docker images for services are listed. Here we have only postgres.

In variables section sets up postgres database and its credentials. Hostname for postgres will be ‘postgres’.

The ordering of elements in stages defines the ordering of builds' execution:

  1. Builds of the same stage are run in parallel.
  2. Builds of the next stage are run after the jobs from the previous stage complete successfully.

Job in ‘test’ stage goes first. Jobs in ‘deploy’ stage goes after ‘test’ stage.

all_tests is a job our of pipeline, belonging to ‘test’ stage. script hold all commands that will be issued. We have only one command here — to run tests.

#!/bin/bash
coverage run --source="app1,app2,app3" manage.py test --noinput --testrunner="xmlrunner.extra.djangotestrunner.XMLTestRunner" app1.tests app2.tests app3.tests
coverage report --skip-covered

Dockerfile for that separate kpavlovsky/backoffice:latestimage looks this way:

FROM python:3-onbuild
ENV PYTHONUNBUFFERED 1
ENV PYTHONDONTWRITEBYTECODE 0
ENV DJANGO_SETTINGS_MODULE project.settings.docker
RUN apt-get update && apt-get install -y --force-yes mc nano htop python python-pip netcat gettext && rm -rf /var/lib/apt/lists/*
RUN mkdir /code
WORKDIR /code
COPY requirements.txt /code/
RUN pip install --upgrade pip
RUN pip install -r requirements.txt
CMD ["bash"]

So now if we push code to repo and tests pass, then we see notification in slack and email that pipeline is successful or that pipeline is failed if tests fail.

Deployment

Now that we migrated the build part, we need to deploy our project in case of successful build on ‘dev’ branch to staging.

‘Stage’ environment is another linux box, without docker, just supervisor and gunicorn.

Deployment process involves ssh-ing to remote box from runner, activating virtualenv, git pulling and running django management commands.

First step is add job in ‘deploy’ stage in our .gitlab-ci.yml

deploy_stage:
stage:
deploy
script:
- bash ./deploy_stage_gitlab.sh
when: on_success
only:
- dev

This job will only be run on ‘dev’ branch and only if ‘test’ stage is successful.

To ssh to ‘stage’ machine we need to transfer ssh keys to runner.

Storing keys in repository is bad practice.

Thanks to gitlab ‘Variables’ we can transfer keys via environment variables, write them to files and then issue fabric command to execute required tasks on ‘stage’ box.

First we need to generate ssh key without passphrase. For this purpose, use ssh-keygen .

Public key must be put in ~/.ssh/authorized_keys on stage server.

Then we put contents of public and private keys to Variables.

After adding variables with keys, Variables screen in gitlab project looks similar to this:

deploy_stage_gitlab.sh looks this way:

#!/usr/bin/env bash
mkdir ~/.ssh/
echo
$STAGE_PRIVATE_KEY > ~/.ssh/id_rsa
echo
$STAGE_PUBLIC_KEY > ~/.ssh/id_rsa.pub
fab
upd_dev

Quick note: use fabric3 in python3.5 environment!

fabric function logs in to remote server, git pull everything, runs migrations and uses supervisorctl to restart process group for this project.

See also: allowing non-root user to user supervisorctl:

Conslusion

With .gitlab_ci.yml we have ability to change pipeline, so it reflects changes in our code. That was harder to achieve with Bamboo and its build/deploy settings in GUI. Of course, we could let some bash-script to do all tasks and it would be changed from commit to commit. In this case it is impossible to add/remove/change stages, etc.

Also Bamboo’s guy build/deploy setup requires much more time to set up, i didn’t find a way to clone it from project to project, which is very easy to do with .gitlab_ci.yml.

From now, we don’t need a separate large server for Atlassian tools, we don’t spend weekend to update them. And the best part: we can focus on doing real job, developing and delivering OUR apps, instead of wasting time on Atlassian’s.

Happy developing!

Click recommend button if this article is useful for you.

If you have any ideas about gitlab CI, ideas how to improve described workflow – post comments, i will be very happy to hear and discuss them!