Рассмотрим компиляцию проекта на GO в принципе и как это работает в Gitlab CI.
Компиляция
Для сборки нам необходимо вызвать go build -o binary_name
.
Если в вашем проекте есть импорты из сторонних репозиториев, значит есть внешние зависимости. Для компиляции для GO потребует весь исходный код, включая код библиотек-зависимостей. Таким образом для компиляции необходимо обеспечить доступность в том числе сторонних библиотек, которые могут не хранится в вашем репозитории.
Вендоринг или загрузка
На данный момент актуальной версией является 1.14.2. Начиная с версии GO 1.11 доступна функциональностей модулей (go modules
).
Модули GO работают так, что сам GO подгружает все сторонние библиотеки при вызове go run
, go build
или при явном подгрузке всех библиотек - go get ./...
.
Исходный код скачанных библиотек сохраняется в $GOPATH/pkg/mod
или в $HOME/go/pkg/mod
, если переменная окружения $GOPATH
не установлена.
Существует подход для работы с зависимостями, называемый вендоринг. Модули GO поддерживают вендоринг так, что если указать параметр -mod=vendor_dir
, то зависимости будут скачаны в папку vendor_dir
. Данная папка может находится внутри вашего репозитория и быть запушена в Git репозиторий вместе с вашим исходным кодом.
При использовании вендоринга, зависимости будут скачаны единожды и будут поставляться вместе с исходным кодом проекта, что позволит быстрее выполнять сборку.
Gitlab
Gitlab предоставляет возможность выполнять различные задачи после пуша в репозиторий.
Чтобы задействовать эту функциональность, необходимо добавить в корень репозитория файл .gitlab-ci.yml
.
Компиляция в Gitlab
Необходимо определить этапы (stages) и конкретные действия, выполняемые на этих этапах. Для каждого действия можно задать докер-образ.
Код вашего проекта будет автоматически скачан в папку /builds/{project_group}/{project_name}
. Это означает, что нет никакой необходимости скачивать его вручную.
В коде ниже я задал этап build и одноименное действие в нем:
stages:
— build
build:
image: rhaps1071/golang-1.14-alpine-git
stage: build
script:
— go get ./...
— GOARCH=amd64 GOOS=linux go build -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/binary
artifacts:
paths:
— binary
Здесь используется докер-образ моего авторства rhaps1071/golang-1.14-alpine-git.
Он добавляет команду git
в golang:1.14-alpine
. Данная доработка необходима для команды go get ./...
, которая подгружает зависимости и использует git clone
.
Базовый образ основан на Alpine Linux, так как данный дистрибутив весит всего несколько мегабайт.
Однако размер golang:1.14-alpine
весит 370MB, что много, но все же двухкратный выйгрыш по сравнению с golang:1.14
(809MB), основанным на Ubuntu.
Я не использую вендоринг, поэтому для сборки мне необходимо скачать зависимости. Для этого вызывается команда go get ./...
.
Как я сказал выше, код в Gitlab CI расположен в папке /builds/{project_name}/{project_folder}
, которая находится вне $GOPATH
.
Для того, чтобы это работало, в корне вашего проекта должен быть файл go.mod
. Создать его можно с помощью команды go mod init
. При отсутствии файла go.mod
команда go get ./...
не сможет выяснить зависимости.
Если ваш проект не использует модули GO, то ваш исходный код перед выполнением каких-либо команд нужно будет перенести в $GOPATH
.
В Gitlab CI это копирование будет выглядеть так:- cp /builds/* $GOPATH/src/
Инструкция artifacts в .gitlab-ci.yml
позволяет сохранить какой-либо из файлов или папок для скачивания, а также для использования на следующих этапах Gitlab CI.
Деплой по SSH
Рассмотрим деплой полученного бинарника с помощью SSH.
deploy_stage:
image: kroniak/ssh-client
stage: deploy
environment:
name: stage
url: http://stage.project.com
when: manual
script:
— mkdir -p ~/.ssh
— echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
— chmod -R 700 ~/.ssh
— echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
— chmod 644 ~/.ssh/known_hosts
— echo "$CONFIG" > ./config.json
— scp -P$SSH_PORT -r ./config.json $SSH_USER@$SSH_HOST:/var/www/project/config/
— scp -P$SSH_PORT -r ./binary $SSH_USER@$SSH_HOST:~/binary_tmp
— ssh -p$SSH_PORT $SSH_USER@$SSH_HOST 'sudo service project stop && cp ~/binary_tmp /var/www/project/binary && sudo service project restart'
Здесь снова использован кастомизированный образ Alpine Linux — kroniak/ssh-client
размером 12.1MB. На этот раз в нем дополнительно предустановлен ssh-клиент, благодаря чему осуществляется вызов команд ssh
и scp
.
Логика деплоя выглядит следующим образом:
- Заведены следующие переменные Gitlab:
$SSH_PRIVATE_KEY
- приватный ключ для доступа к серверу;$SSH_USER
,$SSH_HOST
,$SSH_PORT
— логин, и адрес сервера для деплоя;$SSH_KNOWN_HOSTS
— запись для файла .ssh/known_hosts, через который происходит валидация сервера;$CONFIG
— содержимое файла конфигурации нашего сервиса в формате json; - При деплое сначала заполняем все необходимые файлы данными из переменных Gitlab;
- Все необходимые файлы копируются на сервер при помощи
scp
. Можно было бы использоватьrsync
, так как он копирует только изменившиеся файлы и копирует их в архивированном виде. Однако когда речь об 1-2 файлах, то выйгрыша практически нет. - Последним действием мы подменяем бинарник и перезапускаем наш сервис;
Полученный файл .gitlab-ci.yml
полностью выглядит следующим образом:
stages:
— build
— deploy
build:
image: rhaps1071/golang-1.14-alpine-git
stage: build
script:
— go get ./...
— GOARCH=amd64 GOOS=linux go build -ldflags "-extldflags '-static'" -o $CI_PROJECT_DIR/binary
artifacts:
paths:
— binary
deploy_stage:
image: kroniak/ssh-client
stage: deploy
environment:
name: stage
url: http://stage.project.com
when: manual
script:
— mkdir -p ~/.ssh
— echo "$SSH_PRIVATE_KEY" > ~/.ssh/id_rsa
— chmod -R 700 ~/.ssh
— echo "$SSH_KNOWN_HOSTS" >> ~/.ssh/known_hosts
— chmod 644 ~/.ssh/known_hosts
— echo "$CONFIG" > ./config.json
— scp -P$SSH_PORT -r ./config.json $SSH_USER@$SSH_HOST:/var/www/project/config/
— scp -P$SSH_PORT -r ./binary $SSH_USER@$SSH_HOST:~/binary_tmp
— ssh -p$SSH_PORT $SSH_USER@$SSH_HOST 'sudo service project stop && cp ~/binary_tmp /var/www/project/binary && sudo service project restart'