Всем привет, меня зовут Стас, я техлид в Mish Product Lab.
Тема возникла не просто так: внутри команды у нас было немало споров и дискуссий о том, какой инструмент для проксирования и терминации SSL лучше использовать в различных ситуациях. Изначально все наши гипотезы были основаны больше на личных предпочтениях, чем на реальных данных. Мы долго спорили, надеясь, что истина будет где-то рядом с нашими любимыми решениями. Но в итоге пришли к выводу, что единственный способ получить действительно объективный ответ — это протестировать и сравнить различные варианты на практике.
Именно так родилась идея провести сравнительный анализ производительности HAProxy, Envoy, Nginx, Caddy и Traefik с поддержкой SSL/TLS. Мы хотели понять, какой из инструментов «из коробки» предоставляет наилучшую производительность и минимальные накладные расходы, особенно при обработке SSL-трафика, который, как известно, требует дополнительных ресурсов из-за шифрования и дешифрования.
Важно подчеркнуть, что наша задача не заключалась в поиске идеального или максимально оптимизированного решения. Для глубоких и тонких настроек всегда лучше обратиться к DevOps-инженерам, которые обладают необходимыми компетенциями и понимают нюансы каждого инструмента. Нашей целью было скорее провести «полевые испытания» и составить заметки, которые могли бы помочь нам самим и будущим поколениям разработчиков быстрее принимать решения на основе хотя бы приблизительных ориентиров.
Итак, мы готовы поделиться нашими наблюдениями и результатами тестов.
Работаем с тестовыми стендами
Для обеспечения объективности и прозрачности тестирования мы выбрали три отдельных стенда, каждый из которых отвечал за свою часть сценария нагрузки. Разделение ролей стендов было принципиально важным для минимизации возможных взаимных влияний и искажений результатов. Это позволило максимально точно зафиксировать поведение каждого инструмента и компонента инфраструктуры.
Все тестовые стенды были размещены в одной сети, что гарантировало стабильную и предсказуемую сеть без значительных задержек. Никаких настроек ядра, голые системы.
-
Тестовый стенд 1 (K6) предназначался для генерации и управления трафиком при помощи инструмента K6. Его ресурсы (32 vCPU, 128 ГБ RAM) обеспечивали возможность генерировать существенную нагрузку и давала нам уверенность в том, что сам инструмент генерации не станет узким местом.
-
Тестовый стенд 2 (Web) был основным объектом тестирования. Здесь были установлены различные инструменты для проксирования (HAProxy, Envoy, Nginx, Caddy, Traefik) и мониторинг штатными средствами хостера. Это позволило нам не только протестировать производительность каждого решения, но и подробно отслеживать потребление ресурсов. Ресурсы 32 vCPU, 128 ГБ RAM.
-
Тестовый стенд 3 (Backend) выполнял роль типичного приложения (backend), написанного на Golang, которое должно было принимать и обрабатывать запросы, проходящие через прокси. Ресурсы 8 vCPU, 32 ГБ RAM.
Такое разделение ролей и ресурсов дало возможность получить максимально чистые и объективные данные о производительности каждого инструмента.
Работаем с бэкэндом
Сервис делали на Golang, для обработки запросов использовали fasthttp — никаких излишеств.
package main
import (
"fmt"
"log"
"github.com/valyala/fasthttp"
)
func main() {
fmt.Println("Listening on :8080")
requestHandler := func(ctx *fasthttp.RequestCtx) {
switch string(ctx.Path()) {
case "/":
handleRoot(ctx)
default:
ctx.Error("Unsupported path", fasthttp.StatusNotFound)
}
}
if err := fasthttp.ListenAndServe(":8080", requestHandler); err != nil {
log.Fatalf("Error in ListenAndServe: %s", err)
}
}
func handleRoot(ctx *fasthttp.RequestCtx) {
ctx.SetStatusCode(fasthttp.StatusOK)
ctx.SetBodyString("hello world")
}
Запускаем стресс-тест на этот сервис: 50 000 rps, 1 минута
K6 (генерация трафика и проверка ответа, http code и body):
import http from 'k6/http';
import { check } from 'k6';
export let options = {
scenarios: {
constant_request_rate: {
executor: 'constant-arrival-rate',
rate: 50000,
timeUnit: '1s',
duration: '60s',
preAllocatedVUs: 1000,
maxVUs: 100000,
},
},
};
export default function () {
let res = http.get('http://10.0.0.8:8080');
check(res, {
'status is 200': (r) => r.status === 200,
'body contains expected content': (r) => typeof r.body === 'string' && r.body.includes('hello world'),
});
}
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✓ 'rate>=0.95' rate=100.00%
http_req_duration
✓ 'p(95)<500' p(95)=387.29µs
http_req_failed
✓ 'rate<0.01' rate=0.00%
█ TOTAL RESULTS
checks_total.......................: 5993052 99880.644605/s
checks_succeeded...................: 100.00% 5993052 out of 5993052
checks_failed......................: 0.00% 0 out of 5993052
✓ status is 200
✓ body contains expected content
HTTP
http_req_duration.......................................................: avg=283.17µs min=110.92µs med=234.52µs max=235.41ms p(90)=324.36µs p(95)=387.29µs
{ expected_response:true }............................................: avg=283.17µs min=110.92µs med=234.52µs max=235.41ms p(90)=324.36µs p(95)=387.29µs
http_req_failed.........................................................: 0.00% 0 out of 2996526
http_reqs...............................................................: 2996526 49940.322303/s
EXECUTION
dropped_iterations......................................................: 3476 57.931271/s
iteration_duration......................................................: avg=349.05µs min=142.88µs med=277.87µs max=1.06s p(90)=374.7µs p(95)=453.85µs
iterations..............................................................: 2996526 49940.322303/s
vus.....................................................................: 15 min=10 max=183
vus_max.................................................................: 1044 min=1044 max=1044
NETWORK
data_received...........................................................: 438 MB 7.3 MB/s
data_sent...............................................................: 246 MB 4.1 MB/s
Результат — тест выполнен успешно.
Максимальная задержка — max=47.52ms
Пропускная способность — 49964.433616/s
Настройка и тестирование проксирования
K6 (генерация трафика и проверка ответа, http code и body):
import http from 'k6/http';
import { check } from 'k6';
export let options = {
scenarios: {
constant_request_rate: {
executor: 'constant-arrival-rate',
rate: 50000, // количество запросов в секунду
timeUnit: '1s', // единица времени для rate
duration: '60s', // длительность теста
preAllocatedVUs: 1000, // количество предварительно выделенных виртуальных пользователей
maxVUs: 100000, // максимальное количество VU, которое может быть задействовано
},
},
thresholds: {
checks: ['rate>=0.95'],
http_req_failed: ['rate<0.01'],
http_req_duration: ['p(95)<500'],
},
};
export default function () {
let res = http.get('https://test-backend.mish.design');
check(res, {
'status is 200': (r) => r.status === 200,
'body contains expected content': (r) => typeof r.body === 'string' && r.body.includes('hello world'),
});
}
Получаем сертификат:
sudo apt install certbot
sudo certbot certonly --standalone -d test-backend.mish.design
Сертификаты появятся тут:
/etc/letsencrypt/live/test-backend.mish.design/fullchain.pem
/etc/letsencrypt/live/test-backend.mish.design/privkey.pem
Теперь совместим их — это нужно для HAProxy:
cd /etc/letsencrypt/live/test-backend.mish.design/
cat fullchain.pem privkey.pem > haproxy.pem
HAProxy
Начать решили именно с HAProxy, так как он позволяет максимально быстро и эффективно протестировать работоспособность архитектуры без сложной предварительной настройки инфраструктуры.
docker-compose.yml
-
HAProxy запускается через Docker Compose, что позволяет легко воспроизводить окружение на разных серверах и машинах разработчиков.
-
Порты 80 и 443 прокидываются наружу, то есть сервис явно ориентирован на обработку HTTP и HTTPS-запросов.
-
SSL-сертификат Let’s Encrypt подключается напрямую через volume, таким образом сервис сразу готов к работе в защищённом режиме (HTTPS).
services:
haproxy:
image: haproxy:latest
container_name: haproxy
ports:
- "80:80"
- "443:443"
volumes:
- ./haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg:ro
- /etc/letsencrypt/live/test-backend.mish.design/haproxy.pem:/usr/local/etc/haproxy/certs/haproxy.pem:ro
haproxy.cfg
HAProxy настроен в режиме http, и весь трафик, поступающий на порт 443, перенаправляется в backend-сервис, указанный в конфигурации (10.0.0.8:8080).
defaults
mode http
frontend main
bind *:443 ssl crt /usr/local/etc/haproxy/certs/haproxy.pem
default_backend test_backend
backend test_backend
server test_backend_server 10.0.0.8:8080 check
Запускаем созданный выше сценарий
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✓ 'rate>=0.95' rate=100.00%
http_req_duration
✓ 'p(95)<500' p(95)=1.16ms
http_req_failed
✓ 'rate<0.01' rate=0.00%
█ TOTAL RESULTS
checks_total.......................: 5967620 99455.517508/s
checks_succeeded...................: 100.00% 5967620 out of 5967620
checks_failed......................: 0.00% 0 out of 5967620
✓ status is 200
✓ body contains expected content
HTTP
http_req_duration.......................................................: avg=759.38µs min=330.64µs med=571.94µs max=1.07s p(90)=861.18µs p(95)=1.16ms
{ expected_response:true }............................................: avg=759.38µs min=330.64µs med=571.94µs max=1.07s p(90)=861.18µs p(95)=1.16ms
http_req_failed.........................................................: 0.00% 0 out of 2983810
http_reqs...............................................................: 2983810 49727.758754/s
EXECUTION
dropped_iterations......................................................: 16192 269.8536/s
iteration_duration......................................................: avg=984.55µs min=360.44µs med=643.34µs max=1.36s p(90)=968.37µs p(95)=1.35ms
iterations..............................................................: 2983810 49727.758754/s
vus.....................................................................: 34 min=28 max=357
vus_max.................................................................: 1293 min=1293 max=1293
NETWORK
data_received...........................................................: 374 MB 6.2 MB/s
data_sent...............................................................: 108 MB 1.8 MB/s
Нагрузка на систему


