Как стать автором
Обновить

Проксирование из коробки: сравнительный анализ HAProxy, Envoy, Nginx, Caddy и Traefik

Уровень сложностиСредний
Время на прочтение23 мин
Количество просмотров15K

Всем привет, меня зовут Стас, я техлид в 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.

Такое разделение ролей и ресурсов дало возможность получить максимально чистые и объективные данные о производительности каждого инструмента.

Версии веб серверов

haproxy 3.1.6
envoy 1.34.0-dev
nginx 1.27.4
traeffik 3.3.5
caddy 2.9.1

Работаем с бэкэндом

Сервис делали на 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:

  • Для небольших и средних проектов, где нагрузки ниже и можно позволить себе чуть сложнее конфиг или доработку.

  • Если в команде есть человек, который умеет настраивать и оптимизировать эти инструменты под конкретные задачи.

Update

По просьбам из комментариев дополнительные тесты NGINX

Тест №1

keepalive 4;
Без multi_accept

worker_processes auto;
worker_rlimit_nofile 300000;

events {
    worker_connections 5000;
    use epoll;
}

http {
  upstream backend {
    server 10.0.0.4:8080;
    keepalive 4;
  }
  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://backend;
        proxy_set_header Connection '';
        proxy_http_version 1.1;
        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;
    }
  }
}
  █ THRESHOLDS 

    checks
    ✗ 'rate>=0.95' rate=30.91%

    http_req_duration
    ✗ 'p(95)<500' p(95)=1.11s

    http_req_failed
    ✗ 'rate<0.01' rate=69.08%


  █ TOTAL RESULTS 

    checks_total.......................: 1012194 11241.972175/s
    checks_succeeded...................: 30.91%  312910 out of 1012194
    checks_failed......................: 69.08%  699284 out of 1012194

    ✗ status is 200
      ↳  30% — ✓ 156455 / ✗ 349642
    ✗ body contains expected content
      ↳  30% — ✓ 156455 / ✗ 349642

    HTTP
    http_req_duration.......................................................: avg=482.88ms min=0s       med=541.38ms max=2.64s p(90)=1s    p(95)=1.11s
      { expected_response:true }............................................: avg=308.14ms min=448.5µs  med=26.44ms  max=2.64s p(90)=1.19s p(95)=1.45s
    http_req_failed.........................................................: 69.08%  349642 out of 506097
    http_reqs...............................................................: 506097  5620.986087/s

    EXECUTION
    dropped_iterations......................................................: 1780766 19778.147096/s
    iteration_duration......................................................: avg=3.13s    min=495.19µs med=658.68ms max=1m0s  p(90)=1.37s p(95)=30s  
    iterations..............................................................: 506097  5620.986087/s
    vus.....................................................................: 13      min=13               max=29174
    vus_max.................................................................: 29266   min=1504             max=29266

    NETWORK
    data_received...........................................................: 179 MB  2.0 MB/s
    data_sent...............................................................: 56 MB   624 kB/s

Тест №2

keepalive 4;
C multi_accept

worker_processes auto;
worker_rlimit_nofile 300000;

events {
    worker_connections 5000;
    use epoll;
    multi_accept on;
}

http {
  upstream backend {
    server 10.0.0.4:8080;
    keepalive 4;
  }
  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://backend;
        proxy_set_header Connection '';
        proxy_http_version 1.1;
        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;
    }
  }
}
  █ THRESHOLDS 

    checks
    ✗ 'rate>=0.95' rate=25.90%

    http_req_duration
    ✗ 'p(95)<500' p(95)=9.14s

    http_req_failed
    ✗ 'rate<0.01' rate=74.09%


  █ TOTAL RESULTS 

    checks_total.......................: 944528 13781.245473/s
    checks_succeeded...................: 25.90% 244664 out of 944528
    checks_failed......................: 74.09% 699864 out of 944528

    ✗ status is 200
      ↳  25% — ✓ 122332 / ✗ 349932
    ✗ body contains expected content
      ↳  25% — ✓ 122332 / ✗ 349932

    HTTP
    http_req_duration.......................................................: avg=3.1s  min=0s       med=2.85s   max=20.66s p(90)=7.18s p(95)=9.14s
      { expected_response:true }............................................: avg=1.37s min=462.33µs med=26.11ms max=20.66s p(90)=6.27s p(95)=8.41s
    http_req_failed.........................................................: 74.09%  349932 out of 472264
    http_reqs...............................................................: 472264  6890.622736/s

    EXECUTION
    dropped_iterations......................................................: 1642833 23969.94567/s
    iteration_duration......................................................: avg=3.28s min=504.69µs med=3.01s   max=21.41s p(90)=7.37s p(95)=9.31s
    iterations..............................................................: 472264  6890.622736/s
    vus.....................................................................: 376     min=376              max=29063
    vus_max.................................................................: 29248   min=1490             max=29248

    NETWORK
    data_received...........................................................: 200 MB  2.9 MB/s
    data_sent...............................................................: 55 MB   797 kB/s

Тест №3

Исходный конфиг + тюнинг SSL

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;

    ssl_ciphers 'TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES128-GCM-SHA256';
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
	
    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;
    }
  }
}
  █ THRESHOLDS 

    checks
    ✗ 'rate>=0.95' rate=90.23%

    http_req_duration
    ✗ 'p(95)<500' p(95)=3.97s

    http_req_failed
    ✗ 'rate<0.01' rate=9.76%


  █ TOTAL RESULTS 

    checks_total.......................: 1719696 24413.0758/s
    checks_succeeded...................: 90.23%  1551846 out of 1719696
    checks_failed......................: 9.76%   167850 out of 1719696

    ✗ status is 200
      ↳  90% — ✓ 775923 / ✗ 83925
    ✗ body contains expected content
      ↳  90% — ✓ 775923 / ✗ 83925

    HTTP
    http_req_duration.......................................................: avg=1.49s min=0s      med=1.29s max=1m0s   p(90)=3.12s p(95)=3.97s
      { expected_response:true }............................................: avg=1.65s min=4.09ms  med=1.42s max=22.06s p(90)=3.26s p(95)=4.06s
    http_req_failed.........................................................: 9.76%   83925 out of 859848
    http_reqs...............................................................: 859848  12206.5379/s

    EXECUTION
    dropped_iterations......................................................: 1921167 27273.189911/s
    iteration_duration......................................................: avg=1.56s min=13.02ms med=1.35s max=1m0s   p(90)=3.18s p(95)=4.03s
    iterations..............................................................: 859848  12206.5379/s
    vus.....................................................................: 1       min=1               max=28502
    vus_max.................................................................: 28789   min=2785            max=28789

    NETWORK
    data_received...........................................................: 239 MB  3.4 MB/s
    data_sent...............................................................: 100 MB  1.4 MB/s

Спасибо что дочитали до конца ;-)

Теги:
Хабы:
+61
Комментарии40

Публикации