Что такое Canary Deployments и почему это спасёт ваш продакшн?
Вы когда-нибудь выкатывали обновление в полночь, молясь, чтобы ничего не упало? Знакомо. А что, если я скажу, что можно тестировать новый код на живых пользователях, но так, чтобы никто не пострадал? Речь о canary deployments — стратегии развёртывания, при которой новая версия приложения сначала получает лишь малую часть трафика. Если всё хорошо — трафик увеличивается. Если нет — откат за секунды.
В мире .NET и C# это особенно актуально: микросервисы, высокие нагрузки, требования к стабильности. Мы напишем код, настроим canary deployment и разберём реальные примеры. Поехали!
Почему Canary — не просто модное слово?
Традиционный blue-green deployment переключает весь трафик сразу. Rolling update — постепенно, но без тонкого контроля. Canary даёт вам суперсилу: вы выпускаете новую версию, но направляете на неё, скажем, 5% пользователей. Остальные 95% работают на старой стабильной версии. Вы мониторите метрики, ошибки, задержки. Если всё ок — постепенно наращиваете процент до 100. Если нет — просто отключаете канарейку.
Для C#-разработчика это особенно удобно: мы можем использовать встроенные средства ASP.NET Core, Kubernetes, Feature Flags или даже простой балансировщик. Главное — правильная архитектура.
Архитектура Canary Deployment для .NET приложения
Представим, что у нас есть веб-API на ASP.NET Core, которое обрабатывает заказы. Мы хотим выкатить новую логику расчёта скидок. Вместо того чтобы обновлять все поды сразу, мы:
- Запускаем две версии сервиса: stable (v1) и canary (v2).
- Настраиваем балансировщик (например, Nginx или Kubernetes Service) так, чтобы 95% трафика шло на v1, а 5% — на v2.
- Добавляем HTTP-заголовок
X-Canary: trueв ответах canary-версии для идентификации. - Собираем метрики (код 200/500, latency, количество заказов).
- Если за 30 минут ошибок не больше, чем на v1, увеличиваем вес canary до 50%, затем до 100%.
Реализация на C#: от простого к сложному
Способ 1: Canary через HTTP-заголовки и балансировщик
Самый простой путь — использовать слой middleware в ASP.NET Core, который помечает запросы. Но сначала давайте посмотрим, как выглядит код, который отличает canary-версию.
// Startup.cs или Program.cs
public class CanaryMiddleware
{
private readonly RequestDelegate _next;
private readonly string _version;
public CanaryMiddleware(RequestDelegate next, string version)
{
_next = next;
_version = version;
}
public async Task InvokeAsync(HttpContext context)
{
// Добавляем заголовок для идентификации версии
context.Response.Headers.Add("X-App-Version", _version);
await _next(context);
}
}
// Регистрация
app.UseMiddleware<CanaryMiddleware>("v2-canary");
Теперь на уровне балансировщика (например, Nginx) мы решаем, какой процент трафика отправлять на canary-под:
upstream backend {
server app-stable:5000 weight=95;
server app-canary:5000 weight=5;
}
Плюсы: простота. Минусы: нет программного контроля, сложно делать A/B тестирование по пользователям.
Способ 2: Feature Flags в C#
Более гибкий подход — использовать feature flags (фича-флаги). Вы включаете новую логику только для определённой группы пользователей. В ASP.NET Core есть встроенная поддержка:
// Установите пакет Microsoft.FeatureManagement.AspNetCore
// Добавьте в Program.cs
builder.Services.AddFeatureManagement();
// Определите флаг в appsettings.json
{
"FeatureManagement": {
"NewDiscountLogic": false
}
}
// Используйте в контроллере
[ApiController]
[Route("api/[controller]")]
public class DiscountController : ControllerBase
{
private readonly IFeatureManager _featureManager;
public DiscountController(IFeatureManager featureManager)
{
_featureManager = featureManager;
}
[HttpGet("calculate")]
public async Task<IActionResult> Calculate(decimal amount)
{
if (await _featureManager.IsEnabledAsync("NewDiscountLogic"))
{
// Новая логика
var discount = amount * 0.2m;
return Ok(new { discount, version = "canary" });
}
else
{
// Старая логика
var discount = amount * 0.1m;
return Ok(new { discount, version = "stable" });
}
}
}
Теперь, чтобы включить canary для 5% пользователей, используем Contextual Feature Filters. Например, фильтр по ID пользователя:
public class PercentageFilter : IFeatureFilter
{
public Task<bool> EvaluateAsync(FeatureFilterEvaluationContext context)
{
var settings = context.Parameters.Get<PercentageFilterSettings>();
var userId = context.Context?.Request.Headers["X-User-Id"].FirstOrDefault();
if (string.IsNullOrEmpty(userId)) return Task.FromResult(false);
var hash = Math.Abs(userId.GetHashCode()) % 100;
return Task.FromResult(hash < settings.Percentage);
}
}
public class PercentageFilterSettings
{
public int Percentage { get; set; }
}
// Регистрация
builder.Services.AddFeatureManagement()
.AddFeatureFilter<PercentageFilter>();
Теперь canary deployment — это просто изменение процента в конфиге. Без перезапуска!
Способ 3: Kubernetes Canary Deployment для .NET
Если ваш C#-сервис работает в Kubernetes, вы можете использовать нативные механизмы. Создаём два деплоймента:
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api-stable
spec:
replicas: 10
selector:
matchLabels:
app: orders-api
version: stable
template:
metadata:
labels:
app: orders-api
version: stable
spec:
containers:
- name: api
image: myrepo/orders-api:1.0.0
ports:
- containerPort: 8080
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: orders-api-canary
spec:
replicas: 1
selector:
matchLabels:
app: orders-api
version: canary
template:
metadata:
labels:
app: orders-api
version: canary
spec:
containers:
- name: api
image: myrepo/orders-api:2.0.0
ports:
- containerPort: 8080
А теперь — умный Service, который распределяет трафик:
apiVersion: v1
kind: Service
metadata:
name: orders-api
spec:
selector:
app: orders-api
ports:
- port: 80
targetPort: 8080
По умолчанию Service будет отправлять запросы на все поды с меткой app: orders-api. Чтобы управлять весами, используем Istio или Linkerd. Но