Gitlab + Runner with SSH and Docker

By Paulus, 12 February, 2018

If you’re going to go with a cloud provider, I would highly recommend using Digital Ocean because it’s the best bang for your buck overall. With Digital Ocean, a droplet that meets the recommended resource requirements costs $20/mo. That price also includes bandwidth unlike Google Cloud Platform and Amazon’s Web Services. The cost for GCP, AWS, and Azure are about 2-3 times the cost.

Installing Gitlab

Installing Gitlab is straight forward and can be found here.

Enable HTTPS with LetsEncrypt

Go through the installation steps described in the link above, setting the EXTERNAL_URL to a non-https URI.
[text title="Create directory for ACME Challenge"]mkdir -p /var/www/letsencrypt[/text]
Add the following line to /etc/gitlab/gitlab.rb:
[ruby gutter="1" title="/etc/gitlab/gitlab.rb"]nginx['custom_gitlab_server_config'] = "location ^~ /.well-known { root /var/www/letsencrypt; }"[/ruby]
Request a certificate for the domain, replacing gitlab.example.com with yours.
[text title="Request certificate"]certbot certonly --webroot --webroot-path=/var/www/letsencrypt -d gitlab.example.com[/text]
After successfully obtaining the SSL certificate, edit following lines in the /etc/gitlab/gitlab.rb file:
[ruby title="/etc/gitlab/gitlab.rb"]external_url 'https://gitlab.example.com'
nginx['redirect_http_to_https'] = true
nginx['ssl_certificate'] = "/etc/letsencrypt/live/gitlab.example.com/fullchain.pem"
nginx['ssl_certificate_key'] = "/etc/letsencrypt/live/gitlab.example.com/privkey.pem"[/ruby]

Finally, run gitlab-ctl reconfigure. For more information about configuring nginx, click here.

Installing GitLab Runner

Again, the process is pretty straight forward and the documentation can be found here.

Configuring GitLab and Gitlab Runner

