Envoy vs Nginx: HTTP/1.1 Reverse Proxy Performance

HTTP/1.1のリバースプロキシとして、EnvoyとNginxとの軽めのベンチマークを取りました.
しっかりとしたベンチマークとはいえませんが、少しは参考になる結果かもしれません.

TL; DR

  • リバースプロキシコンテナは、CPU・Memoryともにリソースの制限をする
  • リバースプロキシとサービスはHTTP/1.1でTCPソケットで通信する
  • wrk -t 10 -c 10 http://hostで出力結果を取る
  • Nginxの方が約1msほど早かったが、はっきりいって誤差
  • corrupt952/surveyに今回の記事の元となるコードを置いている

実行環境

  • docker ... 18.09.0
  • docker-compose ... 1.23.2
  • wrk ... 4.1.0

準備

今回もdocker-composeを利用して環境を作ります.
コードは、corrupt952/surveyにあります.

docker-compose.yml

Envoy・Nginx・サービス代わりのNginxの3つを定義します

version: '3'
services:
  envoy:
    build:
      context: .
      dockerfile: dockerfiles/envoy/Dockerfile
    expose:
      - "80"
    ports:
      - "8000:80"

  nginx:
    build:
      context: .
      dockerfile: dockerfiles/nginx/Dockerfile
    expose:
      - "80"
    ports:
      - "8001:80"

  service:
    image: nginx:stable-alpine
    expose:
      - "80"

dockerfiles/envoy/Dockerfile

Envoyのサンプルコードを参考にしていますが、不要なコードは削除しました.

FROM envoyproxy/envoy-alpine:01d726a41bdd790c16765e1d321cb50590574eb0

COPY dockerfiles/envoy/front-envoy.yaml /etc/front-envoy.yaml
CMD ["/usr/local/bin/envoy", "-c", "/etc/front-envoy.yaml", "--service-cluster", "front-proxy"]

dockerfiles/envoy/front-envoy.yaml

Envoyのサンプルコードを参考にしてサービスへリダイレクトするようにしています.

admin:
  access_log_path: "/dev/null"
  address:
    socket_address:
      address: 0.0.0.0
      port_value: 8001

static_resources:
  listeners:
    - address:
        socket_address:
          address: 0.0.0.0
          port_value: 80
      filter_chains:
        - filters:
          - name: envoy.http_connection_manager
            config:
              codec_type: HTTP1
              stat_prefix: ingress_http
              route_config:
                name: local_route
                virtual_hosts:
                  - name: backend
                    domains:
                      - "*"
                    routes:
                      - match:
                          prefix: "/"
                        route:
                          cluster: service
              http_filters:
                - name: envoy.router
                  config: {}
  clusters:
    - name: service
      connect_timeout: 0.25s
      type: strict_dns
      lb_policy: round_robin
      hosts:
        - socket_address:
            address: service
            port_value: 80

dockerfiles/nginx/Dockerfile

次に紹介するdefault.confをNginxが読み込める位置に定義しておきます.

FROM nginx:stable-alpine
COPY dockerfiles/nginx/default.conf /etc/nginx/default.conf

dockerfiles/nginx/default.conf

サービスへプロキシするように定義するだけなので特筆することはありません.

server {
    listen 80 default_server;
    server_name _;

    location / {
        proxy_pass http://service/;
    }
}

これで準備は終わりです.

ベンチマークしてみる

wrkというツールを使い、簡易的にベンチマークを実行してみます.
コネクション数10,スレッド数10に設定にします.(これといって根拠はないです)

Envoy

$ wrk -t 10 -c 10 http://localhost:8000
Running 10s test @ http://localhost:8000
  10 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     2.45ms    2.02ms  46.58ms   96.63%
    Req/Sec   433.29    121.99     1.17k    82.34%
  43501 requests in 10.10s, 35.39MB read
Requests/sec:   4305.17
Transfer/sec:      3.50MB

Nginx

$ wrk -t 10 -c 10 http://localhost:8001
Running 10s test @ http://localhost:8001
  10 threads and 10 connections
  Thread Stats   Avg      Stdev     Max   +/- Stdev
    Latency     1.65ms    0.96ms  13.85ms   88.35%
    Req/Sec   631.85    129.09     1.20k    66.53%
  63373 requests in 10.10s, 51.37MB read
Requests/sec:   6271.63
Transfer/sec:      5.08MB

Nginxの方が約1ms早いという結果になりました.
ですが、実際この差は膨大なリクエストを捌く必要がある大規模なサービスでなければ誤差といえる範疇かと思います.

最後に

今回は、HTTP/1.1のFront Proxyという役割でやってみましたが、Nginx・Envoyともにサポートしている領域が他にもあります.
そういった点を踏まえながら最適なモノを選んでいくのが良いでしょう.