In this post I will show how I use GitLab CICD with Ansible. I'll show the pipelines and how the merge requests are handled for full control and auditing.
The GitLab flow is as following, you have one or more dev/working branches. To push code to the staging environment you do this via an merge request in GitLab, en to get this code in to production. Again via a merge request from the master branch to the production branch. This way we have automation but also control and auditing via merge request.
This can be be adjusted to suit environments and policies you need. It will not matter if you have a test and acceptance environment, or use tags, the principle will be the same.
GitLab CICD Pipeline
To use GitLab's CICD pipeline we need to create a .gitlab-ci.yml file, this is the heart of CI in GitLab.
Below a breakdown of of this yaml file. On my system it will spin up a Kubernetes Ubuntu pod, in this pod the verify test will run and the Ansible playbooks will be runned. But it should also work on a Gitlab runner with Docker.
First we need a before_script which will be executed by the GitLab runner at the start. We will use a local Docker image that has the environment for Ansible. This already has installed python, ansible and ansible-lint. Also we will put the private SSH key here. This key is defined as a variable in GitLab so this key is not in the git repository.
image: localhost:5000/ubuntu_ansible before_script: - whoami - apt-get update -qy #update system - git submodule update --init - ansible --version - ansible-lint --version - mkdir secret # create secret dir - echo "$ANSIBLE_SSHKEY" > secret/ansible.key # create priv key from gitlab variable - chmod 400 secret/ansible.key - export ANSIBLE_HOST_KEY_CHECKING=False # disable host key checking for ansible
We have defined a couple of stages we want to run on certain events in GitLab.
stages: - verify - prestaging - staging - predeploy - deploy
For each commit into GitLab there will be a verify pipeline running. That will do an ansible-lint and a ansible-playbook --check. This is to make sure that the code is syntax error free.
#verify syntaxs etc... ansible-verify: stage: verify script: - ansible-lint -v *.yml - ansible-playbook --inventory inventory/production --syntax-check *.yml rules: - if: '$CI_BUILD_BEFORE_SHA == "0000000000000000000000000000000000000000"' when: always - if: '$CI_COMMIT_BRANCH != "master" && $CI_COMMIT_BRANCH != "production"' when: always
Merge request to master (staging)
When you need to push code to the staging environment you do this via a merge request to master. Then again will the verify stage be runned, but also there wil be a pre-staging and staging pipe be runned. The pre-staging pipeline will do an Ansible ping. To make sure all hosts are up and working for Ansible. If this fails the pipeline will be cancelled. If the staging hosts are ok, the the Ansible playbook will be runned and the code will be commit to master.
We can tick the option to automatically merge to master when the pipeline succeeds.
The yaml code that will check is staging hosts are online and working via Ansible ping. If they are execute the playbook, on success merge to master.
# make sure all staging hosts are online and can be manged by ansible if not stop pipeline prestaging: stage: prestaging script: - ansible --private-key secret/ansible.key --user ansible --inventory inventory/staging all -m ping rules: - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"' when: always # test playbook on staging envirioment only run on merge requests to master staging: stage: staging script: - ./cicd-info.sh > roles/common/files/cicd-info.txt # Run script to get GitLab CI env vars. Ansible will copy then to hosts. - ansible-playbook --private-key secret/ansible.key --user ansible --inventory inventory/staging common.yml rules: - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master"' when: always
Merge request to production from master (staging)
To deploy to production will also happen via a merge request. It is the same principle as from work-brand to master. The only difference is that Ansible will use production hosts.
# make sure all production hosts are online and can be manged by ansible if not stop pipeline predeploy: stage: predeploy script: - ansible --private-key secret/ansible.key --user ansible --inventory inventory/production all -m ping rules: - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "production"' when: always # deploy playbooks to production deploy: stage: deploy script: - ./cicd-info.sh > roles/common/files/cicd-info.txt # Run script to get GitLab CI env vars. Ansible will copy then to hosts. - ansible-playbook --private-key secret/ansible.key --user ansible --inventory inventory/production common.yml rules: - if: '$CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "production"' when: always
Here we see all jobs that are involved from the commit in the work-branch al the way to production. These jobs are lint and syntax checked, have passed staging and have been successful deployed on production.
I also created a small script, that will gather some usefull GitLab CI envirioment varibles on the runner. This script will put these in a file and Ansible will pick this file up and put the on the hosts.
#!/bin/bash echo "Gitlab CICD Ansible run info" echo echo "started on `date`" echo "Project name: $CI_PROJECT_NAME" echo "Commit SHA: $CI_COMMIT_SHORT_SHA" echo "On runner: $HOSTNAME with ID: $CI_RUNNER_ID" echo "Job info url: $CI_JOB_URL" echo "By user: $GITLAB_USER_LOGIN <$GITLAB_USER_EMAIL>" echo "Commit: $CI_COMMIT_TITLE"
The output on the hosts will look something like this. With these info we can always look at the latest Ansible run for this host in GitLab.
0 dennis@ragnarok:~> cat /etc/cicd-info.txt Gitlab CICD Ansible run info started on Wed Jan 15 14:09:34 UTC 2020 Project name: ansible Commit SHA: ed2cc1b0 On runner: runner-bhpg76e-project-2-concurrent-0dgklh with ID: 2 Job info url: http://192.168.66.148/dkruyt/ansible/-/jobs/192 By user: dkruyt <email@example.com> Commit: Merge branch 'work-branch' into 'master'
Protect master and production branch
Make sure your master and production branch are protected, so no one can commit directly in to these branches. Because we want to run the correct pipelines on merge requests.