Canary deployments с PHP: как запускать обновления без страха

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Canary deployments с PHP: как запускать обновления без страха

Каждый разработчик знает это чувство: вы выкатываете новую версию на прод, задерживаете дыхание и молитесь, чтобы всё не рухнуло. А если рухнет — вы узнаете об этом первым, когда сервер упадёт под напором тысяч пользователей.

Но есть способ умнее. Canary deployments (канареечные развёртывания) — это техника, при которой новая версия приложения сначала отправляется небольшой группе пользователей, а затем, если всё хорошо, постепенно распространяется на всех. Название пошло от старых шахтёров, которые брали с собой канареек: если птица замолкала — в шахте был газ. В нашем случае «канарейка» — это первые 5-10% трафика, которые проверяют код на прочность.

В этой статье я покажу, как реализовать canary deployments на PHP, используя реальные примеры кода, балансировщик трафика и инструменты мониторинга. Вы узнаете, как обновлять приложение с нулевым временем простоя, быстро откатывать изменения и проводить A/B тестирование без боли.

Почему canary — это не просто модное слово?

Традиционное развёртывание (blue-green или rolling update) работает, но у него есть недостатки:

  • Blue-green требует двойной инфраструктуры — дорого и не всегда оправдано.
  • Rolling update обновляет сервера по одному, но если ошибка в коде — она всё равно добирается до всех.
  • Feature flags хороши, но не решают проблему с производительностью или багами, которые проявляются только под нагрузкой.

Canary deployments решают эти проблемы элегантно: вы пускаете новый код на 5-10% пользователей, следите за метриками (ошибки, latency, CPU) и, если всё ок, увеличиваете процент. Если нет — просто откатываете эти 5%.

Для PHP это особенно актуально, потому что многие проекты работают на shared hosting или VPS без сложного оркестратора. Но даже на обычном сервере с nginx можно настроить canary — и я покажу как.

Архитектура canary deployments для PHP-приложения

Представим, что у нас есть типичное PHP-приложение на Laravel или Symfony, работающее за nginx. Чтобы реализовать canary, нам понадобятся:

  • Две версии приложения — стабильная (stable) и новая (canary).
  • Балансировщик — nginx, который будет направлять часть трафика на canary.
  • Механизм переключения — по кукам, IP-адресу или заголовкам.
  • Мониторинг — чтобы понять, что canary работает нормально.

Вот базовая схема:

Пользователь → nginx (балансировщик) → stable (80% трафика)
                                          → canary (20% трафика)

Но мы можем сделать умнее: например, направлять на canary только тех пользователей, у которых есть определённая cookie. Так мы сможем тестировать на конкретной группе (например, сотрудники компании).

Шаг 1: Настройка nginx для canary

Nginx — идеальный инструмент для canary, потому что он умеет распределять трафик на уровне upstream и проверять заголовки. Вот пример конфигурации:

upstream php_stable {
    server 127.0.0.1:9001;
}

upstream php_canary {
    server 127.0.0.1:9002;
}

server {
    listen 80;
    server_name example.com;

    location / {
        # Если есть cookie canary=1 — отправляем на canary
        if ($http_cookie ~* "canary=1") {
            set $upstream "php_canary";
        }

        # Иначе — рандомно 10% трафика на canary
        if ($upstream = "") {
            set $rand $request_id;
            set $modulo 10;
            if ($rand ~* "^[0-9a-f]{1}$") {
                set $upstream "php_canary";
            }
            if ($upstream = "") {
                set $upstream "php_stable";
            }
        }

        proxy_pass http://$upstream;
        include proxy_params;
    }
}

Здесь мы используем трюк с $request_id (уникальный ID запроса) и берём первый символ. Если это hex-цифра от 0 до 9 — шанс 10/16 ≈ 62%? Нет, это криво. Давайте сделаем честный рандом через PHP.

Лучше оставить логику на стороне приложения, а nginx использовать только для прокси:

upstream php_stable {
    server 127.0.0.1:9001;
}

upstream php_canary {
    server 127.0.0.1:9002;
}

server {
    listen 80;

    location / {
        # Проксируем на разные порты в зависимости от заголовка X-Canary
        proxy_pass http://$http_x_canary;
    }
}

Но это небезопасно — клиент может подменить заголовок. Поэтому лучше использовать куки или IP.

Шаг 2: Логика canary на стороне PHP

Самый гибкий способ — управлять canary из PHP. Мы создаём middleware, который решает, на какую версию отправить пользователя. Но есть нюанс: если приложение уже запущено, перенаправить запрос на другой порт из PHP нельзя. Поэтому мы делаем хитрее — запускаем два процесса PHP-FPM на разных портах, а nginx решает, куда направить, на основе cookie.

Вот PHP-код, который устанавливает cookie для canary:

<?php

class CanaryService
{
    private float $canaryPercentage;
    private string $canaryCookieName = 'canary_test';
    private int $cookieLifetime = 3600; // 1 час

    public function __construct(float $canaryPercentage = 10.0)
    {
        $this->canaryPercentage = $canaryPercentage;
    }

    /**
     * Проверяет, должен ли пользователь попасть на canary-версию.
     * Если да — устанавливает cookie.
     */
    public function assignCanary(): bool
    {
        // Если уже есть cookie — используем её
        if (isset($_COOKIE[$this->canaryCookieName])) {
            return $_COOKIE[$this->canaryCookieName] === '1';
        }

        // Иначе — рандом
        $isCanary = (mt_rand(1, 100) <= $this->canaryPercentage);

        if ($isCanary) {
            setcookie($this->canaryCookieName, '1', time() + $this->cookieLifetime, '/', '', true, true);
        } else {
            setcookie($this->canaryCookieName, '0', time() + $this->cookieLifetime, '/', '', true, true);
        }

        return $isCanary;
    }

    /**
     * Принудительно включает canary для текущего пользователя (например, для тестировщика).
     */
    public function forceCanary(): void
    {
        setcookie($this->canaryCookieName, '1', time() + $this->cookieLifetime, '/', '', true, true);
    }

    /**
     * Отключает canary для пользователя.
     */
    public function removeCanary(): void
    {
        setcookie($this->canaryCookieName, '', time() -                

Блоги

Рекомендаций по книгам