Continuous Integration

Configuring a CI pipeline

To enable the Gitlab automation, it is needed to insert a configuration file that must be placed in the root of the repository and called “.gitlab-ci.yml”. It mainly contains definitions of how your project should be built. An example of it can be found within the project “ska-python-skeleton” available here. Once the file is in the root directory, it is possible to run the CI pipeline manually (creating a pipeline) or with a commit in gitlab as soon as the mirroring finishes. The following pipeline was created manually pressing the button “Run pipeline” on a specific branch (i.e. master).

image5

Using a specific executor

The pipeline by default will run with a shared runner made available from GitLab. It is also possible to assign specific SKA runners to the project (by adding the tags). To do that the option must be enabled:

image6

The EngageSKA cluster located at the Datacenter of Institute of Telecommunication (IT) in Aveiro provides some virtual machines available adding the tag “engageska” or “docker-executor” as shown here.

Note

In order to have the SKA runners available, a project must be under the SKA Telescope group. Currently projects can only be added to the SKA group by the System Team - refer to our guide to Create a new project.

The typically used SKA CI Pipeline stages are being documented as they are developed and improved. The currently available documentation on this is discussed under CI pipeline stage descriptions. The following section deals with the required CI metrics that need to be included in the pipeline.

Automated Collection of CI health metrics as part of the CI pipeline

As part of the CI/CD process all teams are expected to collect and consolidate the required code health metrics. Namely unit tests, linting (static code analysis) and coverage.

Part of the CI/CD functionality is to add a quick glance of those metrics to each repository in the form of badges. These badges will always show the status of the default branch in each repository.

Teams have the option to use the automatic parsing of their CI and code health metrics and have the badges created automatically as long as the output from their code health reports follows the requirements described bellow. As an alternative the teams can instead create the ci-metrics.json file themselves according to what is described in Manual Metrics.

These metrics reports must pass the following requirements:

  1. These files must not be part of the repository, but be created under their respective steps (test, linting) in the CI pipeline.
  2. Unit Tests report must be a JUnit XML file residing under ./build/reports/unit-tests.xml
  3. Linting report must be a JUnit XML file residing under ./build/reports/linting.xml
  4. Coverage report must be a XML file in the standard used by Coverage.py residing under ./build/reports/code-coverage.xml
  5. The XML format expected for the coverage is the standard XML output from Coverage.py for Python or from a similar tool like Cobertura for Javascript with the line-rate attribute specifying the coverage. See the example code bellow.
<?xml version="1.0" encoding="UTF-8"?>
<coverage branch-rate="0" branches-covered="0" branches-valid="0" complexity="0" line-rate="0.6861" lines-covered="765" lines-valid="1115" timestamp="1574079100055" version="4.5.4">

In order to automate the process as much as possible for the teams, the ci-metrics-utilities repository was created and it will automate the all metrics collection, and badge creation as long as the 5 points above are observed.

In order to use this automation, the following code must be added at the end of .gitlab-ci.yml

create ci metrics:
  stage: .post
  image: nexus.engageska-portugal.pt/ska-docker/ska-python-buildenv:latest
  when: always
  tags:
    - docker-executor
  script:
    # Gitlab CI badges creation: START
    - apt-get -y update
    - apt-get install -y curl --no-install-recommends
    - curl -s https://gitlab.com/ska-telescope/ci-metrics-utilities/raw/master/scripts/ci-badges-func.sh | sh
    # Gitlab CI badges creation: END
  artifacts:
    paths:
      - ./build

Manual Collection of CI health metrics as part of the CI pipeline

The teams that prefer to create their own ci-metrics.json file instead of using the provided automation, can do so. They are still expected to observe all the points described in Automated Metrics.

The ci-metrics.json file is expect to be created automatically as part of the CI pipeline by the teams by collecting the relevant information from the unit tests, coverage, linting and build status. An important point to notice, is that ci-metrics.json shouldn’t exist as part of the repository, but, be created specifically as part of the CI pipeline. The file must be created and properly populated before the start of the marked stage: .post step in the CI pipeline (.gitlab-ci.yml file).

The metrics should be collected under the following structure:

  • commit-sha (string): sha tag for the git commit
  • build-status: top level placeholder for the build process status
    • last: placeholder about the last build process
      • timestamp (float): the Unix timestamp with the date and time of the last build status
  • coverage: placeholder about the unit test coverage
    • percentage (float): the coverage percentage of the unit tests
  • tests: placeholder about the unit tests
    • errors (int): number of test errors
    • failures (int): number of test failures - this denotes a serious error in the code that broke the testing process
    • total (int): total number of tests
  • lint: placeholder about the linting (static code analysis)
    • errors (int): number of linting errors
    • failures (int): number of linting failures - this denotes a serious error in the code that broke the linting process
    • total (int): total number of linting tests

