Sử Dụng Proxy Để Shadow Traffic Cho Triển Khai Canary
6 tháng 10, 2026
Tại sao cần traffic shadowing với proxy?
Khi triển khai một phiên bản mới (canary) của dịch vụ, việc chỉ định một phần nhỏ traffic thực tế đến phiên bản đó giúp quan sát hành vi thực tế mà không rủi ro ảnh hưởng đến toàn bộ người dùng. Proxy đóng vai trò là lớp trung gian có thể nhân bản request gốc và gửi song song đến cả production và canary, sau đó chỉ trả về response của production cho client.
Lợi ích chính:
- Không làm gián đoạn UX: Client vẫn nhận response từ phiên bản ổn định.
- Dữ liệu thực: Canary nhận request thật, bao gồm header, cookie, payload, giúp test logic nghiệp vụ chính xác.
- Phát hiện sớm: Lỗi 5xx, latency cao, hoặc hành vi bất thường được phát hiện trước khi mở rộng traffic.
Kiến trúc tổng quan
Client → [Proxy Edge] → Production (100% response)
↳→ Canary (shadow, response bị bỏ qua)
Proxy edge có thể là một instance RoProxy chạy ở chế độ sticky session để giữ nguyên kết nối TCP/TLS cho cả hai backend, hoặc một proxy layer tùy chỉnh (NGINX, Envoy, hoặc script Python/Node) thực hiện request duplication.
Chuẩn bị môi trường
- Hai endpoint backend:
https://api.prod.example.comvàhttps://api.canary.example.com. - Ngôn ngữ: Python 3.11+ (thư viện
httpxhỗ trợ async) hoặc Node.js 20+ (undici). - Công cụ quan sát: Prometheus + Grafana để thu metric latency, error rate cho canary.
Cấu hình RoProxy cho sticky session
Trong dashboard RoProxy, tạo một proxy pool với chế độ sticky và TTL 10 phút. Ghi lại proxy_host, proxy_port, username, password. Ví dụ:
proxy_host = "GATEWAY_HOST"
proxy_port = 8080
proxy_user = "myuser"
proxy_pass = "mypassword"
Sticky session đảm bảo cùng một IP được dùng cho cả production và canary trong một request duplication, tránh vấn đề session affinity hoặc rate limit theo IP.
Triển khai request duplication bằng Python (httpx)
import os
import asyncio
import httpx
from typing import Tuple
PROXY = f"http://{os.getenv('PROXY_USER')}:{os.getenv('PROXY_PASS')}@{os.getenv('PROXY_HOST')}:{os.getenv('PROXY_PORT')}"
PROD_URL = "https://api.prod.example.com"
CANARY_URL = "https://api.canary.example.com"
async def forward_request(method: str, path: str, headers: dict, body: bytes) -> httpx.Response:
"""Gửi request đến production và canary song song."""
url_prod = f"{PROD_URL}{path}"
url_canary = f"{CANARY_URL}{path}"
async with httpx.AsyncClient(proxy=PROXY, timeout=30.0, http2=True) as client:
# Tạo request objects
req_prod = client.build_request(method, url_prod, headers=headers, content=body)
req_canary = client.build_request(method, url_canary, headers=headers, content=body)
# Gửi song song
resp_prod, resp_canary = await asyncio.gather(
client.send(req_prod, stream=True),
client.send(req_canary, stream=True),
return_exceptions=True
)
# Xử lý exception cho canary (không làm hỏng production)
if isinstance(resp_canary, Exception):
print(f"[Canary error] {resp_canary}")
else:
# Thu metric canary (latency, status)
latency = resp_canary.elapsed.total_seconds()
status = resp_canary.status_code
# Gửi metric đến Prometheus Pushgateway hoặc OpenTelemetry
record_canary_metric(path, status, latency)
# Đọc body để giải phóng kết nối
await resp_canary.aread()
# Trả về response production cho client
return resp_prod
# Ví dụ dùng trong FastAPI
from fastapi import FastAPI, Request, Response
app = FastAPI()
@app.api_route("/{path:path}", methods=["GET","POST","PUT","DELETE","PATCH","HEAD","OPTIONS"])
async def proxy_endpoint(request: Request, path: str):
body = await request.body()
headers = dict(request.headers)
# Loại bỏ header hop-by-hop
headers.pop("host", None)
headers.pop("content-length", None)
resp = await forward_request(request.method, f"/{path}", headers, body)
return Response(content=await resp.aread(), status_code=resp.status_code, headers=dict(resp.headers))
Giải thích:
httpx.AsyncClientvớiproxytự động áp dụng RoProxy cho cả hai kết nối.asyncio.gathergửi song song,return_exceptions=Trueđảm bảo lỗi canary không làm crash production.- Metric canary được ghi riêng (hàm
record_canary_metricbạn tự triển khai với Prometheus client).
Triển khai tương tự bằng Node.js (undici)
import { ProxyAgent } from 'undici-proxy-agent';
import { request } from 'undici';
import { PrometheusExporter } from '@prometheus/client';
const proxy = new ProxyAgent(`http://${process.env.PROXY_USER}:${process.env.PROXY_PASS}@${process.env.PROXY_HOST}:${process.env.PROXY_PORT}`);
const PROD = 'https://api.prod.example.com';
const CANARY = 'https://api.canary.example.com';
const exporter = new PrometheusExporter({ port: 9464 });
async function forward(req, res) {
const path = req.url;
const method = req.method;
const headers = { ...req.headers };
delete headers.host;
delete headers['content-length'];
const body = await new Promise((resolve) => {
const chunks = [];
req.on('data', c => chunks.push(c));
req.on('end', () => resolve(Buffer.concat(chunks)));
});
const prodReq = request(`${PROD}${path}`, { method, headers, body, dispatcher: proxy });
const canaryReq = request(`${CANARY}${path}`, { method, headers, body, dispatcher: proxy });
const [prodRes, canaryRes] = await Promise.allSettled([prodReq, canaryReq]);
// Xử lý canary metric
if (canaryRes.status === 'fulfilled') {
const latency = Date.now() - canaryRes.value.startTime; // cần custom wrapper để lấy startTime
exporter.registerMetric({
name: 'canary_request_latency_seconds',
type: 'Histogram',
help: 'Latency of shadow requests',
labels: { path, status: canaryRes.value.statusCode }
}).observe(latency / 1000);
canaryRes.value.body.resume(); // consume stream
} else {
console.error('[Canary error]', canaryRes.reason);
}
// Trả về production response
if (prodRes.status === 'fulfilled') {
res.writeHead(prodRes.value.statusCode, prodRes.value.headers);
prodRes.value.body.pipe(res);
} else {
res.writeHead(502);
res.end('Bad gateway');
}
}
// Express example
import express from 'express';
const app = express();
app.use(express.raw({ type: '*/*' }));
app.all('*', forward);
app.listen(8080);
Theo dõi và cảnh báo
- Metric chính:
canary_request_latency_seconds,canary_error_total,canary_status_code. - Dashboard Grafana: Biểu đồ latency p95, error rate theo endpoint.
- Alert rule (Prometheus):
- alert: CanaryHighErrorRate expr: sum(rate(canary_error_total[5m])) by (path) > 0.05 for: 2m labels: severity: warning annotations: summary: "Canary error rate >5% on {{ $labels.path }}"
Khi alert kích hoạt, team có thể rollback canary hoặc điều tra log mà không ảnh hưởng production.
Xử lý các trường hợp đặc biệt
| Trường hợp | Giải pháp |
|---|---|
| Request có body lớn (>10MB) | Bật streaming (stream=True trong httpx, body là readable stream trong Node) để tránh OOM. |
| Authentication token rotation | Đảm bảo proxy forward header Authorization nguyên vẹn; canary cần cùng secret key với production. |
| Idempotency | Chỉ shadow các method an toàn (GET, HEAD, OPTIONS) hoặc những POST có header Idempotency-Key. |
| Rate limit phía backend | Sử dụng sticky session để cùng IP cho cả hai backend, tránh bị chặn do IP khác nhau. |
| TLS mutual auth | Cấu hình RoProxy với cert client (--cert-file, --key-file) và thêm vào httpx.AsyncClient(cert=("cert.pem","key.pem")). |
Best practices
- Bắt đầu nhỏ: Chỉ shadow 1-2% traffic (có thể dùng weighted routing ở load balancer trước proxy).
- Log correlation: Thêm header
X-Request-IDduy nhất cho mỗi request, ghi log cả production và canary để trace. - Automated rollback: Kết hợp với Argo Rollouts hoặc Flagger; khi metric canary vượt ngưỡng, tự động pause rollout.
- Separate proxy pools: Nếu lượng traffic lớn, tách pool residential cho production, datacenter cho canary để tối ưu chi phí.
- Test chaos: Định kỳ inject latency/failure vào canary (via proxy middleware) để kiểm tra resilience.
Kết luận
Sử dụng proxy để shadow traffic là kỹ thuật mạnh mẽ, cho phép team validate canary trong môi trường thực mà không rủi ro. Với RoProxy cung cấp sticky session, rotating IP và hỗ trợ SOCKS5/HTTP, việc triển khai trở nên đơn giản: chỉ cần một proxy edge, một script duplication request (Python hoặc Node), và hệ thống observability. Áp dụng các best practice trên sẽ giúp quy trình CI/CD an toàn, giảm thời gian phát hiện lỗi từ giờ xuống phút.
Bài viết này tập trung vào góc độ traffic shadowing cho canary deployment – một chủ đề chưa được bao phủ trong các bài trước của RoProxy. Hy vọng các ví dụ code và checklist sẽ giúp bạn triển khai ngay hôm nay.