After installing Gitlab and Gitlab Runner, you need to tell Gitlab about the runners you have by registering them.
[text title="$ sudo gitlab-runner register"]
Please enter the gitlab-ci coordinator URL (e.g. https://gitlab.com )
https://gitlab.example.com

Please enter the gitlab-ci token for this runner
xxxxxxx

Please enter the gitlab-ci description for this runner
[hostame] runner1.example.com

Please enter the gitlab-ci tags for this runner (comma separated):
php,laravel,composer,npm,webpack

Whether to lock Runner to current project [true/false]:
[true]: false

Please enter the executor: ssh, docker+machine, docker-ssh+machine, kubernetes, docker, parallels, virtualbox, docker-ssh, shell:
docker[/text]
Tags are not necessary but can be helpful when there are projects that use different tools, libraries, frameworks, etc.

Locking a runner to current project assigns it exclusively to a project where that project is very large and requires a lot of resources. For general web development purposes, it’s not necessary.

GitLab Configuration

After registering the runner, it’s time to add users or configure authentication, create labels, add Deploy Keys, etc.

User Authentication

If you have an LDAP or Active Directory server you would like to use, you can configure GitLab to allow users to sign in with those credentials instead. The settings for using a directory service are set in the /etc/gitlab/gitlab.rb file.
[ruby title="/etc/gitlab/gitlab.rb"]# These settings are documented in more detail at
# https://gitlab.com/gitlab-org/gitlab-ce/blob/a0a826ebdcb783c660dd40d8cb…
# Be careful not to break the identation in the ldap_servers block. It is in
# yaml format and the spaces must be retained. Using tabs will not work.

gitlab_rails['ldap_enabled'] = true
gitlab_rails['ldap_servers'] = YAML.load <<-EOS # remember to close this block with 'EOS' below
main: # 'main' is the GitLab 'provider ID' of this LDAP server
## label
#
# A human-friendly name for your LDAP server. It is OK to change the label later,
# for instance if you find out it is too large to fit on the web page.
#
# Example: 'Paris' or 'Acme, Ltd.'
label: 'LDAP'

host: '_your_ldap_server'
port: 389 # or 636
uid: 'sAMAccountName'
encryption: 'plain' # "start_tls" or "simple_tls" or "plain"
bind_dn: '_the_full_dn_of_the_user_you_will_bind_with'
password: '_the_password_of_the_bind_user'

# This setting specifies if LDAP server is Active Directory LDAP server.
# For non AD servers it skips the AD specific queries.
# If your LDAP server is not AD, set this to false.
active_directory: true

# If allow_username_or_email_login is enabled, GitLab will ignore everything
# after the first '@' in the LDAP username submitted by the user on login.
#
# Example:
# - the user enters 'jane.doe@example.com' and 'p@ssw0rd' as LDAP credentials;
# - GitLab queries the LDAP server with 'jane.doe' and 'p@ssw0rd'.
#
# If you are using "uid: 'userPrincipalName'" on ActiveDirectory you need to
# disable this setting, because the userPrincipalName contains an '@'.
allow_username_or_email_login: false

# Base where we can search for users
#
# Ex. ou=People,dc=gitlab,dc=example
#
base: ''

# Filter LDAP users
#
# Format: RFC 4515 http://tools.ietf.org/search/rfc4515
# Ex. (employeeType=developer)
#
# Note: GitLab does not support omniauth-ldap's custom filter syntax.
#
user_filter: ''
EOS[/ruby]

Container Registry

With a container registry, applications can have their own space to store docker images. Omnibus installations simply need to specify where the registry will reside.
[text title="Shell snippet"]export REGISTRY_EXTERNAL_URL=https://gitlab.example.com:5050
sed -i "s@.*\(registry_external_url\s'\).*\('\)@\1"$REGISTRY_EXTERNAL_URL"\2@" /etc/gitlab/gitlab.rb[/text]

  1. The registry listens on port 5000, so we need to use a different one.
  2. sed allows us to use different delimiters, since the address contains forward slashes, we use `@` rather than `/`.

By default, images are stored locally but can be changed by changing the registry['storage'] variable.
[ruby title="/etc/gitlab/gitlab.rb"]
### Registry backend storage
###! Docs: https://docs.gitlab.com/ce/administration/container_registry.html#conta…
# registry['storage'] = {
# 's3' => {
# 'accesskey' => 'AKIAKIAKI',
# 'secretkey' => 'secret123',
# 'bucket' => 'gitlab-registry-bucket-AKIAKIAKI'
# }
# }[/ruby]

Runners

On the https://gitlab.example.com/admin/runners page, you can make changes to when a runner will execute the .gitlab-ci.yml file. Other changes that can be made are

  • Whether or not the runner is active, meaning if it will accept new tasks.
  • If the runner should execute on protected tasks.
  • What tags the runner will pick new jobs from or if it can pick jobs that don’t have any tags.
  • Prevent the runner from being assigned to other projects.
  • Restrict jobs for the runner.

You won’t be able to specify its executor on the page, that needs to be done in the /etc/gitlab-runner/config.toml

GitLab Runner Configuration

The shell executor is the easiest to use but it requires that the tools be installed on the runner. With Docker you can specify a container that already has that as well as other containers that use different versions to perform a more in depth test of the application being developed.

Install Docker

Assuming that the requirements don’t include any specialized parameters, installing Docker is pretty straight forward and the following is for RedHat based distributions.
[text title="Install, enable, start Docker"]yum install docker device-mapper-libs device-mapper-event-libs
systemctl start docker.service
systemctl enable docker.service[/text]
Other distributions will be similar with the difference being the package manager command and possibly package names.
For more information about working with containers, see RedHat's Getting Started with Containers.

Add Docker Registry

In order to be able to use images from GitLab, Docker must know about it. Edit the appropriate configuration file. For RedHat EL, the correct file is /etc/containers/registries.conf
[text title="/etc/containers/registries.conf"]
# This is a system-wide configuration file used to
# keep track of registries for various container backends.
# It adheres to TOML format and does not support recursive
# lists of registries.

# The default location for this configuration file is /etc/containers/registries.conf.

# The only valid categories are: 'registries.search', 'registries.insecure',
# and 'registries.block'.

[registries.search]
registries = ['registry.access.redhat.com', 'gitlab.example.com:5050']

# If you need to access insecure registries, add the registry's fully-qualified name.
# An insecure registry is one that does not have a valid SSL certificate or only does HTTP.
[registries.insecure]
registries = ['gitlab.example.com:5050']

# If you need to block pull access from a registry, uncomment the section below
# and add the registries fully-qualified name.
#
# Docker only
[registries.block]
registries = [][/text]
Adding a registry to registries.search allows you to use namespace/image[:tag] schema to specify the image to use. If you need to specify a registry that doesn’t have a valid SSL certificate, such as a self-signed one, or if the registry only uses HTTP then you need to add it to the registries under [registries.insecure].

Using Images and Containers

Once everything is set up, you need to either define a pre-built image or make one yourself. If a lot of the projects are similar then you can create an image and push it to the registry.

Building a Local Image

Run the proceeding commands after the following Dockerfile.
[text title="Dockerfile"]
FROM registry.access.redhat.com/rhscl/php-70-rhel7
MAINTAINER Paul Lyon

USER root

RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer && chmod +x /usr/local/bin/composer[/text]
The following commands will build the docker image locally and then push it to the GitLab registry.
[text title="Docker, build, login, and push commands"]
docker build -t gitlab.example.com:4999 .
docker login
Username: paulus
Password:
docker push gitlab.example.com:499/paulus/docker-webapp[/text]

Building an Image with GitLab

Using GitLab to build a Docker image and publishing it requires at the very least a Dockerfile and a .gitlab-ci.yml file.
[text title="Dockerfile"]FROM centos/php-70-centos7
RUN npm install -g grunt gulp bower
VOLUME [ "/sys/fs/cgroup" ]
CMD [ "/usr/sbin/init" ][/text]
[text title=".gitlab-ci.yml"]
image: docker:latest
build-master:
stage: build
script:
- docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY
- docker build -t $CI_REGISTRY_IMAGE .
- docker push $CI_REGISTRY_IMAGE
only:
- master[/text]

Using Docker Registry Images

The following assumptions are made in the proceeding build script. The first is that we have created a Docker repository at docker-images/my-php7-centos, built, and published it. The second is that the SSH_PRIVATE_KEY and SSH_KNOWN_HOSTS secure variables have been defined. Those can be defined by going to Settings → CI / CD and under Secure Variables.
[text title=".gitlab-ci.yml"]
image: gitlab.example.com:5050/docker-images/my-php7-centos
stages:
- all

before_script:
- which ssh-agent || yum -y install openssh-clients
- eval $(ssh-agent -s)
- echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - > /dev/null
- mkdir -p ~/.ssh
- chmod 700 ~/.ssh
- echo "$SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts

job:
stage: all
script:
- echo "Doing work..."
- rsync $CI_PROJECT_DIR user@host:/path/to/deployment[/text]

Further Reading