ci-metrics.json example:

{
  "commit-sha": "cd07bea4bc8226b186dd02831424264ab0e4f822",
  "build-status": {
      "last": {
          "timestamp": 1568202193.0
      }
  },
  "coverage": {
      "percentage": 60.00
      },
  "tests": {
      "errors": 0,
      "failures": 3,
      "total": 170
  },
  "lint": {
      "errors": 4,
      "failures": 0,
      "total": 7
  }
}

CI pipeline stage descriptions

Caution

This section is a work in progress

The CI/CD pipeline will ensure that software projects are packaged, tested and released in a consistent and predictable manner. SKA Pipelines are viewable and executable at https://gitlab.com/ska-telescope

General Notes

  • Every commit could potentially trigger a pipeline build. There may be different rules applied to determine which stages are executed in the pipeline based on factors like the branch name.

    • E.g Every commit in a feature branch may trigger the “Lint” stage, but not a slow test suite.
  • When doing a release with a git tag, the full pipeline will be run.

  • Every pipeline job is associated with its git commit (including tag commits).

  • Try and have the stages complete as fast as possible.

    • In some cases it may be possible to parallelize jobs. For example, unit tests and static analysis could be run in parallel.
  • All projects must include all the stages listed below.

  • Project dependencies must be stored in, and made available from the SKA software repository.

  • All tests must pass on the “master” branch and should be kept stable.

Stages

Build

The build stage packages/compiles the software project into distributable units of software. The project will be checked out at the git commit hash. This specific version of the code must then be built. Failing the build stage will stop the further steps from being executed. Where possible Semantic Versioning should be used. To create a release a git tag should be used. See Release Procedure.

Input
Git commit hash
Output
A distributable unit of software. E.g .deb .whl .jar or docker image. These must be stored as part of the artifacts and will then be available to subsequent jobs. One could also store metadata together with the artefact, such as a hash of the binary artefact. This should be provided by our artefact registry.

Linting

The static analysis stage does static code analysis on the source code such as Linting.

Input
None
Output
Quality analysis results in JUnit format.

Test

The test stage must install/make use of the packages created during the build stage and execute tests on the installed software. Tests should be grouped into Fast / Medium / Slow / Very Slow categories.

Input
The output from the Build stage. E.g .deb or .whl or docker image. Input could also consist of test data or environment.
Output
  • The results of the tests in JUnit format. These need to be added to the artifacts. See Gitlab Test Reports.
  • Coverage metrics in JUnit format.
Test types

Todo

  • Further define components to be mocked or not
  • Further define smoke/deployments tests
Unit tests
The smallest possible units/components are tested in very fast tests. Each test should complete in milliseconds.
Component tests
Individual components are tested.
Integration/Interface tests
Components are no longer being mocked, but the interactions between them are tested. If a component is a docker image, the image itself should be verified along with its expected functionality.
Deployment tests
Tests that software can be deployed as expected and once deployed, that it behaves as expected.
Configuration tests
Multiple combinations of software and hardware are tested.
System tests
The complete solution, integrated hardware and software is tested. There tests ensure that the system requirements are met.

Publish

Once the build and test stages have completed successfully the output from the build stage is uploaded to the SKA software repository. This stage may only be applicable on git tag commits for full releases in certain projects.

Input
The output from the Build stage. .deb or .whl for example. This could also include docker images.
Output
The packages are uploaded to the SKA software repository.

Pages

This is a gitlab stage publishes the results from the stages to Gitlab

Input
The JUnit files generated in each pipeline stage.
Output
The generated HTML containing the pipeline test results.

Documentation

Currently the documentation is generated by the “readthedocs” online service. The list of SKA projects available here. The project documentation will be updated and accessible at the following URL https://developer.skatelescope.org/projects/<PROJECT> E.g lmc-base-classes

Input
A docs folder containing the project documentation.
Output
The generated HTML containing the latest documentation.

Using environment variables in the CI pipeline to upload to Nexus

There are several environment variables available in the CI pipeline that should be used when uploading Python packages and Docker images to Nexus. This will make these packages available to the rest of the SKA project. This section describes some of these variables. A full list is also available.

Python Modules

