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() -