Настройка GitLab CI-CD через SSH на VPS

1. Предварительная информация

У кого есть опыт в CI/CD, могут сразу смотреть разделы Настройка деплоя (Deploy Stage) или Настройка тестирования (Test Stage).

Краткая предистория этих заметок

Впервые я столкнулся с методологией CI/CD в 2021 году, когда перешёл из аутсорса в продуктовую компанию и стал активно релизить проекты в команде. В то время разработчикам не особо приходилось погружаться в эту тему — это была компетенция DevOps. Но когда я решил завести собственный блог, вопрос автоматизации деплоя стал для меня важным. В итоге появились заметки, которыми поделюсь в этом посте.

Почему я выбрал CI/CD через SSH на VPS?

У меня обычный блог на PHP/Laravel, и ключевым требованием стала простота реализации. Я рассматривал также вариант деплоя через Runner на VPS, но в силу ограниченных ресурсов VPS и отсутствия необходимости сборки и тестирования проекта в едином окружении решил использовать SSH-подход.

Что уже должно быть настроено

Предполагается, что уже настроены следующие моменты:

  • поднят VPS с Ubuntu;
  • настроен доступ к VPS по SSH-ключам;
  • код проекта залит в репозиторий GitLab.

Версия GitLab >= 16.11

2. Настройка деплоя (Deploy Stage)

В данном разделе описан простой пример деплоя проекта из репозитория GitLab на VPS. Деплой срабатывает при любом варианте обновления ветки master:

  • прямой push изменений в ветку;
  • после слияния MR (например, Merge branch 'develop' into 'master');
  • ручной запуск Pipeline.

2.1. Алгоритм действий

  1. Подготовка bash скрипта для деплоя
  2. Настройка переменных в GitLab
  3. Настройка файла .gitlab-ci.yml (Часть Deploy)

2.2. Подготовка bash скрипта для деплоя

Я использую bash-скрипт ~/deploy.sh для автоматизации команд деплоя на VPS. Пример базовых команд из моего скрипта:

#!/bin/bash

project_dir="/var/www/laravel"
ssh_key="~/.ssh/gitlab_rsa"

eval `ssh-agent -s`
ssh-add "$ssh_key"

cd "$project_dir"
git pull origin master

php artisan migrate --force
php artisan optimize

Реальный скрипт сложнее: с бэкапом БД и сопутствующими процессами.

Важно выдать права на выполнение:

chmod +x ~/deploy.sh

2.3. Настройка переменных в GitLab

Эти переменные будут использоваться в Pipeline для организации процесса деплоя.

Переходим: Project → Settings - CI/CD → Variables

Рекомендуемый набор:

  • SSH_KEY — приватный ключ для доступа к серверу. Лучше задать тип File.
  • SSH_USER — имя пользователя на VPS.
  • SSH_HOST — IP-адрес или домен сервера.

На скриншоте я не использовал тип Environments, поскольку у меня только production-окружение. Но при наличии других окружений (stage, feature...) эта настройка может понадобиться.

Если SSH_KEY имеет тип File, GitLab создаёт временный файл и подставляет в переменную путь к нему. Тогда в before_script можно использовать:

cp "$SSH_KEY" ~/.ssh/id_rsa

Если вы храните ключ как обычную переменную типа Variable, нужно записывать его в файл вручную:

printf '%s\n' "$SSH_KEY" > ~/.ssh/id_rsa

2.4. Настройка файла .gitlab-ci.yml (Часть Deploy)

В корне проекта создаём файл .gitlab-ci.yml со следующими настройками:

stages:
  - deploy

deploy:
  image: alpine:latest
  stage: deploy
  only:
    - master
  before_script:
    - apk add --no-cache openssh-client
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - cp "$SSH_KEY" ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan $SSH_HOST > ~/.ssh/known_hosts 2>/dev/null
  script:
    - ssh $SSH_USER@$SSH_HOST "bash ~/deploy.sh"

После добавления изменений в ветку master появится Pipeline для Stage: deploy. Его проще всего увидеть в разделе: Project → Build → Pipelines.

3. Настройка тестирования (Test Stage)

При необходимости запуска тестов и других команд для проверки работоспособности кода можно добавить соответствующий этап. Опишу пример запуска тестов на этапе создания Merge Request из ветки develop в master.

3.1. Алгоритм действий

  1. Подготовка проекта к тестам
  2. Настройка файла .gitlab-ci.yml (Часть Test)