The Nexus PYPI destination as well as a username and password is available. For a reference implementation see the lmc-base-classes .gitlab-ci.yaml

Note the following:
  • The Nexus PYPI_REPOSITORY_URL is where the packages will be uploaded to.
  • twine uses the local environment variables (TWINE_USERNAME, TWINE_PASSWORD) to authenticate the upload, therefore they are defined in the variables section.
publish to nexus:
  stage: publish
  tags:
    - docker-executor
  variables:
    TWINE_USERNAME: $TWINE_USERNAME
    TWINE_PASSWORD: $TWINE_PASSWORD
  script:
    # check metadata requirements
    - scripts/validate-metadata.sh
    - pip install twine
    - twine upload --repository-url $PYPI_REPOSITORY_URL dist/*
  only:
    variables:
      - $CI_COMMIT_MESSAGE =~ /^.+$/ # Confirm tag message exists
      - $CI_COMMIT_TAG =~ /^((([0-9]+)\.([0-9]+)\.([0-9]+)(?:-([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?)$/ # Confirm semantic versioning of tag

Docker images

The Nexus Docker registery host and user is available. For a reference implementation see the SKA docker gitlab-ci.yml

Note the following:
  • The DOCKER_REGISTRY_USER corresponds to the folder where the images are uploaded, hence the $DOCKER_REGISTRY_FOLDER is used.
script:
- cd docker/tango/tango-cpp
- make DOCKER_BUILD_ARGS="--no-cache" DOCKER_REGISTRY_USER=$DOCKER_REGISTRY_FOLDER DOCKER_REGISTRY_HOST=$DOCKER_REGISTRY_HOST build
- make DOCKER_REGISTRY_USER=$DOCKER_REGISTRY_FOLDER DOCKER_REGISTRY_HOST=$DOCKER_REGISTRY_HOST push

Kubernetes based Runners Architecture

GitLab runners are orchestrated by Kubernetes cluster. They could be deployed to any Kubernetes clusters with following the instructions on deploy-gitlab-runners repository. The main architecture is illustrated below.

runners_on_kubernetes

Features

  • The main runner pod is deployed with Helm Chart under gitlab namespace with the repository.
  • Main runner pod is registered to ska-telescope group shared runners with configurable tags.
  • The main pod picks up GitLab Jobs and creates on-demand pods. This is configured using helm chart values file/or config.toml file of GitLab runners below.
  • Runners are scaled according to configuration.
  • Runners have resource limits i.e. cpuRequests, memoryRequests, cpuLimit, memoryLimit. This is not applied at the moment.
  • Runners are running in nodes that are specifically labelled for ci/cd jobs.
  • Runners share a cache between them that is used to speed up the job times.
  • Docker support
  • Kubernetes support

With this approach, GitLab Runners are proven to be a viable option to be used in a cluster with auto-scaling and easy management. Docker Support

Docker can be used in the CI/CD jobs as with the normal runners. Note that: docker-compose cannot be used in conjunction with Kubernetes! You should follow the instruction on the developer portal to set up your repo.

To elevate some of the security concerns listed below with using Docker in Docker, another docker daemon is deployed in the nodes. This daemon then used as default docker-daemon in the runner pods. Kubernetes Support

Kubernetes clusters could be created in ci/cd jobs. These clusters are created on the ci-worker nodes and destroyed at the end of the job.

Note: in order to run deploy clusters, the account permissions need to be set up correctly for the runner services.

Migrating to new Runner Infrastructure

Compose is a commonly used tool for defining and running multi-container Docker applications. It works in all environments including CI workflows and requires a three-step process:

  1. Prepare your Dockerfile.
  2. Define the services that make up your app in docker-compose.yml.
  3. Run docker-compose up.

The SKA is currently promoting migration to Kubernetes as the container orchestrator, and this requires for applications developed with docker-compose to be converted into the new runner infrastructure. In principle there is no need to make any changes if one is not using docker-compose.

The conversion tool allowing the migration is Kompose. A detailed guide for changing from docker-compose to kubernetes can be found at https://kubernetes.io/docs/tasks/configure-pod-container/translate-compose-kubernetes/

The conversion process is relatively straightforward, requiring two steps:

  1. Run kompose convert in the same directory of docker-compose.yml file.
  2. Prepare a make target with kubectl apply -f <output_file>.

This converts the docker-compose.yml file to files that you can use with kubectl. To make sure that the transition will work in the runner cluster you can test locally with Minikube. If it works on your local Minikube then it will work in the kubernetes runner cluster.