Недавно я начал работать над несколькими микросервисами Golang.
Я решил прокешировать gitlab и разделить работу на несколько шагов для лучшей обратной связи в пользовательском интерфейсе.
Вот некоторые изменения, появившиеся в моем файле .gitlab-ci.yaml:
- нет docker-in-docker;
- использование кеша для программных пакетов вместо готового изображения с зависимостями;
- разбиение на шаги;
- автодеплой с помощью Helm.
Построение образа Golang
Поскольку golang привередлив к местоположению проекта, нужно внести некоторые изменения в работу CI. Изменения производятся в блоке before_script
, который просто создает необходимые каталоги и исходный код ссылки. Предположительно официальным репозиторием проекта является gitlab.example.com/librerio/libr_files
, поэтому сценарий должен выглядеть следующим образом.
variables:
APP_PATH: /go/src/gitlab.example.com/librerio/libr_files
before_script:
- mkdir -p /go/src/gitlab.example.com/librerio/
- ln -s $PWD ${APP_PATH}
- mkdir -p ${APP_PATH}/vendor
- cd ${APP_PATH}
Теперь мы можем устанавливать зависимости и строить двоичные файлы. Чтобы избежать загрузки всех пакетов в каждой сборке, нужно настроить кеширование.
Согласно правилам кеширования GitLab нужно добавить каталог поставщика как в кеш, так и в артефакты. Кеш даст возможность использовать каталог между заданиями сборки, а программные изделия — внутри того же задания.
cache:
untracked: true
key: "$CI_BUILD_REF_NAME"
paths:
- vendor/
setup:
stage: setup
image: lwolf/golang-glide:0.12.3
script:
- glide install -v
artifacts:
paths:
- vendor/
Шаг сборки не изменился, дело в создании двоичной системы, которую я добавляю к артефактам.
build:
stage: build
image: lwolf/golang-glide:0.12.3
script:
- cd ${APP_PATH}
- GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o release/app -ldflags '-w -s'
- cd release
artifacts:
paths:
- release/
Этап тестирования
Чтобы запустить тесты golang с отчетами о покрытии, я использую скрипт оболочки. Он выполняет все тесты в подкаталогах проекта и создает отчет о покрытии. Прежде чем опубликовать скрипт в gist, я немного изменил его, исключив каталог поставщика из тестов.
покрытие regexp для gitlab-ci: ^total:\s*\(statements\)\s*(\d+.\d+\%)
Этап развертывания
Я не использую встроенную интеграцию Gitlab с Kubernetes.
Сначала я думал о создании Kubernetes-секретов и проведении мониторинга в под gitlab-runner. Но это сложно: развертывание необходимо обновлять каждый раз, когда требуется добавить новую конфигурацию кластера. Поэтому я использую переменные CI/CD GitLab с конфигурацией Kubernetes и кодировкой base64. Каждый проект может иметь любое количество конфигураций. Процесс прост: создайте строку base64 из конфига и скопируйте его в буфер обмена. После этого поместите его в переменную kube_config (назовите ее как угодно).
cat ~/.kube/config | base64 | pbcopy
Если вы не обладаете полной установкой gitlab, подумайте о создании пользователя Kubernetes с ограниченными разрешениями.
Затем на этапе развертывания можно декодировать KUBECONFIG
переменную обратно в файл и использовать ее с kubectl
.
variables:
KUBECONFIG: /etc/deploy/config
deploy:
...
before_script:
- mkdir -p /etc/deploy
- echo ${kube_config} | base64 -d > ${KUBECONFIG}
- kubectl config use-context homekube
- helm init --client-only
- helm repo add stable https://kubernetes-charts.storage.googleapis.com/
- helm repo add incubator https://kubernetes-charts-incubator.storage.googleapis.com/
- helm repo update
Этап развертывания включает случай, когда у вас несколько версий одного и того же приложения.
Например, у вас есть две версии API: v1.0 и v1.1. Все, что нужно сделать, — установить appVersion в файле Chart.yaml. Система сборки проверяет версии API и развертывает или обновляет необходимую.
- export API_VERSION="$(grep "appVersion" Chart.yaml | cut -d" " -f2)"
- export RELEASE_NAME="libr-files-v${API_VERSION/./-}"
- export DEPLOYS=$(helm ls | grep $RELEASE_NAME | wc -l)
- if [ ${DEPLOYS} -eq 0 ]; then helm install --name=${RELEASE_NAME} . --namespace=${STAGING_NAMESPACE}; else helm upgrade ${RELEASE_NAME} . --namespace=${STAGING_NAMESPACE}; fi
TL;DR;
Вот полный файл .gitlab-ci.yaml
.
cache:
untracked: true
key: "$CI_BUILD_REF_NAME"
paths:
- vendor/
before_script:
- mkdir -p /go/src/gitlab.example.com/librerio/
- ln -s $PWD ${APP_PATH}
- mkdir -p ${APP_PATH}/vendor
- cd ${APP_PATH}
stages:
- setup
- test
- build
- release
- deploy
variables:
CONTAINER_IMAGE: ${CI_REGISTRY}/${CI_PROJECT_PATH}:${CI_BUILD_REF_NAME}_${CI_BUILD_REF}
CONTAINER_IMAGE_LATEST: ${CI_REGISTRY}/${CI_PROJECT_PATH}:latest
DOCKER_DRIVER: overlay2
KUBECONFIG: /etc/deploy/config
STAGING_NAMESPACE: app-stage
PRODUCTION_NAMESPACE: app-prod
APP_PATH: /go/src/gitlab.example.com/librerio/libr_files
POSTGRES_USER: gorma
POSTGRES_DB: test-${CI_BUILD_REF}
POSTGRES_PASSWORD: gorma
setup:
stage: setup
image: lwolf/golang-glide:0.12.3
script:
- glide install -v
artifacts:
paths:
- vendor/
build:
stage: build
image: lwolf/golang-glide:0.12.3
script:
- cd ${APP_PATH}
- GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -o release/app -ldflags '-w -s'
- cd release
artifacts:
paths:
- release/
release:
stage: release
image: docker:latest
script:
- cd ${APP_PATH}/release
- docker login -u gitlab-ci-token -p ${CI_BUILD_TOKEN} ${CI_REGISTRY}
- docker build -t ${CONTAINER_IMAGE} .
- docker tag ${CONTAINER_IMAGE} ${CONTAINER_IMAGE_LATEST}
- docker push ${CONTAINER_IMAGE}
- docker push ${CONTAINER_IMAGE_LATEST}
test:
stage: test
image: lwolf/golang-glide:0.12.3
services:
- postgres:9.6
script:
- cd ${APP_PATH}
- curl -o coverage.sh https://gist.githubusercontent.com/lwolf/3764a3b6cd08387e80aa6ca3b9534b8a/raw
- sh coverage.sh
deploy_staging:
stage: deploy
image: lwolf/helm-kubectl-docker:v152_213
before_script:
- mkdir -p /etc/deploy
- echo ${kube_config} | base64 -d > ${KUBECONFIG}
- kubectl config use-context homekube
- helm init --client-only
- helm repo add stable https://kubernetes-charts.storage.googleapis.com/
- helm repo add incubator https://kubernetes-charts-incubator.storage.googleapis.com/
- helm repo update
script:
- cd deploy/libr-files
- helm dep build
- export API_VERSION="$(grep "appVersion" Chart.yaml | cut -d" " -f2)"
- export RELEASE_NAME="libr-files-v${API_VERSION/./-}"
- export DEPLOYS=$(helm ls | grep $RELEASE_NAME | wc -l)
- if [ ${DEPLOYS} -eq 0 ]; then helm install --name=${RELEASE_NAME} . --namespace=${STAGING_NAMESPACE}; else helm upgrade ${RELEASE_NAME} . --namespace=${STAGING_NAMESPACE}; fi
environment:
name: staging
url: https://librerio.example.com
only:
- master