Основные выводы:
CPU
-
Загрузка до ~900% (9 ядер), стабильна весь тест → CPU справился.
K6 Test Summary
-
RPS (успешные): 49,728 / сек
-
Ошибки: 0% (http_req_failed = 0.00%)
-
Дропнутые итерации: 16,192 (≈0.5%)
-
Время ответа p(95): 1.16 ms
-
Макс. задержка: 1.07s
-
Объём данных:
-
Получено: 374 MB (6.2 MB/s)
-
Отправлено: 108 MB (1.8 MB/s)
-
-
VUs использовано: 28–357
-
Макс. допустимо: 1293 VUs
🟢 Отличная производительность, минимальные задержки, всё стабильно.
Envoy
Высокопроизводительный прокси-сервер и балансировщик нагрузки, разработанный компанией Lyft, поддерживающий динамическое конфигурирование, observability и множество современных протоколов.
docker-compose.yml
-
Envoy разворачивается через Docker Compose для простоты повторяемости окружения.
-
Порты 80 и 443 открываются для обработки входящего трафика.
-
Используется сертификат Let’s Encrypt, напрямую подключённый в конфигурацию.
services:
envoy:
image: envoyproxy/envoy-dev:latest
container_name: envoy
environment:
- "ENVOY_UID=0"
volumes:
- /etc/letsencrypt:/etc/certs:ro
- ./envoy.yaml:/etc/envoy/envoy.yaml
ports:
- "80:80"
- "443:443"
envoy.yaml
-
Запросы, поступающие на домен test-backend.mish.design, перенаправляются на backend-сервис по адресу 10.0.0.4:8080 с балансировкой по алгоритму round-robin.
-
Envoy автоматически терминирует TLS-соединения и маршрутизирует трафик по HTTP.
static_resources:
secrets:
- name: server_cert
tls_certificate:
certificate_chain:
filename: /etc/certs/live/test-backend.mish.design/fullchain.pem
private_key:
filename: /etc/certs/live/test-backend.mish.design/privkey.pem
listeners:
- name: back_listener
address:
socket_address:
address: 0.0.0.0
port_value: 443
filter_chains:
- filters:
- name: envoy.filters.network.http_connection_manager
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
codec_type: AUTO
stat_prefix: ingress_http
upgrade_configs:
- upgrade_type: websocket
route_config:
name: local_route
virtual_hosts:
- name: main
domains:
- "test-backend.mish.design"
routes:
- match:
prefix: "/"
route:
cluster: back
http_filters:
- name: envoy.filters.http.router
typed_config:
"@type": type.googleapis.com/envoy.extensions.filters.http.router.v3.Router
transport_socket:
name: envoy.transport_sockets.tls
typed_config:
"@type": type.googleapis.com/envoy.extensions.transport_sockets.tls.v3.DownstreamTlsContext
common_tls_context:
tls_certificate_sds_secret_configs:
- name: server_cert
clusters:
- name: back
connect_timeout: 5s
type: STRICT_DNS
lb_policy: ROUND_ROBIN
load_assignment:
cluster_name: back
endpoints:
- lb_endpoints:
- endpoint:
address:
socket_address:
address: 10.0.0.4
port_value: 8080
Запускаем созданный выше сценарий
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✓ 'rate>=0.95' rate=100.00%
http_req_duration
✓ 'p(95)<500' p(95)=3.34ms
http_req_failed
✓ 'rate<0.01' rate=0.00%
█ TOTAL RESULTS
checks_total.......................: 5970208 99497.954679/s
checks_succeeded...................: 100.00% 5970208 out of 5970208
checks_failed......................: 0.00% 0 out of 5970208
✓ status is 200
✓ body contains expected content
HTTP
http_req_duration.......................................................: avg=1.38ms min=421.73µs med=1ms max=131.98ms p(90)=2.11ms p(95)=3.34ms
{ expected_response:true }............................................: avg=1.38ms min=421.73µs med=1ms max=131.98ms p(90)=2.11ms p(95)=3.34ms
http_req_failed.........................................................: 0.00% 0 out of 2985104
http_reqs...............................................................: 2985104 49748.977339/s
EXECUTION
dropped_iterations......................................................: 14895 248.236248/s
iteration_duration......................................................: avg=1.63ms min=454.49µs med=1.08ms max=1.15s p(90)=2.33ms p(95)=3.87ms
iterations..............................................................: 2985104 49748.977339/s
vus.....................................................................: 59 min=49 max=291
vus_max.................................................................: 1262 min=1246 max=1262
NETWORK
data_received...........................................................: 597 MB 9.9 MB/s
data_sent...............................................................: 344 MB 5.7 MB/s
Нагрузка на систему


