動的なKubernetesクラウド環境におけるNGINX Ingress Controllerのパフォーマンステスト

NGINX | 2020年9月22日

編集者 – この記事は全10回のシリーズの一部です:

  1. 本番環境対応のKubernetesで複雑さを軽減
  2. 高度なトラフィック管理でKubernetesのレジリエンスを向上させる方法
  3. Kubernetesの可視性を向上させる方法
  4. トラフィック管理ツールを使用してKubernetesを保護する6つの方法
  5. Ingress Controller選定ガイド 第1部:要件の特定
  6. Ingress Controller選定ガイド 第2部:リスクと将来への備え
  7. Ingress Controller選定ガイド 第3回:オープンソース、デフォルト、商用の比較
  8. Ingress Controller選定ガイド、第4回:NGINX Ingress Controllerの選択肢
  9. サービスメッシュの選び方
  10. 動的なKubernetesクラウド環境におけるNGINX Ingress Controllerのパフォーマンステスト(本記事)

一連のブログ記事をまとめた無料のeBook「Kubernetesをテスト環境から本番環境へ」もダウンロードできます。

コンテナ化されたアプリを本番環境で運用する企業が増える中、Kubernetesはコンテナオーケストレーションの標準ツールとしての地位を確立しつつあります。同時に、COVID‑19のパンデミックを受けた在宅勤務の取り組みによりインターネットトラフィックの増加が加速し、クラウドコンピューティングの需要は数年前倒しで拡大しました。顧客が大規模なネットワーク障害や過負荷に直面しているため、企業はインフラストラクチャのアップグレードを急いでいます。

クラウドベースのマイクロサービス環境で求められるレベルのパフォーマンスを実現するには、次世代ハイパースケールデータセンターの拡張性とパフォーマンスを活用できる、高速かつ完全に動的なソフトウェアが必要です。Kubernetesでコンテナを管理する多くの組織は、アプリをユーザーに提供するために、NGINXベースのIngressコントローラーを利用しています。

このブログでは、実際の運用環境に近いマルチクラウド環境で3つのNGINX Ingress Controllerを対象に実施したパフォーマンステストの結果を紹介します。このテストでは、インターネット経由のクライアント接続のレイテンシを測定しました。

テスト手順と収集したメトリクス

負荷生成プログラム「wrk2」を使用してクライアントをエミュレートし、所定の期間にわたってHTTPSリクエストを継続的に送信しました。テスト対象のIngressコントローラー(コミュニティ版Ingressコントローラー、NGINX Open SourceベースのNGINX Ingress Controller、またはNGINX PlusベースのNGINX Ingress Controller)は、Kubernetes Podにデプロイされたバックエンドアプリケーションにリクエストを転送し、アプリケーションが生成したレスポンスをクライアントに返しました。一定量のクライアントトラフィックを継続的に生成してIngressコントローラーにストレステストを実施し、以下のパフォーマンス指標を収集しました。

  • レイテンシー:クライアントがリクエストを生成してからレスポンスを受信するまでの時間です。レイテンシーはパーセンタイル分布で示します。たとえば、レイテンシーテストのサンプルが100件ある場合、99パーセンタイル値は、100回のテスト実行で測定されたレスポンスのうち、2番目に遅いレイテンシーです。
  • 接続タイムアウト – Ingressコントローラーが一定時間内にリクエストへ応答できなかったため、通知なく切断または破棄されたTCP接続。
  • 読み取りエラー – Ingressコントローラーのソケットが閉じられたために失敗した、接続からの読み取り試行。
  • 接続エラー – クライアントとIngressコントローラー間で確立されなかったTCP接続。

トポロジー

すべてのテストで、AWSのクライアントマシン上で動作するwrk2ユーティリティを使用してリクエストを生成しました。AWSクライアントは、Google Kubernetes Engine(GKE)環境のGKE-node-1にKubernetes DaemonSetとしてデプロイされたIngressコントローラーの外部IPアドレスに接続しました。Ingressコントローラーには、Kubernetes Secretを参照するSSL終端とレイヤー7ルーティングを設定し、Kubernetes ServiceのTypeをLoadBalancerとして公開しました。バックエンドアプリケーションは、GKE-node-2上でKubernetes Deploymentとして実行しました。

クラウドマシンのタイプとソフトウェア構成の詳細については、「付録」を参照してください。

