Как настроить CI/CD для PHP-проекта в GitLab в 2016 году
Коротко: Создайте .gitlab-ci.yml в корне репозитория, определите стадии test и deploy, укажите Docker-образ php:7.0, настройте SSH-ключ как переменную окружения в GitLab Settings → Variables. При push в master — автоматически запускаются тесты, при успехе — деплой на сервер.
.gitlab-ci.yml
# .gitlab-ci.yml — в корне репозитория
image: php:7.0-cli
stages:
- prepare
- test
- deploy
variables:
COMPOSER_HOME: "$CI_PROJECT_DIR/.composer"
APP_ENV: "testing"
cache:
key: "$CI_COMMIT_REF_NAME"
paths:
- vendor/
- .composer/
# Кэшировать vendor/ между пайплайнами = не скачивать Composer каждый раз
# Стадия 1: установить зависимости
composer_install:
stage: prepare
script:
- apt-get update -q && apt-get install -yq unzip git libzip-dev
- docker-php-ext-install zip pdo_mysql mbstring
- curl -sS https://getcomposer.org/installer | php
- php composer.phar install --no-interaction --prefer-dist
artifacts:
paths:
- vendor/
expire_in: 1 hour
# Стадия 2: PHPUnit тесты
phpunit:
stage: test
services:
- mysql:5.7
variables:
MYSQL_DATABASE: test_db
MYSQL_ROOT_PASSWORD: rootsecret
DB_HOST: mysql
DB_DATABASE: test_db
DB_USERNAME: root
DB_PASSWORD: rootsecret
script:
- docker-php-ext-install pdo_mysql
- cp .env.testing .env
- php artisan migrate --env=testing
- vendor/bin/phpunit --coverage-text
coverage: '/Lines:\s+(\d+\.\d+)%/' # Парсинг покрытия из вывода
# Стадия 2: Статический анализ
phpcs:
stage: test
script:
- vendor/bin/phpcs --standard=PSR2 app/ --ignore=app/Exceptions/
allow_failure: true # Не блокировать деплой из-за code style
# Стадия 3: Деплой на production (только master ветка)
deploy_production:
stage: deploy
script:
- apt-get update -q && apt-get install -yq openssh-client rsync
- mkdir -p ~/.ssh
- echo "$DEPLOY_SSH_PRIVATE_KEY" | tr -d '\r' > ~/.ssh/id_rsa
- chmod 600 ~/.ssh/id_rsa
- echo "$DEPLOY_SSH_KNOWN_HOSTS" > ~/.ssh/known_hosts
- chmod 644 ~/.ssh/known_hosts
- chmod +x deploy.sh
- ./deploy.sh
only:
- master
environment:
name: production
url: https://myapp.ru
when: on_success # Только если все тесты прошли
deploy.sh
#!/bin/bash
# deploy.sh — деплой на production сервер
set -e # Выйти при любой ошибке
DEPLOY_USER="deploy"
DEPLOY_HOST="10.0.0.1"
DEPLOY_PATH="/var/www/myapp"
RELEASE_PATH="$DEPLOY_PATH/releases/$(date +%Y%m%d%H%M%S)"
SHARED_PATH="$DEPLOY_PATH/shared"
CURRENT_LINK="$DEPLOY_PATH/current"
echo "=== Deploying to $DEPLOY_HOST ==="
# Создать директорию для нового релиза
ssh $DEPLOY_USER@$DEPLOY_HOST "mkdir -p $RELEASE_PATH"
# Скопировать код на сервер
rsync -az --delete \
--exclude='.git' \
--exclude='node_modules' \
--exclude='tests' \
./ $DEPLOY_USER@$DEPLOY_HOST:$RELEASE_PATH/
# Выполнить шаги деплоя на сервере
ssh $DEPLOY_USER@$DEPLOY_HOST << EOF
set -e
# Символические ссылки на shared ресурсы
ln -sf $SHARED_PATH/.env $RELEASE_PATH/.env
ln -sf $SHARED_PATH/storage $RELEASE_PATH/storage
cd $RELEASE_PATH
# Установить зависимости
composer install --no-dev --optimize-autoloader --no-interaction
# Запустить миграции
php artisan migrate --force
# Кэшировать конфигурацию
php artisan config:cache
php artisan route:cache
# Переключить current на новый релиз (атомарная операция)
ln -sfn $RELEASE_PATH $CURRENT_LINK
# Перезапустить PHP-FPM (graceful reload)
sudo service php7.0-fpm reload
# Очистить Queue Workers
php artisan queue:restart
# Удалить старые релизы (оставить последние 5)
ls -dt $DEPLOY_PATH/releases/*/ | tail -n +6 | xargs rm -rf
echo "Deploy complete: $RELEASE_PATH"
EOF
echo "=== Deploy successful ==="
Настройка переменных в GitLab
GitLab → Project → Settings → CI/CD → Variables
DEPLOY_SSH_PRIVATE_KEY = (приватный SSH-ключ, тип: File)
# Сгенерировать: ssh-keygen -t rsa -b 4096 -f deploy_key
# Публичный ключ добавить в ~/.ssh/authorized_keys на сервере
DEPLOY_SSH_KNOWN_HOSTS = (known_hosts запись)
# Получить: ssh-keyscan -H 10.0.0.1
.env.testing для CI
# .env.testing — используется только в CI
APP_ENV=testing
APP_DEBUG=true
APP_KEY=base64:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa=
DB_CONNECTION=mysql
DB_HOST=mysql # Имя сервиса из .gitlab-ci.yml services
DB_PORT=3306
DB_DATABASE=test_db
DB_USERNAME=root
DB_PASSWORD=rootsecret
CACHE_DRIVER=array # Кэш в памяти, не Redis
SESSION_DRIVER=array
QUEUE_DRIVER=sync # Очереди синхронно в тестах
MAIL_DRIVER=log # Email в лог, не отправлять
Структура директорий на сервере (Zero-Downtime)
/var/www/myapp/
├── current/ ← Символическая ссылка на текущий релиз
├── shared/
│ ├── .env ← Конфиг (не в git)
│ └── storage/ ← Загруженные файлы, логи, кэш
└── releases/
├── 20160325120000/ ← Релиз 1
├── 20160326093015/ ← Релиз 2
└── 20160327140530/ ← Релиз 3 (current)
ln -sfn атомарная операция: nginx начинает отдавать файлы нового релиза мгновенно, без задержки.
Результаты после внедрения CI/CD
| Метрика | Ручной деплой | GitLab CI/CD |
|---|---|---|
| Время деплоя | 20–30 мин | 4–6 мин |
| Ошибок в production | 3–5/мес | 0.5/мес |
| Обнаружение поломанных тестов | На продакшне | В CI |
| Откат при ошибке | 15–20 мин | 30 секунд (ln -sfn предыдущий релиз) |
Главный результат: разработчики перестали бояться делать деплои. Частота релизов выросла с 2–3 в месяц до 8–12.