Jump to content
View in the app

A better way to browse. Learn more.

T.M.I IThub

A full-screen app on your home screen with push notifications, badges and more.

To install this app on iOS and iPadOS
  1. Tap the Share icon in Safari
  2. Scroll the menu and tap Add to Home Screen.
  3. Tap Add in the top-right corner.
To install this app on Android
  1. Tap the 3-dot menu (⋮) in the top-right corner of the browser.
  2. Tap Add to Home screen or Install app.
  3. Confirm by tapping Install.

CI/CD: от "фтп в продакшн" до zero-downtime deploy за 4 минуты

(0 reviews)

Это история эволюции. Начнём с того момента, когда деплой выглядел так: "Ваня, выгрузи файлики на сервер через FileZilla". И закончим тем, что у нас сейчас — полностью автоматический pipeline с тестами, security checks, zero-downtime deploy и автоматическим rollback.


Эра FTP (тёмные времена)

Просто знайте: когда я пришёл в эту компанию, деплой выглядел следующим образом:

  1. Разработчик локально делал изменения

  2. Открывал FileZilla

  3. Перетаскивал папку app/ на сервер

  4. Молился

Это было в 2019 году. Не в 2009, а в 2019. Такое ещё встречается.


Шаг 1: Git + простой CI

# .gitlab-ci.yml v1.0 — наивная версия
stages:
  - test
  - deploy

test:
  stage: test
  script:
    - composer install
    - php vendor/bin/phpunit

deploy:
  stage: deploy
  script:
    - ssh deploy@production "cd /var/www/app && git pull && composer install"
  only:
    - main

git pull на продакшне — это тоже не очень хорошая идея, но это следующий шаг.


Шаг 2: Деплой через rsync + atomic switch

#!/bin/bash
set -euo pipefail

DEPLOY_PATH="/var/www"
RELEASES_PATH="$DEPLOY_PATH/releases"
CURRENT_LINK="$DEPLOY_PATH/current"
RELEASE_ID=$(date +%Y%m%d_%H%M%S)
RELEASE_PATH="$RELEASES_PATH/$RELEASE_ID"

mkdir -p "$RELEASE_PATH"

rsync -az --exclude='.git' --exclude='vendor' --exclude='writable' \
    ./ "$RELEASE_PATH/"

ln -sf "$SHARED_PATH/writable" "$RELEASE_PATH/writable"
ln -sf "$SHARED_PATH/.env" "$RELEASE_PATH/.env"

cd "$RELEASE_PATH"
composer install --no-dev --optimize-autoloader --no-interaction

php spark migrate --no-interaction

# Атомарное переключение симлинка — zero downtime!
ln -sfn "$RELEASE_PATH" "$CURRENT_LINK"
nginx -s reload

ls -dt "$RELEASES_PATH"/* | tail -n +6 | xargs rm -rf

echo "

Deploy $RELEASE_ID complete"

Rollback:

ls -dt /var/www/releases/* | head -2 | tail -1 | xargs -I{} ln -sfn {} /var/www/current
nginx -s reload

Шаг 3: Docker + Kubernetes

Dockerfile для PHP 8.3 + CI4:

FROM composer:2.7 AS vendor-builder

WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install --no-dev --no-scripts --no-autoloader --prefer-dist

COPY . .
RUN composer dump-autoload --optimize --classmap-authoritative

FROM php:8.3-fpm-alpine AS production

RUN apk add --no-cache libzip-dev icu-dev \
    && docker-php-ext-install pdo_mysql zip intl opcache \
    && pecl install redis igbinary \
    && docker-php-ext-enable redis igbinary

COPY docker/php/php.ini /usr/local/etc/php/php.ini
COPY docker/php/www.conf /usr/local/etc/php-fpm.d/www.conf

WORKDIR /var/www/html

COPY --from=vendor-builder /app/vendor ./vendor
COPY --from=vendor-builder /app/app ./app
COPY --from=vendor-builder /app/system ./system
COPY --from=vendor-builder /app/public ./public
COPY --from=vendor-builder /app/spark ./spark

HEALTHCHECK --interval=10s --timeout=3s --retries=3 \
    CMD php-fpm -t || exit 1

EXPOSE 9000
USER www-data
CMD ["php-fpm"]

GitLab CI/CD — финальная версия:

stages:
  - validate
  - test
  - security
  - build
  - deploy-staging
  - integration-test
  - deploy-production
  - verify

variables:
  DOCKER_IMAGE: $CI_REGISTRY_IMAGE:$CI_COMMIT_SHORT_SHA

lint:
  stage: validate
  script:
    - find app/ -name "*.php" -exec php -l {} \;
    - composer validate --strict

unit-tests:
  stage: test
  services:
    - mysql:8.0
    - redis:7.0-alpine
  script:
    - php spark migrate --env=testing
    - vendor/bin/phpunit --coverage-min=70

sast:
  stage: security
  script:
    - vendor/bin/psalm --no-cache
    - composer audit --no-dev

build-image:
  stage: build
  script:
    - docker build --target production -t $DOCKER_IMAGE .
    - docker push $DOCKER_IMAGE

deploy-production:
  stage: deploy-production
  when: manual  # Требует ручного подтверждения!
  script:
    - kubectl set image deployment/php-app php-fpm=$DOCKER_IMAGE -n production
    - kubectl rollout status deployment/php-app -n production --timeout=10m

post-deploy-verify:
  stage: verify
  script:
    - sleep 60
    - php spark synthetic:run --suite=smoke --env=production
    - |
      ERROR_RATE=$(curl -s "http://prometheus/api/v1/query?query=rate(http_errors_total[5m])" | jq '.data.result[0].value[1]')
      if (( $(echo "$ERROR_RATE > 0.05" | bc -l) )); then
        echo "Error rate too high! Auto-rollback!"
        kubectl rollout undo deployment/php-app -n production
        exit 1
      fi

Итого: что получили

Метрика

FTP era

Git + rsync

Docker + k8s

Время деплоя

5-40 мин

3-8 мин

2-4 мин

Downtime при деплое

30s-5min

0

0

Rollback время

10-30 мин

2 мин

45 секунд

Деплоев в день

1-2

5-10

20-30

Инцидентов из-за деплоя

2-3/мес

0-1/мес

~0

Больше деплоев в день — это не потому что мы хаотичнее. Маленькие частые деплои безопаснее больших редких. Каждый PR идёт в прод в тот же день. Это называется continuous delivery. И это меняет жизнь. 🚀


0 Comments

Recommended Comments

There are no comments to display.

Configure browser push notifications

Chrome (Android)
  1. Tap the lock icon next to the address bar.
  2. Tap Permissions → Notifications.
  3. Adjust your preference.
Chrome (Desktop)
  1. Click the padlock icon in the address bar.
  2. Select Site settings.
  3. Find Notifications and adjust your preference.