テスト方法

クライアントのデプロイ

AWSクライアントマシンで、次のwrk2(バージョン4.0.0)スクリプトを実行しました。このスクリプトは2つのwrkスレッドを起動し、GKEにデプロイされたIngressコントローラーに合計1,000の接続を確立します。各3分間のテスト実行中、スクリプトは1秒あたり30,000リクエスト(RPS)を生成します。これは、本番環境におけるIngressコントローラーの負荷を適切に再現するものと考えています。

wrk -t2 -c1000 -d180s -L -R30000 https://app.example.com:443/

各項目の意味は次のとおりです。

  • -t – スレッド数を設定します(2)
  • -c – TCP接続数を設定します(1000)
  • -d – テスト実行時間を秒単位で設定します(180秒、つまり3分)
  • -L – 分析ツールにエクスポートするための詳細なレイテンシパーセンタイル情報を生成します
  • -R – RPS数を設定します(30,000)

TLS暗号化には、2,048ビットの鍵長を持つRSAとPerfect Forward Secrecyを使用しました。

バックエンドアプリケーション(アクセス先:https://app.example.com:443)からの各レスポンスは、約1 KBの基本的なサーバーメタデータと、HTTPステータスコード「200 OK」で構成されます。

バックエンドアプリケーションのデプロイ

バックエンドアプリケーションを静的および動的にデプロイし、それぞれテストを実施しました。

静的デプロイでは、Podレプリカを5つ用意し、Kubernetes APIを使用した変更は適用しませんでした。

動的デプロイでは、次のスクリプトを使用して、バックエンドのnginxデプロイを5つのPodレプリカから7つに定期的にスケールアウトし、その後5つにスケールインしました。これにより、動的なKubernetes環境を再現し、Ingressコントローラーがエンドポイントの変更にどの程度効果的に適応できるかをテストしました。

while [ 1 -eq 1 ]
do
  kubectl scale deployment nginx --replicas=5
  sleep 12
  kubectl scale deployment nginx --replicas=7
  sleep 10
done

パフォーマンス結果

静的デプロイメントのレイテンシ結果

グラフが示すように、バックエンドアプリケーションを静的にデプロイした場合、3つのIngressコントローラーはいずれも同程度のパフォーマンスを達成しました。これは、すべてNGINX Open Sourceをベースとしており、静的デプロイではIngressコントローラーによる再構成が不要であることを考えれば、妥当な結果です。

動的デプロイメントのレイテンシ結果

このグラフは、バックエンドアプリケーションのレプリカPod数を5から7に増やした後、再び5に戻すスケーリングを定期的に行う動的デプロイメントにおいて、各Ingressコントローラーで発生したレイテンシーを示しています(詳細については「バックエンドアプリケーションのデプロイメント」を参照してください)。

この環境で良好なパフォーマンスを発揮するのはNGINX PlusベースのIngressコントローラーだけであり、99.99パーセンタイルに至るまでレイテンシーはほとんど発生していません。コミュニティ版とNGINX Open SourceベースのIngressコントローラーでは、比較的低いパーセンタイルから顕著なレイテンシーが発生しますが、その傾向は大きく異なります。コミュニティ版Ingressコントローラーでは、レイテンシーが緩やかながら着実に増加し、99パーセンタイルで約5,000 ms(5秒)に達した後、横ばいになります。NGINX Open SourceベースのIngressコントローラーでは、レイテンシーが99パーセンタイルまでに約32秒へと急増し、99.99パーセンタイルまでに再び増加して60秒に達します。

詳しくは「動的デプロイにおけるタイムアウトとエラーの結果」で説明しますが、コミュニティ版およびNGINX Open SourceベースのIngressコントローラーで発生するレイテンシーは、バックエンドアプリケーションのエンドポイントの変更に応じてNGINXの設定が更新され、再読み込みされた後に発生するエラーとタイムアウトが原因です。

前のグラフと同じテスト条件における、コミュニティ版IngressコントローラーとNGINX PlusベースのIngressコントローラーの結果を、より詳細に示します。NGINX PlusベースのIngressコントローラーでは、99.99パーセンタイルまでレイテンシーがほとんど発生せず、その後増加し始め、99.9999パーセンタイルでは254 ms近くに達します。コミュニティ版Ingressコントローラーのレイテンシーは徐々に増加し、99パーセンタイルで5,000 msに達した後、横ばいになります。

動的デプロイメントのタイムアウトとエラーの結果

この表は、レイテンシの測定結果の原因をより詳しく示しています。

NGINX Open Source コミュニティ NGINX Plus接続エラー 33365 0 0接続タイムアウト 309 8809 0読み取りエラー 4650 0 0

NGINX Open SourceベースのIngressコントローラーでは、バックエンドアプリケーションのエンドポイントが変更されるたびにNGINXの設定を更新して再読み込みする必要があるため、多数の接続エラー、接続タイムアウト、読み取りエラーが発生します。接続/ソケットエラーは、NGINXの再読み込みにかかるわずかな時間に、クライアントがNGINXプロセスに割り当てられなくなったソケットへ接続しようとした場合に発生します。接続タイムアウトは、クライアントがIngressコントローラーとの接続を確立したものの、バックエンドエンドポイントがすでに利用できなくなっている場合に発生します。エラーとタイムアウトはいずれもレイテンシーに深刻な影響を及ぼし、99パーセンタイルでは32秒、99.99パーセンタイルでは60秒まで急増します。

コミュニティ版Ingressコントローラーでは、バックエンドアプリケーションのスケールアップおよびスケールダウンに伴うエンドポイントの変更により、8,809件の接続タイムアウトが発生しました。コミュニティ版Ingressコントローラーは、エンドポイントの変更時に設定を再読み込みしなくて済むよう、Luaコードを使用しています。結果から、NGINX内でLuaハンドラーを実行してエンドポイントの変更を検出することで、エンドポイントが変更されるたびに設定を再読み込みする必要があるNGINX Open Sourceベースのバージョンにおけるパフォーマンス上の制約の一部が解消されることがわかります。それでも接続タイムアウトは発生し、パーセンタイルが高くなるほどレイテンシーが大幅に増加します。

NGINX PlusベースのIngressコントローラーでは、エラーもタイムアウトも発生せず、動的環境がパフォーマンスに及ぼす影響はほとんどありませんでした。これは、エンドポイントの変更時にNGINX Plus APIを使用してNGINXの設定を動的に更新するためです。前述のとおり、最大レイテンシは254 msで、99.9999パーセンタイルでのみ発生しました。

まとめ

パフォーマンステストの結果から、動的なKubernetesクラウド環境でタイムアウトやエラーを完全に排除するには、Ingressコントローラーがイベントハンドラーや設定の再読み込みを使用せず、バックエンドエンドポイントの変更に動的に対応する必要があることが分かりました。この結果から、動的な環境でNGINXを動的に再構成するには、NGINX Plus APIが最適なソリューションであると言えます。テストでは、変化の激しいKubernetes環境において、ユーザーの満足度を維持するために必要な、エラーのないパフォーマンスを実現したのは、NGINX PlusベースのIngressコントローラーだけでした。

付録

クラウドマシンの仕様

マシン クラウドプロバイダー マシンタイプClient AWS m5a.4xlargeGKE-node-1 GCP e2-standard-32GKE-node-2 GCP e2-standard-32

NGINX Open SourceおよびNGINX Plus Ingress Controllerの構成

Kubernetesの設定

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: nginx-ingress
  namespace: nginx-ingress
spec:
  selector:
    matchLabels:
      app: nginx-ingress
  template:
    metadata:
      labels:
        app: nginx-ingress
     #annotations:
       #prometheus.io/scrape: "true"
       #prometheus.io/port: "9113"
    spec:
      serviceAccountName: nginx-ingress
      nodeSelector:
        kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-6nzr
      hostNetwork: true    
      containers:
      - image: gcr.io/nginx-demos/nap-ingress:edge
        imagePullPolicy: Always
        name: nginx-plus-ingress
        ports:
        - name: http
          containerPort: 80
          hostPort: 80
        - name: https
          containerPort: 443
          hostPort: 443
        - name: readiness-port
          containerPort: 8081
       #- name: prometheus
         #containerPort: 9113
        readinessProbe:
          httpGet:
            path: /nginx-ready
            port: readiness-port
          periodSeconds: 1
        securityContext:
          allowPrivilegeEscalation: true
          runAsUser: 101 #nginx
          capabilities:
            drop:
            - ALL
            add:
            - NET_BIND_SERVICE
        env:
        - name: POD_NAMESPACE
          valueFrom:
            fieldRef:
              fieldPath: metadata.namespace
        - name: POD_NAME
          valueFrom:
            fieldRef:
              fieldPath: metadata.name
        args:
          - -nginx-plus
          - -nginx-configmaps=$(POD_NAMESPACE)/nginx-config
          - -default-server-tls-secret=$(POD_NAMESPACE)/default-server-secret

注記:

  • この構成はNGINX Plus向けです。nginx‑plusへの参照は、NGINX Open Source向けの構成で必要に応じて調整されています。
  • NGINX App Protectはイメージ(gcr.io/nginx-demos/nap-ingress:edge)に含まれていますが、無効化されています(-enable-app-protectフラグは省略されています)。

ConfigMap

kind: ConfigMap
apiVersion: v1
metadata:
  name: nginx-config
  namespace: nginx-ingress
data:
  worker-connections: "10000"
  worker-rlimit-nofile: "10240"
  keepalive: "100"
  keepalive-requests: "100000000"

Community NGINX Ingress Controllerの設定

Kubernetesの設定

apiVersion: apps/v1
kind: DaemonSet
metadata:
  labels:
    helm.sh/chart: ingress-nginx-2.11.1
    app.kubernetes.io/name: ingress-nginx
    app.kubernetes.io/instance: ingress-nginx
    app.kubernetes.io/version: 0.34.1
    app.kubernetes.io/managed-by: Helm
    app.kubernetes.io/component: controller
  name: ingress-nginx-controller
  namespace: ingress-nginx
spec:
  selector:
    matchLabels:
      app.kubernetes.io/name: ingress-nginx
      app.kubernetes.io/instance: ingress-nginx
      app.kubernetes.io/component: controller
  template:
    metadata:
      labels:
        app.kubernetes.io/name: ingress-nginx
        app.kubernetes.io/instance: ingress-nginx
        app.kubernetes.io/component: controller
    spec:
      nodeSelector: 
        kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-6nzr
      hostNetwork: true
      containers:
        - name: controller
          image: us.gcr.io/k8s-artifacts-prod/ingress-nginx/controller:v0.34.1@sha256:0e072dddd1f7f8fc8909a2ca6f65e76c5f0d2fcfb8be47935ae3457e8bbceb20
          imagePullPolicy: IfNotPresent
          lifecycle:
            preStop:
              exec:
                command:
                  - /wait-shutdown
          args:
            - /nginx-ingress-controller
            - --election-id=ingress-controller-leader
            - --ingress-class=nginx
            - --configmap=$(POD_NAMESPACE)/ingress-nginx-controller
            - --validating-webhook=:8443
            - --validating-webhook-certificate=/usr/local/certificates/cert
            - --validating-webhook-key=/usr/local/certificates/key
          securityContext:
            capabilities:
              drop:
                - ALL
              add:
                - NET_BIND_SERVICE
            runAsUser: 101
            allowPrivilegeEscalation: true
          env:
            - name: POD_NAME
              valueFrom:
                fieldRef:
                  fieldPath: metadata.name
            - name: POD_NAMESPACE
              valueFrom:
                fieldRef:
                  fieldPath: metadata.namespace
          readinessProbe:
            httpGet:
              path: /healthz
              port: 10254
              scheme: HTTP
            periodSeconds: 1
          ports:
            - name: http
              containerPort: 80
              protocol: TCP
            - name: https
              containerPort: 443
              protocol: TCP
            - name: webhook
              containerPort: 8443
              protocol: TCP
          volumeMounts:
            - name: webhook-cert
              mountPath: /usr/local/certificates/
              readOnly: true
      serviceAccountName: ingress-nginx
      terminationGracePeriodSeconds: 300
      volumes:
        - name: webhook-cert
          secret:
            secretName: ingress-nginx-admission

ConfigMap

apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  max-worker-connections: "10000"
  max-worker-open-files: "10204"
  upstream-keepalive-connections: "100"
  keep-alive-requests: "100000000"

バックエンドアプリの設定

Kubernetesの設定

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx
spec:
  selector:
    matchLabels:
      app: nginx 
  template:
    metadata:
      labels:
        app: nginx
    spec:
      nodeSelector:
        kubernetes.io/hostname: gke-rawdata-cluster-default-pool-3ac53622-t2dz 
      containers:
      - name: nginx
        image: nginx
        ports:
        - containerPort: 8080
        volumeMounts: 
        - name: main-config-volume
          mountPath: /etc/nginx
        - name: app-config-volume
          mountPath: /etc/nginx/conf.d
        readinessProbe:
          httpGet:
            path: /healthz
            port: 8080
          periodSeconds: 3
      volumes: 
      - name: main-config-volume
        configMap:
          name: main-conf
      - name: app-config-volume
        configMap: 
          name: app-conf
---

ConfigMaps

apiVersion: v1
kind: ConfigMap
metadata:
  name: main-conf
  namespace: default
data:
  nginx.conf: |+
    user nginx;
    worker_processes 16;
    worker_rlimit_nofile 102400;
    worker_cpu_affinity auto 1111111111111111;
    error_log  /var/log/nginx/error.log notice;
    pid        /var/run/nginx.pid;
 
    events {
        worker_connections  100000;
    }
 
    http {
 
        log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                          '$status $body_bytes_sent "$http_referer" '
                          '"$http_user_agent" "$http_x_forwarded_for"';
 
        sendfile        on;
        tcp_nodelay on;
 
        access_log off;
 
        include /etc/nginx/conf.d/*.conf;
    }
 
---
 
apiVersion: v1
kind: ConfigMap
metadata:
  name: app-conf
  namespace: default
data:
  app.conf: "server {listen 8080;location / {default_type text/plain;expires -1;return 200 'Server address: $server_addr:$server_portnServer name:$hostnamenDate: $time_localnURI: $request_urinRequest ID: $request_idn';}location /healthz {return 200 'I am happy and healthy :)';}}"
---

サービス

apiVersion: v1
kind: Service
metadata:
  name: app-svc
spec:
  ports:
  - port: 80
    targetPort: 8080
    protocol: TCP
    name: http
  selector:
    app: nginx
---


共有

著者について

Amir Rawdat
Amir Rawdatソリューションエンジニア

Amir Rawdatのその他のブログ記事

関連するブログ記事

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する
NGINX | 2024/01/18

チュートリアル:NGINXを使用してアプリケーション向けにOpenTelemetryを構成する

OpenTelemetryを使用してNGINXサーバーの状態と健全性をより明確に把握することで、問題のトラブルシューティングを行い、Webアプリケーションのパフォーマンスを向上できます。

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正
NGINX | 2023/05/31

AkitaのNGINX Plus認定モジュールでAPIエンドポイントの問題を特定・修正

Akita SoftwareのAPIオブザーバビリティ向け新しいNGINX Plus Certified Moduleを活用して、APIエンドポイントの問題をより迅速に特定し、修正できます。

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法
NGINX | 2023/02/09

JavaプロジェクトをスキャフォールディングしてKubernetesネイティブアプリを構築する方法

Red HatのシニアプリンシパルデベロッパーアドボケイトであるDaniel Oh氏が、Javaプロジェクトをゼロから構築するためのひな形を作成し、Kubernetesネイティブアプリケーションとしてビルドする方法を解説します。

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング
NGINX | 2022/05/24

F5 NGINXaaS for Azureのご紹介:Microsoft Azureでネイティブに利用できるSaaS型ロードバランシング

NGINXaaS for Azureは、NGINX PlusをAzureネイティブのSaaSとして利用できるサービスです。NGINXの設定を容易にリフト&シフトできるため、クラウド導入とモダンアプリの展開を加速できます。

SELinuxでNGINXおよびNGINX Plusを使用する
NGINX | 2018/08/17

SELinuxでNGINXおよびNGINX Plusを使用する

Red Hat Enterprise Linux(RHEL)および関連ディストリビューションでSecurity-Enhanced Linux(SELinux)を有効にすると、デフォルト設定によりNGINXとNGINX Plusの一部の操作が制限されます。この記事では、すべての機能を利用できるようにSELinuxの設定を変更する方法について説明します。

NGINX PlusによるAWS Auto Scalingグループの負荷分散
NGINX | 2017/03/06

NGINX PlusによるAWS Auto Scalingグループの負荷分散

新しいnginx-asg-sync統合ソフトウェアにより、NGINX PlusはAWS Auto Scalingグループの負荷を直接分散できるため、クラウドでの負荷分散がさらに柔軟になります