В этой статье будет рассмотрена настройка Gitlab CI-CD для автоматического запуска ваших тестов в Laravel-приложении. Кроме тестов также запустим проверку оформления php-кода и статический анализ через phpstan. Перед запуском проверок будет создаваться Docker-образ с php нужной версии и необходимыми расширениями. Наши тесты будут работать с базой Mysql.
Содержание
- Задачи и этапы
- Сервисы Gitlab
- Создание основного образа
- Содержимое Dockerfile
- Установка зависимостей composer
- Запуск тестов php
- Проверки кода
- Итог
Задачи и этапы
И так, вы наверное уже слышали, что CI-CD в Gitlab состоит из описания задач (jobs). В каждой задаче есть одна или более команд.
Также там есть этапы (stages). Каждая задача принадлежит какому-либо этапу. Названия и этапов и задач мы назначаем сами.
В рамках одного этапа задачи выполняются параллельно. А сами этапы выполняются в той последовательности, в которой объявлены.
Чтобы настроить CI-CD в Gitlab нам необходимо создать файл .gitlab-ci.yml
и описать в нем этапы и задачи.
В нашем примере будет 3 этапа. Файл начнется с их перечисления:
stages:
- build
- compose
- check
У нас три этапа: build (сборка образа с нужной версией php и расширениями), compose (установка зависимостей через composer и check (все наши проверки и тесты).
Сервисы Gitlab
Когда мы настраиваем CI-CD, то создаем образ, который будет использоваться для запуска контейнеров, в которых выполняются наши задачи. Но зачастую нам нужны еще вспомогательные образы и контейнеры. Из задач контейнера на базе основного образа можно обращаться к вспомогательному контейнеру.
Обычно в качестве сервисов используются контейнеры на базе образов баз данных. Но мы вольны использовать любые образы с Docker Hub или другого реестра.
Наш сервис будет описываться так:
services:
- name: mysql:8.0
alias: mysql
variables:
MYSQL_DATABASE: hello_world_db
MYSQL_ROOT_PASSWORD: root_secret
Ключевое слово services
используется для того, чтобы указать какие сервисы мы будем использовать. В name
указываем название и версию docker-образа. Используем образ mysql.
Ключевое слово variables
, как ясно из названия, задает переменные. Контейнер сервиса mysql будет использовать эти переменные (их имена можно увидеть в документации к образу mysql). В нашем примере создается БД с конкретным именем и задается пароль root. Эти переменные объявлены в глобальном пространстве и могут использоваться не только этим сервисом, а например Laravel-приложением при запуске тестов в нем.
Создание основного образа
Для того, чтобы задачи из нашего файла .gitlab-ci.yml
выполнялись, должен работать Gitlab-runner. Такие runner-ы бывают разными. Для Docker используется соответствующий. При обработке задач docker runner запускает контейнер указанного образа для каждого шага. И здесь кроется первая проблема. Для создания своего образа Docker в задаче (этап build) нам нужно обращаться к docker из docker-контейнера.
Для создания docker-образа в CI-CD в основном используется два подхода:
- dind – Docker-in-docker (почему один в другом – описано выше)
- kaniko. Инструмент, который собирает образы контейнеров из Dockerfile без Docker
Dind
Для dind задача по созданию образа выглядит так:
gitlab:
stage: build
image: docker
services:
- name: docker:dind
alias: docker
script:
- echo "${CI_REGISTRY_PASSWORD}" | docker login $CI_REGISTRY -u "${CI_REGISTRY_USER}" --password-stdin
- docker build --pull -t ${CI_REGISTRY_IMAGE}/gitlab:${CI_COMMIT_REF_NAME} -f ./docker/gitlab/Dockerfile ./
- docker push ${CI_REGISTRY_IMAGE}/gitlab:${CI_COMMIT_REF_NAME}
Опишем основные элементы секции.
Для задачи указывается имя. Здесь указано gitlab
, но оно может быть любым. Указывается этап, к которому относится задача – stage: build
. В секции services
указан сервис docker:dind
, который используется в командах задачи. И, наконец, в секции script
указываются конкретные запускаемые команды. В последних используются предустановленные переменные Gitlab (Например, CI_REGISTRY_USER
).
В командах мы создаем образ на базе файла docker/gitlab/Dockerfile
, который должен находиться в нашем репозитории. Затем образ отправляется в реестр gitlab.
Kaniko
Для Kaniko эта задача выглядит таким образом:
kaniko-build:
stage: build
image:
name: gcr.io/kaniko-project/executor:v1.23.1-debug
entrypoint: [""]
variables:
DOCKER_CONFIG: /kaniko/.docker/
script:
- echo "{\"auths\":{\"$CI_REGISTRY\":{\"username\":\"$CI_REGISTRY_USER\",\"password\":\"$CI_REGISTRY_PASSWORD\"}}}" > /kaniko/.docker/config.json
- /kaniko/executor
--context $CI_PROJECT_DIR
--dockerfile $CI_PROJECT_DIR/docker/gitlab/Dockerfile
--destination ${CI_REGISTRY_IMAGE}/gitlab:${CI_COMMIT_REF_NAME}
Секции здесь уже знакомые. Но, как видно, мы здесь не обращаемся к docker. Всю магию делает /kaniko/executor
.
Важно чтобы в image.name
был указан образ -debug
. Этот образ содержит sh
, который используется Gitlab-runner. Без этого работать не будет.
Dind или Kaniko?
Какой же из вариантов выбрать?
Вариант dind
позволяет использовать привычные команды Docker для сборки, запуска и управления контейнерами. Это упрощает переносимость существующих скриптов и процессов. В этом случае можно выполнять любые операции с Docker, включая запуск контейнеров, создание сетей и управление томами.
Однако, dind
требует привилегированных контейнеров для работы, что увеличивает риск безопасности. Привилегированные контейнеры могут получить доступ к хостовому ядру, что создает угрозу для безопасности.
Kaniko не требует привилегированных контейнеров и не запускает Docker-демон. Это значительно снижает риск безопасности. Он использует кэширование и оптимизирован для работы в облачных средах, что может улучшить производительность сборок.
Однако, kaniko
ограничен сборкой Docker-образов и не может выполнять другие операции Docker, такие как управление контейнерами или сетями. Он может не поддерживать некоторые сложные сценарии сборки, которые требуют специфических команд или функций Docker.
Другие варианты: Dood и Sysbox
Еще есть возможность использовать docker host-системы путём монтирования docker.sock в контейнер. Называется это Docker outside of Docker (DooD). Он также, как dind, имеет недостатки в плане безопасности.
Также стоит упомянуть, что можно настроить Gitlab-runner для работы с sysbox. В этом случае работать будет по принципу dind, но не потребует привилегированного доступа, что решит основную проблему этого варианта.
Об этих способах написано в статье Использование Docker in Docker в GitLab.
Содержимое Dockerfile
В любом из этих случаев нам потребуется Dockerfile с описание ПО, которое мы используем.
FROM php:8.2-cli-alpine
ADD https://github.com/mlocati/docker-php-extension-installer/releases/latest/download/install-php-extensions /usr/local/bin/
RUN chmod +x /usr/local/bin/install-php-extensions \
&& install-php-extensions mbstring zip pcntl bcmath pdo_mysql dom redis \
&& rm -rf /tmp/* /var/tmp/* /usr/share/doc/* /var/cache/apk/* /usr/share/php8 \
;
COPY --from=composer:latest /usr/bin/composer /usr/bin/composer
Здесь мы используем пакет mlocati/docker-php-extension-installer
, который делает установку php-расширений проще. Можно было бы ставить расширения через пакетный менеджер, но ряд проблем с зависимостями при этом мог бы создать трудности.
Также здесь мы сразу добавляем composer из одноименного образа.
Установка зависимостей composer
Для запуска тестов Laravel-приложения нам потребуется установить зависимости через composer.
composer-dev:
stage: compose
image: ${CI_REGISTRY_IMAGE}/gitlab:${CI_COMMIT_REF_NAME}
cache:
key: composer-dev-${CI_COMMIT_REF_SLUG}
paths:
- vendor/
artifacts:
paths:
- vendor/
expire_in: 1 day
script:
- composer install
Здесь мы указываем следующий этап после сборки образа. В качестве базового образа мы указываем тот, который собрали. Команда в этом шаге очень простая – composer install
.
Но есть пару новых вещей – кеш и артефакты.
Кеш нужен, чтобы не выполнять работу дважды между разными запусками той же задачи. Так мы экономим время. У кеша есть ключ – key
.
А артефакты позволяют оставить какие-либо файлы результата работы доступными для последующих задач (в других этапах). Нам ведь понадобится папка vendor
.
Запуск тестов php
Теперь мы подошли к тому, чтобы запустить наши тесты. Эта задача будет выглядеть так:
phpunit:
stage: check
dependencies:
- composer-dev
image: ${CI_REGISTRY_IMAGE}/gitlab:${CI_COMMIT_REF_NAME}
variables:
APP_NAME: 'MyLaravel'
APP_KEY: 'base64:pHLqwKYCbGRrp3YrrJnH7FBR2buuhdhAsJWaWZe2Xqo='
APP_ENV: testing
APP_DEBUG: true
DB_CONNECTION: mysql
DB_HOST: mysql
DB_PORT: 3306
DB_DATABASE: $MYSQL_DATABASE
DB_USERNAME: root
DB_PASSWORD: $MYSQL_ROOT_PASSWORD
script:
- php artisan test
Здесь мы видим указание следующего этапа в stage
и использование образа, который мы создали ранее (image
).
В секции dependencies
мы указываем, что данная задача зависит от артефактов, которые создаются в задаче composer-dev
.
Приложению Laravel понадобится ряд переменных окружения, которые мы обычно задаем в файле .env
. Часть данных укажем явно, а часть (имя БД и пароль) возьмем из глобальных переменных, которые создали ранее.
Команда задачи запускает тесты php artisan test
. При этом нам необходимо, чтобы миграции выполнились. Если вы используете в тестах trait Illuminate\Foundation\Testing\RefreshDatabase
, то он позаботится о том, чтобы миграции были применены перед тестами. В иных случая в команды (script
) данной задачи необходимо будет добавить и php artisan migrate
.
Проверки кода
В одной из предыдущих статей рассказывалось о дополнительных способах проверки php-кода помимо тестов. Эти инструменты будет полезно также запускать в рамках CI-CD. Поэтому добавим в наш файл еще задач.
phpcs:
stage: check
image: ${CI_REGISTRY_IMAGE}/gitlab:${CI_COMMIT_REF_NAME}
dependencies:
- composer-dev
cache:
key: phpcs-${CI_COMMIT_REF_SLUG}
paths:
- .phpCsCache
script:
- vendor/bin/phpcs --parallel=4 --cache=.phpCsCache
phpstan:
stage: check
image: ${CI_REGISTRY_IMAGE}/gitlab:${CI_COMMIT_REF_NAME}
dependencies:
- composer-dev
cache:
key: phpstan-${CI_COMMIT_REF_SLUG}
paths:
- .phpStanCache/
script:
- vendor/bin/phpstan analyse -c phpstan.neon --no-progress --memory-limit=512M -vvv
Все параметры этих шагов уже описывались ранее. Здесь используется Docker-образ, который мы создали на самом первом шаге. Также здесь используются артефакты из composer-dev. При этом в каждой задаче создается свой кеш, который экономит время в последующих запусках.
Итог
В результате у нас получилось описание нужного нам процесса CI-CD для Gitlab. Он содержит несколько этапов и задачи по подготовке образа, загрузке зависимостей и выполнению самих проверок кода.
Запуск отрабатывает успешно. Все запуски можно найти в разделе Build
-> Pipelines
Полный код примера данного CI-CD с приложением Laravel можно найти в репозитории Gitlab.