Using CI/CD on Gitlab CE

Post Image

Continuous Integration is a software development practice in which you build and test software every time a developer pushes code to the application, and it happens several times a day.

Continuous Delivery is a software engineering approach in which continuous integration, automated testing, and automated deployment capabilities allow software to be developed and deployed rapidly, reliably and repeatedly with minimal human intervention. Still, the deployment to production is defined strategically and triggered manually.

Continuous Deployment is a software development practice in which every code change goes through the entire pipeline and is put into production automatically, resulting in many production deployments every day. It does everything that Continuous Delivery does, but the process is fully automated, there’s no human intervention at all.

The benefits of Continuous Integration are huge when automation plays an integral part of your workflow. GitLab comes with built-in Continuous Integration, Continuous Deployment, and Continuous Delivery support to build, test, and deploy your application.

Installing Gitlab

To install Gitlab, follow the instructions on Installation methods for Gitlab. For Ubuntu, its as simple as adding the repository and installing using apt. You can give an EXTERNAL_URL at the time of installation as the URL at which you want to access your GitLab instance. Installation will automatically configure and start GitLab at that URL.

GitLab is configured by setting the relevant options in /etc/gitlab/gitlab.rb. You can use change the external URL by adding or editing the following line in the same file:

external_url "http://gitlab.example.com"

Run sudo gitlab-ctl reconfigure for the change to take effect.

GitLab offers a continuous integration service. If you add a .gitlab-ci.yml file to your repository, and configure your GitLab project to use a Runner, then each commit or push, triggers your CI pipeline.

- Gitlab Docs

Installing and Configuring Runners

In GitLab, Runners run the jobs that you define in .gitlab-ci.yml. A Runner can be a virtual machine, a VPS, a bare-metal machine, a docker container or even a cluster of containers. GitLab and the Runners communicate through an API, so the only requirement is that the Runner’s machine has network access to the GitLab server. Runner can be specific to a certain project or serve multiple projects in GitLab.

GitLab Runner implements a number of executors that can be used to run your builds in different scenarios.

  • shell - executes build script on host
  • docker - executes build inside docker container(s)
  • virtualbox - executes build in a virtualbox vm
  • parallels - executes build in a parallels vm
  • ssh - runs build on a different host via ssh
  • docker-machine - autoscales docker machine instances and runs builds in containers across these instances

It is recommended that you use docker as the executor as it the cleanest, most common and most feature-rich.

In order to have a functional Runner you need to follow two steps:

Once the Runner has been set up, you should see it on the Runners page of your project, following Settings ➔ CI/CD

What is .gitlab-ci.yml

The .gitlab-ci.yml file is where you configure what CI does with your project. It lives in the root of your repository.

On any push to your repository, GitLab will look for the .gitlab-ci.yml file and start jobs on Runners according to the contents of the file, for that commit.

stages:
  - build
  - test
  - deploy

build_app:
  stage: build
  script: 
    - ./make build

test_app:
  stage: test
  script:
    - ./make test

deploy_app:
  stage: deploy
  script: 
    - scp ./app deployuser@someserver:/app

The .gitlab-ci.yml file defines sets of jobs with constraints of how and when they should be run. The jobs are defined as top-level elements with a name and always have to contain the script keyword. Jobs are used to create jobs, which are then picked by Runners and executed within the environment of the Runner.

In the example we define three build stages: build, test and deploy. The order is significant. We also define three jobs, build_app, test_app and deploy_app, which are executed in the order given by the stages to which they belong. If one build stage fails, the others will not be executed.

For full documentation of .gitlab-ci.yml, visit Configuration of your jobs with .gitlab-ci.yml

Pipeline used in the IRIS Blog

At IRIS, we use an internally hosted Gitlab instance for Version Control and Deployment. The CI/CD pipeline for the IRIS Blog runs on a locally hosted Gitlab Runner using the Docker executor.

image: ruby

stages:
  - build
  - deploy

We use the ruby docker image to run the jobs. The pipeline has two stages - build and deploy. All the jobs in the same stage are executed parallelly. Jobs of the next stage (in the order given by stages) are executed only if all the jobs of the previous stage completed running successfully.

pages:
  stage: build
  script:
    - gem install jekyll
    - bundle install
    - jekyll build -d public
  artifacts:
    paths:
      - public

Build stage has one job pages. We first install the dependencies and then build the app using jekyll build in the public folder. If there’s any error during the build, it’ll show up then and there and the pipeline will come to a halt. So this can also be considered as the testing stage. We have specified the public directory as an artifact which is attached to the job after success.

deploy:
  stage: deploy
  script:
    - curl "http://$VM_IP:5000/deploy/iris_blog/$AUTH_TOKEN"
  only:
    - master

The deploy stage has one job deploy. When this job is run, a curl request is made at the URL http://$VM_IP:5000/deploy/iris_blog/$AUTH_TOKEN. $VP_IP and $AUTH_TOKEN are Gitlab’s secret variables which can be configured in the Variables section in Settings ➔ CI/CD. This request is made to a server running on the production machine which will deploy the app after authorizing the request. The only parameter specifies that this job will run only when the triggering branch is “master”.

Deployment Server

The deployment server is a simple Flask app that is used to run the deployment script to deploy the app. We make a curl request to this server in the deploy job.

An alternate to this server is using ssh directly from .gitlab-ci.yml. But this has security risks of exposing the ssh credentials in the script or in the pipeline logs. The production system could be compromised and rendered useless. The server runs a pre-defined script, therefore even if an intruder manages to get authorized, they won’t get system access.

from flask import Flask, abort
import subprocess
app = Flask(__name__)

@app.route('/deploy/iris_blog/<string:auth_token>')
def deploy_iris_blog(auth_token):
    if not authenticate(auth_token):
        return abort(401, "Authentication Failed")
    ex = subprocess.call("cd ~/test/IRIS-Blog; git pull", shell=True)
    if ex:
        return abort(500, "Git Error")
    ex = subprocess.call("bash ~/test/IRIS-Blog/scripts/deploy.sh", shell=True)
    if ex:
        return abort(500, "Deploy Script Error")
    return "Successfull"

The request is first authenticated using the auth_token sent along with the request. Then the code base is updated using git pull to update the deploy script which is present in the git repository itself. After this is successful, the deploy script is run that will safely deploy the app. It returns an error if any of the steps fail.