Основные выводы:
CPU
-
Загрузка до ~1600% → использовано до 16 ядер, держалось стабильно.
K6 Test Summary
-
RPS (успешные): 49,749 / сек
-
Ошибки: 0%
-
Дропнутые итерации: 14,895 (≈0.5%)
-
Время ответа p(95): 3.34 ms
-
Макс. задержка: 132 ms
-
Объём данных:
-
Получено: 597 MB (9.9 MB/s)
-
Отправлено: 344 MB (5.7 MB/s)
-
-
VUs использовано: 49–291
-
Макс. допустимо: 1262 VUs
🟢 Отличная производительность, минимальные задержки, всё стабильно.
Traefik
Современный обратный прокси и балансировщик нагрузки с встроенной поддержкой автоматической выдачи и обновления SSL-сертификатов через Let’s Encrypt. Он разработан для динамической работы с контейнерными средами, такими как Docker и Kubernetes.
docker-compose.yml
-
Запуск через Docker Compose — Traefik работает как отдельный сервис в контейнере.
-
Порты 80 и 443 — открыт для приёма HTTP и HTTPS-трафика.
-
Сертификаты Let’s Encrypt — полностью автоматическая выдача и хранение сертификатов.
services:
traefik:
image: traefik:latest
container_name: traefik
ports:
- "80:80"
- "443:443"
volumes:
- /var/run/docker.sock:/var/run/docker.sock
- ./traefik_data:/etc/traefik
- ./traefik_data/dynamic_conf.yml:/etc/traefik/dynamic_conf.yml
command:
- "--log.level=ERROR"
- "--accesslog=false"
- "--api.dashboard=false"
- "--providers.docker=true"
- "--providers.docker.exposedbydefault=false"
- "--providers.file.filename=/etc/traefik/dynamic_conf.yml"
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--certificatesresolvers.myresolver.acme.httpchallenge=true"
- "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
- "--certificatesresolvers.myresolver.acme.email=example@yandex.com"
- "--certificatesresolvers.myresolver.acme.storage=/etc/traefik/acme.json"
dynamic_conf.yml
-
Запросы на test-backend.mish.design по HTTPS идут на backend-сервис по адресу http://10.0.0.8:8080.
-
Используется entryPoint websecure (порт 443).
-
Балансировка выполняется на уровне Traefik.
http:
routers:
myrouter:
rule: "Host(`test-backend.mish.design`)"
entryPoints:
- websecure
tls:
certResolver: myresolver
service: myservice
services:
myservice:
loadBalancer:
servers:
- url: "http://10.0.0.4:8080"
Запускаем созданный выше сценарий
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✗ 'rate>=0.95' rate=67.15%
http_req_duration
✗ 'p(95)<500' p(95)=8.46s
http_req_failed
✗ 'rate<0.01' rate=32.84%
█ TOTAL RESULTS
checks_total.......................: 723606 9317.72162/s
checks_succeeded...................: 67.15% 485906 out of 723606
checks_failed......................: 32.84% 237700 out of 723606
✗ status is 200
↳ 67% — ✓ 242953 / ✗ 118850
✗ body contains expected content
↳ 67% — ✓ 242953 / ✗ 118850
HTTP
http_req_duration.......................................................: avg=2.11s min=0s med=70.12ms max=32.98s p(90)=6.25s p(95)=8.46s
{ expected_response:true }............................................: avg=2.47s min=622.32µs med=289.6ms max=32.98s p(90)=6.68s p(95)=9.22s
http_req_failed.........................................................: 32.84% 118850 out of 361803
http_reqs...............................................................: 361803 4658.86081/s
EXECUTION
dropped_iterations......................................................: 2088749 26896.379681/s
iteration_duration......................................................: avg=4.42s min=1.1ms med=358.05ms max=1m0s p(90)=10.28s p(95)=27.16s
iterations..............................................................: 361803 4658.86081/s
vus.....................................................................: 26 min=26 max=29182
vus_max.................................................................: 29243 min=2532 max=29243
NETWORK
data_received...........................................................: 126 MB 1.6 MB/s
data_sent...............................................................: 24 MB 308 kB/s
Нагрузка на систему


