Компиляция и деплой по SSH в Gitlab CI

08.05.2020 gitlab компиляция деплой ssh модули вендоринг ci-cd


Компиляция и деплой по SSH в Gitlab CI

Рассмотрим компиляцию проекта на 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'

Другие статьи