3.2. Подготовка проекта к тестам

Я использую PHPUnit для тестов и соответственно часть кода покрыл unit и интеграционными тестами. В проекте использую отдельный файл .env.testing:

APP_NAME=Laravel
APP_ENV=testing
LOG_CHANNEL=nullable
DB_CONNECTION=sqlite
DB_DATABASE=:memory:
CACHE_DRIVER=array
SESSION_DRIVER=array
QUEUE_CONNECTION=sync
BCRYPT_ROUNDS=4
MAIL_DRIVER=array

Запуск тестов делаю через команду:

composer test

Команда реализована через нотацию scripts в composer.json:

{
    // ...
    "scripts": {
        // ...
        "test": "APP_ENV=testing php -d memory_limit=512M vendor/bin/phpunit"
    }
}

Нам нужно запустить тесты в виртуальном окружении, но перед этим его нужно собрать.

3.3. Настройка файла .gitlab-ci.yml (Часть Test)

Продолжаем редактировать .gitlab-ci.yml и добавляем новый этап для тестирования:

stages:
  - test

test:
  stage: test
  image: php:8.4-fpm
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop")'
      when: always
    - when: never
  before_script:
    - apt-get update && apt-get install -y --no-install-recommends libzip-dev zip unzip libxml2-dev sqlite3 libsqlite3-dev
    - docker-php-ext-install zip bcmath xml pdo pdo_sqlite
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    - cp .env.testing .env
    - composer install --no-progress --no-suggest
    - php artisan config:clear
  script:
    - composer test

Сейчас я использую SQLite, но изначално в проекте был MySQL. Ниже пример для MySQL:

services:
  - mysql:8.0
variables:
  MYSQL_ROOT_PASSWORD: "root"
  MYSQL_DATABASE: "laravel_test"

После создания Merge Request из ветки develop в master появится новый Pipeline для Stage: test. Как и в случае с деплоем, его можно проверить в разделе: Project → Build → Pipelines.

4. Дополнение

4.1. Немного о Pipelines и Jobs

С первого раза вряд ли получится всё запустить правильно. Рекомендую не торопиться и создать тестовую ветку, на примере которой можно потренироваться с запуском и отладкой деплоя или тестов.

DevOps обычно так и делают, чтобы не портить основную ветку тестовыми коммитами.

Основные разделы, с которыми стоит познакомиться:

  • Project → Build → Pipelines
  • Project → Build → Jobs

4.2. Полный пример файла .gitlab-ci.yml

Ниже представлен полный пример файла .gitlab-ci.yml с этапами тестирования и деплоя.

stages:
  - test
  - deploy

test:
  stage: test
  image: php:8.4-fpm
  rules:
    - if: '$CI_PIPELINE_SOURCE == "merge_request_event" && ($CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "master" || $CI_MERGE_REQUEST_TARGET_BRANCH_NAME == "develop")'
      when: always
    - when: never
  before_script:
    - apt-get update && apt-get install -y --no-install-recommends libzip-dev zip unzip libxml2-dev sqlite3 libsqlite3-dev
    - docker-php-ext-install zip bcmath xml pdo pdo_sqlite
    - curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer
    - cp .env.testing .env
    - composer install --no-progress --no-suggest
    - php artisan config:clear
  script:
    - composer test

deploy:
  image: alpine:latest
  stage: deploy
  only:
    - master
  before_script:
    - apk add --no-cache openssh-client
    - mkdir -p ~/.ssh
    - chmod 700 ~/.ssh
    - cp "$SSH_KEY" ~/.ssh/id_rsa
    - chmod 600 ~/.ssh/id_rsa
    - ssh-keyscan $SSH_HOST > ~/.ssh/known_hosts 2>/dev/null
  script:
    - ssh $SSH_USER@$SSH_HOST "bash ~/deploy.sh"

4.3. Что дальше?

Это всего лишь базовая часть CI/CD, которая позволяет автоматизировать релиз проекта. Дальше можно углубляться в такие темы, как настройка собственного registry, кеширование, артефакты и другие особенности настройки и оптимизации CI/CD.

5. Источники и ссылки

  1. GitLab CI/CD variables — GitLab Docs
  2. CI/CD YAML syntax reference — GitLab Docs
  3. GitLab CI/CD examples — GitLab Docs
  4. Using SSH keys with GitLab CI/CD — GitLab Docs