Основные выводы:
CPU
-
Загрузка до ~3000% → задействовано ~30 ядер.
Network Traffic
-
Периоды падения, из-за ошибок и дропов.
K6 Test Summary (Traefik)
-
Успешные запросы: 4,659 RPS (в 10 раз ниже цели)
-
Ошибки: ❌ 32.84%
-
Дропы: ❌ 2,088,749 итераций (‼️ огромный уровень отказов)
-
p(95) ответа: ❌ 8.46 сек
-
Максимальная задержка: 32.98 сек (!)
-
Передано:
-
Получено: 126 MB (1.6 MB/s)
-
Отправлено: 24 MB (308 KB/s)
-
-
VUs: до 29,243 → рост числа VUs указывает на тяжёлую деградацию под нагрузкой
❗ Вывод:
-
Traefik не справился с 50k RPS.
-
Наблюдаются:
-
Массовые дропы итераций
-
Высокие задержки
-
-
Требуется оптимизация, либо он не подходит для такого уровня нагрузки в текущей конфигурации.
Nginx
Классический веб-сервер и обратный прокси, известный своей стабильностью, высокой производительностью и минимальным потреблением ресурсов. Один из самых популярных инструментов для терминации TLS, балансировки нагрузки и проксирования трафика
docker-compose.yml
-
Запуск через Docker Compose — контейнер с NGINX разворачивается быстро и удобно.
-
Открыты порты 80 (HTTP) и 443 (HTTPS) для обработки входящего трафика.
-
Подключены сертификаты от Let’s Encrypt для обеспечения HTTPS.
services:
nginx:
image: nginx:latest
volumes:
- ./default.conf:/etc/nginx/nginx.conf
- /etc/letsencrypt/live/test-backend.mish.design/fullchain.pem:/etc/letsencrypt/live/test-backend.mish.design/fullchain.pem:ro
- /etc/letsencrypt/live/test-backend.mish.design/privkey.pem:/etc/letsencrypt/live/test-backend.mish.design/privkey.pem:ro
ports:
- "80:80"
- "443:443"
default.conf
-
Прописаны все основные заголовки для правильной передачи оригинального IP и схемы клиента на backend.
-
worker_processes auto; — количество воркеров подстраивается под доступные ядра.
-
worker_rlimit_nofile 100000; — увеличен лимит на количество открытых файлов (важно при высоких нагрузках).
-
worker_connections 5000; и multi_accept on; — рассчитано на большое количество одновременных соединений.
-
Используется epoll — эффективный режим для Linux-систем с высоким количеством соединений.
worker_processes auto;
worker_rlimit_nofile 300000;
events {
worker_connections 5000;
multi_accept on;
use epoll;
}
http {
server {
listen 443 ssl;
server_name test-backend.mish.design;
ssl_certificate /etc/letsencrypt/live/test-backend.mish.design/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/test-backend.mish.design/privkey.pem;
location / {
proxy_pass http://10.0.0.4:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
Запускаем созданный выше сценарий
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✗ 'rate>=0.95' rate=89.79%
http_req_duration
✗ 'p(95)<500' p(95)=3.82s
http_req_failed
✗ 'rate<0.01' rate=10.20%
█ TOTAL RESULTS
checks_total.......................: 1649228 18316.01633/s
checks_succeeded...................: 89.79% 1480978 out of 1649228
checks_failed......................: 10.20% 168250 out of 1649228
✗ status is 200
↳ 89% — ✓ 740489 / ✗ 84125
✗ body contains expected content
↳ 89% — ✓ 740489 / ✗ 84125
HTTP
http_req_duration.......................................................: avg=1.72s min=0s med=1.73s max=30.09s p(90)=3.32s p(95)=3.82s
{ expected_response:true }............................................: avg=1.91s min=4.27ms med=1.81s max=30.09s p(90)=3.42s p(95)=3.86s
http_req_failed.........................................................: 10.20% 84125 out of 824614
http_reqs...............................................................: 824614 9158.008165/s
EXECUTION
dropped_iterations......................................................: 1154367 12820.183033/s
iteration_duration......................................................: avg=1.81s min=14.05ms med=1.81s max=30.09s p(90)=3.4s p(95)=3.9s
iterations..............................................................: 824614 9158.008165/s
vus.....................................................................: 1 min=1 max=29297
vus_max.................................................................: 29354 min=2620 max=29354
NETWORK
data_received...........................................................: 231 MB 2.6 MB/s
data_sent...............................................................: 95 MB 1.1 MB/s
Нагрузка на систему


Основные выводы:
CPU
-
Загрузка до ~3000% → задействовано ~30 ядер.
Network Traffic
-
Трафик стабильнее, чем у Traefik, но ниже, чем у Envoy.
K6 Test Summary (NGINX)
-
Успешные RPS: 9,158 / сек (х2 выше Traefik, но всё ещё ниже цели)
-
Ошибки: ❌ 10.2% (http_req_failed)
-
Дропы: ❌ 1,154,367 итераций
-
p(95) задержка: ❌ 3.82 сек, медиана 1.73 сек
-
Макс. задержка: 30 сек (таймаут)
-
Передано:
-
Получено: 231 MB (2.6 MB/s)
-
Отправлено: 95 MB (1.1 MB/s)
-
-
VUs: до 29,354 → высокая нагрузка на уровне Traefik.
⚠️ Вывод:
-
NGINX работает лучше Traefik, но сильно уступает HAProxy и Envoy.
-
Присутствуют таймауты, высокие задержки и дропы.
Caddy
Это современный веб-сервер и обратный прокси с нативной поддержкой HTTPS и автоматического получения сертификатов от Let’s Encrypt. Главные преимущества Caddy — простота конфигурации, автоматическое управление TLS и минимальные усилия по обслуживанию.
docker-compose.yml
-
Запуск через Docker Compose — контейнерный запуск с удобным управлением жизненным циклом.
-
Открыты порты 80 и 443 (включая 443/udp для HTTP/3).
-
Используется Caddyfile — минималистичная и читаемая конфигурация.
services:
caddy:
image: caddy:latest
container_name: caddy
hostname: caddy
ports:
- "80:80"
- "443:443"
- "443:443/udp"
restart: unless-stopped
volumes:
- caddy_data:/data
- caddy_config:/config
- ./Caddyfile:/etc/caddy/Caddyfile
logging:
driver: "json-file"
options:
max-size: "200k"
max-file: "10"
cap_add:
- NET_ADMIN
volumes:
caddy_data:
caddy_config:
Caddyfile
-
Caddy автоматически берёт и обновляет сертификаты Let’s Encrypt без дополнительных скриптов и настроек.
-
Логирование настроено на уровень ERROR для минимальной нагрузки на диск.
-
Проксирование запросов на backend-сервис по адресу http://10.0.0.4:8080.
test-backend.mish.design {
log {
level ERROR
}
reverse_proxy http://10.0.0.4:8080
}
Запускаем созданный выше сценарий
scenarios: (100.00%) 1 scenario, 100000 max VUs, 1m30s max duration (incl. graceful stop):
* constant_request_rate: 50000.00 iterations/s for 1m0s (maxVUs: 1000-100000, gracefulStop: 30s)
█ THRESHOLDS
checks
✗ 'rate>=0.95' rate=64.12%
http_req_duration
✗ 'p(95)<500' p(95)=17.03s
http_req_failed
✗ 'rate<0.01' rate=35.87%
█ TOTAL RESULTS
checks_total.......................: 603996 6708.595341/s
checks_succeeded...................: 64.12% 387296 out of 603996
checks_failed......................: 35.87% 216700 out of 603996
✗ status is 200
↳ 64% — ✓ 193648 / ✗ 108350
✗ body contains expected content
↳ 64% — ✓ 193648 / ✗ 108350
HTTP
http_req_duration.......................................................: avg=4.29s min=0s med=377.57ms max=34.92s p(90)=12.88s p(95)=17.03s
{ expected_response:true }............................................: avg=5.03s min=917.98µs med=2.73s max=31.94s p(90)=12.95s p(95)=16.93s
http_req_failed.........................................................: 35.87% 108350 out of 301998
http_reqs...............................................................: 301998 3354.29767/s
EXECUTION
dropped_iterations......................................................: 1861627 20677.127362/s
iteration_duration......................................................: avg=5.14s min=999.47µs med=477.38ms max=56.43s p(90)=16.17s p(95)=19.59s
iterations..............................................................: 301998 3354.29767/s
vus.....................................................................: 1 min=1 max=28943
vus_max.................................................................: 29170 min=2432 max=29170
NETWORK
data_received...........................................................: 104 MB 1.2 MB/s
data_sent...............................................................: 23 MB 250 kB/s
Нагрузка на систему


Основные выводы:
CPU
-
Загрузка до ~3000% → задействовано ~30 ядер.
Network Traffic
-
Пик входящего трафика: ~6–7 MBps, исходящего: ~3–4 MBps
-
Колебания присутствуют, особенно в начале — возможны ошибки или нестабильная отдача.
K6 Test Summary (Caddy)
-
Успешные RPS: 3,354 / сек → в 15 раз ниже цели
-
Ошибки: ❌ 35.87% (http_req_failed)
-
Дропы: ❌ 1,861,627 итераций
-
p(95) задержка: ❌ 17.03 сек (!), медиана ≈0.4 сек
-
Макс. задержка: 34.92 сек
-
Трафик:
-
Получено: 104 MB (1.2 MB/s)
-
Отправлено: 23 MB (250 KB/s)
-
-
VUs: до 29,170 — почти на пределе лимита
❌ Вывод:
-
Caddy не выдерживает нагрузку в 50k RPS
-
Большая доля ошибок, дропов и задержек
-
Работает хуже, чем NGINX и Traefik, ближе к антирекорду
Итоги
✅ HAProxy — лидер по стабильности
-
Уверенный лидер по результатам тестов: стабильно держит 50 000 rps при минимальной нагрузке на CPU. Прост в запуске и конфиге.
🟢 Envoy — почти наравне с HAProxy
-
Нагрузка на CPU выше, но приемлемая. Удобен в сценариях, где нужны гибкие настройки маршрутизации.
🟡 NGINX — середняк
-
Нужен fine-tuning или другой режим работы
-
Пропускная способность в 5 раз ниже ожидаемой
-
Аномальная нагрузка на CPU
🔴 Traefik — нестабилен
-
Почти 33% ошибок, сильные дропы, p95 = 8.5 сек.
-
Пропускная способность в 5 раз ниже ожидаемой
-
Аномальная нагрузка на CPU
🔻 Caddy — худший результат
-
Самые высокие задержки (p95 = 17 сек), ошибки 36%, дропов >1.8 млн.
-
Аномальная нагрузка на CPU
-
Явно не справляется с такой нагрузкой в дефолтной конфигурации.
Что важно понять из результатов?
-
HAProxy и Envoy — лучшие выборы, если нужна готовая к бою система без плясок с бубном.
-
Traefik, NGINX, Caddy — требуют дополнительной настройки и оптимизации для высоконагруженных сценариев.
Для кого подойдёт и зачем всё это использовать?
Когда можно брать HAProxy или Envoy:
-
Когда нужен стабильный и производительный балансировщик «здесь и сейчас».
-
Минимальные усилия на настройку — запустил и работает.
Когда стоит смотреть на Traefik / NGINX / Caddy:
-
Для небольших и средних проектов, где нагрузки ниже и можно позволить себе чуть сложнее конфиг или доработку.
-
Если в команде есть человек, который умеет настраивать и оптимизировать эти инструменты под конкретные задачи.
Спасибо что дочитали до конца ;-)
Автор: stas_dubich
