Github & Gitlab Ci/Cd: Spring 2021
Github & Gitlab Ci/Cd: Spring 2021
Spring 2021
Contents
1 Introduction 2
1.1 GitHub Actions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.2 GitLab CI/CD . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
2 Setup 3
2.1 Project Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
2.2 Install Docker on Deployment Machine . . . . . . . . . . . . . . . . . . . . . 3
2.3 GitHub Actions Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
2.4 GitLab CI/CD Setup . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
5 Review of Each 12
5.1 GitHub . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12
5.2 GitLab . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
5.3 Closing Thoughts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1
1 Introduction
A proper CI/CD setup allows software development teams to iterate quickly by automating
some of the code review process: checking for proper code formatting, running all tests, and
ensuring it builds without errors (and, hopefully, without warnings). Once new code merges
with master, it also allows for that code to be automatically deployed to a staging / QA
environment for it to be further tested and then be deployed to production.
Previously a separate CI/CD solution such as Jenkins was often used, however more recently
both GitHub and GitLab have integrated CI/CD directly into their products. This guide
aims to give an overview of both GitHub and GitLab’s CI/CD solutions and walk through
setting up both with a sample project.
1
https://github.blog/2019-08-08-github-actions-now-supports-ci-cd/
2
https://docs.github.com/en/github/setting-up-and-managing-billing-and-payments-on-github/about-
billing-for-github-actions
3
https://www.cbc.ca/news/canada/british-columbia/coronavirus-update-bc-what-you-need-to-know-
march-21-1.5505496
4
https://about.gitlab.com/releases/2015/09/22/gitlab-8-0-released/
2
2 Setup
For this guide I will be using a simple React app. This will allow for multiple stages within
the CI/CD pipeline: a “test” stage to check the code format using Prettier, a “build” stage
to build the production version of the app and a “deploy” stage to deploy the new code.
While this is simpler than most CI/CD setups, it’ll allow us to focus on the CI/CD itself
rather than configuring a complex project first. I’ll also be using self-hosted runners so I can
deploy. The provided runners can be used for building and testing but not for deployments.
4. Within the directory just created, create a React project: npx create-react-app example-app
5. Install Prettier within the project: cd example-app && npm install prettier
7. Add both remotes, using the actual URLs given by both GitHub and GitLab:
8. That’s it for the project setup! You could further modify the React app at this point,
although this guide will just deploy the default template for simplicity.
3
2.3 GitHub Actions Setup
To install the self-hosted runner, open your repository within GitHub. Under Settings →
Actions, scroll to the bottom and click “Add runner”. We need to create two runners (one
for testing and building and the other for deployments). Since we need two of them, we’ll
use a different folder name than the suggested actions-runner.
1. Create the first in a folder named github-docker. When you reach the configure step,
name the runner docker and give it a label of docker. The default work folder can be
used. Skip running it for now.
2. Create the second in a folder named github-deploy. When you reach the configure
step, name the runner deploy and give it a label of deploy. The default work folder
can be used. Skip running it for now.
Then install both runners as a service. Within each runner directory, install the service using
sudo ./svc.sh install and then start it with sudo ./svc.sh start.
1. The first should use the docker executor, be tagged with docker and use node:14 as
the default image.
2. The second should use the shell executor and be tagged with shell
We’ll refer to these tags in our CI/CD setup later on. The GitLab Runner will also need
access to Docker - run: sudo usermod -aG docker gitlab-runner to give it access.
4
3 Configuring the Pipelines
3.1 Getting the Project Deployment-Ready
Although the React project can be run locally using npm start for development, a file server
is needed to deploy for production. The easiest way to set this up is using Docker and Caddy.
In the main project directory, create a file named Caddyfile with the following contents:
:80
file_server
root * /var/www
This configuration serves static files on port 80 from the /var/www directory. For simplicity,
we’ll just use plain HTTP. Caddy does have built-in automatic HTTPS but we’d have to
configure a domain for that.
We’ll also need a docker-compose.yml file in the main project directory. It should look like
this:
version: "3.7"
services:
caddy:
image: caddy:2.3.0
restart: always
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- ./example-app/build:/var/www
ports:
- ${PORT}:80
Note how we map the built version of the app to the /var/www directory that Caddy is
serving files from within Docker. We also map port 80 inside the container to the port
contained in the PORT environment variable - this is mostly done so that the GitHub and
GitLab deployments can deploy on separate ports rather than interfering with each other.
In a normal scenario with only one CI/CD setup, one could just map port 80:80 directly. I
cannot imagine a sane setup in which it would make sense to actually use both GitHub and
GitLab to manage deployments for the same project - both are used here for comparison
purposes only.
5
# Specify a name for this workflow
name: CICD
# Run this workflow on pushes to master and pull requests into master
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
test-react:
# Run this job only on runners with both of these labels
runs-on: [self-hosted, docker]
# Run using the `node:14` image in Docker
container: node:14
steps:
# First, checkout the repo
- uses: actions/checkout@v2
# Second, run Prettier
# Also name the step (will be shown in GitHub's UI; optional)
- name: Run Prettier
run: |
cd example-app
npm install
npx prettier --check .
build-react:
needs: test-react
runs-on: [self-hosted, docker]
container: node:14
# Specify environment variables for this job only
env:
GENERATE_SOURCEMAP: false
steps:
- uses: actions/checkout@v2
- name: Build React App
run: |
cd example-app
npm install
npm run build
- name: Generate build artifact
run: tar -czvf example-app.tar.gz example-app/build
# Upload the artifact so it can be used by future jobs
6
- name: Upload build artifact
uses: actions/upload-artifact@v2
with:
name: react-build
path: example-app.tar.gz
deploy:
# Only deploy if the code was commited to master, not for PRs
if: github.ref == 'refs/heads/master'
needs: build-react
# Note no container is specified, this job will run in the shell
runs-on: [self-hosted, deploy]
env:
PORT: 8081
steps:
- uses: actions/checkout@v2
# Grab the build artifact from the previous job
- name: Download build artifact
uses: actions/download-artifact@v2
with:
name: react-build
# Recreate the Docker container(s) running our deployment
- name: Redeploy
run: |
tar -xvf example-app.tar.gz
docker-compose -p github up --force-recreate -d
7
test react:
# Part of the test stage. Multiple jobs can be part of one stage.
stage: test
# Run within Docker using the `node:14` image
image: node:14
# This job will run on any runner with the tag "docker"
tags:
- docker
# Only run if the branch has an associated merge request
# or if the commit was made to the deployment branch
rules:
- if: '$CI_MERGE_REQUEST_ID != null || $CI_COMMIT_BRANCH == $DEPLOY_BRANCH'
when: always
# these commands are run as part of the set up for the job
before_script:
- cd example-app
- npm install
# the commands that are a part of the job itself
script:
- npx prettier --check .
build react:
stage: build
image: node:14
tags:
- docker
rules:
- if: '$CI_MERGE_REQUEST_ID != null || $CI_COMMIT_BRANCH == $DEPLOY_BRANCH'
when: always
# Specify environment variables for this job only
variables:
GENERATE_SOURCEMAP: "false"
before_script:
- cd example-app
- npm install
script:
- npm run build
# commands to run once the job has finished
after_script:
- tar -czvf example-app.tar.gz example-app/build
# these artifacts can be accessed in later stages of the pipeline
artifacts:
paths:
- example-app.tar.gz
8
deploy:
stage: deploy
# Run in the shell, not in Docker
tags:
- shell
rules:
# Only deploy if this is a commit to the deployment branch
- if: '$CI_COMMIT_BRANCH == $DEPLOY_BRANCH'
# Only run job if previous jobs were successful
when: on_success
variables:
PORT: 8082
# Note that the "build" stage artifact is automatically available
script:
- tar -xvf example-app.tar.gz
- docker-compose -p gitlab up --force-recreate -d
9
4 Running the Pipelines
This is the easy part - your pipelines have already run! If GitHub or GitLab detect a con-
figuration file, they will read and run it automatically. In a few minutes you should expect
an email saying that your pipelines have failed (assuming your notifications are on). They
failed because our test step runs Prettier to enforce code formatting but we never actually
formatted our code.
To simulate how the pipelines work with a proper Git workflow, we’ll create a separate
branch to change a bit of the app’s text and fix the code formatting in the process:
After creating the PR & MR, both GitHub and GitLab should automatically run the appli-
cable jobs. Once they complete running, the first job will fail:
GitHub GitLab
GitHub and GitLab can both be configured to disallow merging if some of the jobs fail (not
shown above). In order to fix these failures, in the example-app folder run npx prettier -w .
and then commit and push again. The PR & MR will automatically re-run the jobs and
they should succeed this time. Once merged, the jobs will automatically run again and this
time actually deploy our app!
10
The ports used to access the deployments correspond to the PORT environment variable set
in the configuration files above:
11
5 Review of Each
5.1 GitHub
GitHub Actions is extremely flexible and provides lots of options, many of which were not
covered here. GitHub also provides more free hosted minutes than GitLab, which is much
more convenient than setting up a self-hosted runner (and, also, free). Their self-hosted run-
ner works well, but is quite cumbersome to set up when compared with GitLab’s runner. In
particular, it is non-obvious how to set up multiple runners and the runners are not installed
as a service by default - they must be either manually turned on or manually installed as a
service. Generally I believe most developers would expect them to be running all the time
(otherwise they cannot receive and run jobs). That being said, once it is set up it does seem
to “just work”.
An additional issue I noticed was I was not consistently able to see job output while the job
was running. It worked some of the time (albeit rarely), although I was consistently able to
see it after the job had finished. I have not experienced this issue on GitLab. This is what
each platform shows while the jobs are running:
Output of “Build React App” not shown Full output shown (GitLab)
(GitHub)
12
5.2 GitLab
GitLab has a solid CI/CD offering that is also incredibly configurable. Their self-hosted
runner is very easy to set up and works quite well. Additionally, GitLab provides a much
nicer interface to see all previous pipeline runs and allows rerunning specific jobs (whereas
GitHub requires the entire workflow to be run again):
One drawback of GitLab’s approach is all jobs are contained in one file, therefore with many
jobs it may become difficult to maintain (whereas GitHub allows multiple “workflow” files).
I also noticed that when using gitlab.com, CI/CD jobs take a while to start, sometimes
up to a minute (despite the fact that they are running on a dedicated self-hosted runner). I
have not experienced this issue with self-hosted instances of GitLab nor with GitHub.
Nevertheless, both GitHub and GitLab have great CI/CD functionality built in and neither
is objectively “better” - try both and choose the one you prefer.
13