Continuous Deployment with OpenBSD, Ansible and Gitlab CI Runners
CI/CD is something that many organisations aspire to but the idea of “flicking the switch” inspires dread. With my latest little project I decided that I would do CI/CD from the very start to prevent the fear of automated deploys
Due to its sensible configuration and secure design I aim to use OpenBSD wherever possible and this project is no different.
Gitlab have a CI system built directly into their product. By adding a .gitlab-ci.yml file into your repository you can leverage their extensive CI pipelines.
A simple config might just require the execution of go test and might look something like this;
image: golang:latest
stages:
- test
test-lint-etc:
stage: test
script:
- ln -s /builds /go/src/gitlab.com
- cd /go/src/gitlab.com/$CI_PROJECT_PATH
- go get
- go tool vet -composites=false -shadow=true *.go
- go test
If this pipeline passes we know that our unit tests pass and we could add another pipeline to build the binary.
The Gitlab shared runners are Linux based but the staging and production servers will be OpenBSD, thankfully GoLang makes it easy to cross compile!
image: golang:latest
stages:
- test
- build
test-lint-etc:
stage: test
script:
- ln -s /builds /go/src/gitlab.com
- cd /go/src/gitlab.com/$CI_PROJECT_PATH
- go get
- go tool vet -composites=false -shadow=true *.go
- go test
compile:
stage: build
script:
- ln -s /builds /go/src/gitlab.com
- cd /go/src/gitlab.com/$CI_PROJECT_PATH
- go get
- go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_NAME-linux
- env GOOS=openbsd go build -ldflags "-extldflags '-static'" -o $CI_PROJECT_NAME-openbsd
artifacts:
paths:
- $CI_PROJECT_NAME-*
Simply by adding env GOOS=openbsd before issuing go build we get an OpenBSD binary. Both the Linux and OpenBSD binarys are specified as build artifacts and made available for the next pipeline; deployment to staging!
image: golang:latest
stages:
- test
- build
- deploy-staging
test-lint-etc:
stage: test
script:
- ln -s /builds /go/src/gitlab.com
- cd /go/src/gitlab.com/$CI_PROJECT_PATH
- go get
- go tool vet -composites=false -shadow=true *.go
- go test
compile:
stage: build
script:
- ln -s /builds /go/src/gitlab.com
- cd /go/src/gitlab.com/$CI_PROJECT_PATH
- go get
- go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_NAME-linux
- env GOOS=openbsd go build -ldflags "-extldflags '-static'" -o $CI_PROJECT_NAME-openbsd
artifacts:
paths:
- $CI_PROJECT_NAME-*
push-to-staging:
stage: deploy-staging
tags:
- ansible
script:
- mkdir -p /etc/ablative/ansible/files/$CI_PROJECT_NAME/
- cp $CI_PROJECT_NAME-* /etc/ablative/ansible/files/$CI_PROJECT_NAME/
- cd /etc/ablative/ansible && git pull
- ansible-playbook -i /etc/ablative/ansible/infrastructure_staging /etc/ablative/ansible/$CI_PROJECT_NAME.yml
Note that this pipeline is tagged with ansible, this will ensure that this pipeline executes on one of my ‘Specific Runners’ which in this case is a physical machine which has been locked down (as it contains the ansible-vault password file!) and then had the Gitlab runner (also written in GoLang!) deployed.
Installing a Gitlab runner is quite simple and if one doesn’t already exist I’ll shortly be writing an Ansible play to do it, during configuration I specified the shell executor instead of using Docker, primarily to avoid having to mount the ansible directory etc but also to limit the amount of security maintenance (updates, firewall rules, sudo/doas exec, file permissions etc).
If Ansible executes without any issues then this pipeline will succeed and we know we are good to go to production!
image: golang:latest
stages:
- test
- build
- deploy-staging
- deploy-production
test-lint-etc:
stage: test
script:
- ln -s /builds /go/src/gitlab.com
- cd /go/src/gitlab.com/$CI_PROJECT_PATH
- go get
- go tool vet -composites=false -shadow=true *.go
- go test
compile:
stage: build
script:
- ln -s /builds /go/src/gitlab.com
- cd /go/src/gitlab.com/$CI_PROJECT_PATH
- go get
- go build -race -ldflags "-extldflags '-static'" -o $CI_PROJECT_NAME-linux
- env GOOS=openbsd go build -ldflags "-extldflags '-static'" -o $CI_PROJECT_NAME-openbsd
artifacts:
paths:
- $CI_PROJECT_NAME-*
push-to-staging:
stage: deploy-staging
tags:
- ansible
script:
- mkdir -p /etc/ablative/ansible/files/$CI_PROJECT_NAME/
- cp $CI_PROJECT_NAME-* /etc/ablative/ansible/files/$CI_PROJECT_NAME/
- cd /etc/ablative/ansible && git pull
- ansible-playbook -i /etc/ablative/ansible/infrastructure_staging /etc/ablative/ansible/$CI_PROJECT_NAME.yml
push-to-production:
stage: deploy-production
tags:
- ansible
script:
- mkdir -p /etc/ablative/ansible/files/$CI_PROJECT_NAME/
- cp $CI_PROJECT_NAME-* /etc/ablative/ansible/files/$CI_PROJECT_NAME/
- cd /etc/ablative/ansible && git pull
- ansible-playbook -i /etc/ablative/ansible/infrastructure_production /etc/ablative/ansible/$CI_PROJECT_NAME.yml
when: manual
The final point to note is the addition of when:manual, this allows you to gate a release or commit requiring manual intervention via the Gitlab UI. As the project in question has a holding page and no backend servers there is currently no production to speak of (the inventory infrastructure_production is empty :/) but once the project goes live that can be removed (or not as needs require!)