Skip to content

Instantly share code, notes, and snippets.

@superbrothers
Last active June 14, 2021 14:42
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save superbrothers/5fd0dd75878ea4d9b66a1f0fa28d4655 to your computer and use it in GitHub Desktop.
Save superbrothers/5fd0dd75878ea4d9b66a1f0fa28d4655 to your computer and use it in GitHub Desktop.

NLB + Envoy 負荷分散検証

TL;DR

  • Envoy はデフォルトでは接続済みコネクションを無制限に保持するため、Envoy Deployment の更新などでコネクション数が一度偏ると長時間それが維持される
  • Envoy max-connection-duration を用いて定期的にコネクションをクローズすることでコネクション数が分散する
    • gRPC クライアントはコネクションがクローズされると自動的に再接続する(Go クライアントの場合)
  • アクティブなコネクションのクローズを避けたい場合は idle-timeout も使用できる

screencapture-localhost-8001-graph-2021-06-12-12_45_39

検証環境の構築

ここでは scalar-terraform-examples を用いる。

$ g show -q
commit 4e7aad442b3e7ae8da8984df5567edbc54aeff71 (HEAD -> main, origin/main, origin/HEAD)
Author: tei-k <tei@leosys.space>
Date:   Tue Jun 8 16:44:25 2021 +0900

    Fix to use prefix version for azure k8s module (#28)

ネットワーク

$ g diff
diff --git a/aws/network/example.tfvars b/aws/network/example.tfvars
index a5519c2..b17090c 100644
--- a/aws/network/example.tfvars
+++ b/aws/network/example.tfvars
@@ -1,13 +1,12 @@
-region = "ap-northeast-1"
+region = "us-west-1"

 base = "default" # bai, chiku, sho

-name = "example-aws" # maximum of 13 characters
+name = "ksuda-nlb-envoy" # maximum of 13 characters

 locations = [
-  "ap-northeast-1a",
-  "ap-northeast-1c",
-  "ap-northeast-1d",
+  "us-west-1b",
+  "us-west-1c"
 ]

 public_key_path = "./example_key.pub"

Kubernetes クラスタ

$ g diff example.tfvars
diff --git a/aws/kubernetes/example.tfvars b/aws/kubernetes/example.tfvars
index 4ce93c0..a3b4497 100644
--- a/aws/kubernetes/example.tfvars
+++ b/aws/kubernetes/example.tfvars
@@ -1,7 +1,7 @@
-region = "ap-northeast-1"
+region = "us-west-1"

 kubernetes_cluster = {
-  # name                                 = "scalar-kubernetes"
+  name                                 = "ksuda-nlb-envoy"
   # kubernetes_version                   = "1.19"
   # cluster_enabled_log_types            = ""
   # cluster_log_retention_in_days        = "90"

kube-prometheus のインストール

Envoy のメトリクスを収集、可視化するために kube-prometheus を用いて Prometheus をインストールする。

[centos@bastion-1 ~]$ git clone https://github.com/prometheus-operator/kube-prometheus.git
[centos@bastion-1 ~]$ cd kube-prometheus/
[centos@bastion-1 kube-prometheus]$ kubectl create -f manifests/setup
[centos@bastion-1 kube-prometheus]$ kubectl create -f manifests/

Envoy + gRPC アプリケーションのデプロイ

ここでは簡単な gRPC アプリケーションをデプロイする。これはシンプルな Hello world アプリケーションだが、クライアントはコネクションを維持しつつ、一定間隔でリクエストするようになっている。Envoy Deployment は3つの Pod レプリカを持つ。

また、この時点での Envoy は、接続済のコネクションは無制限で保持する設定となっている。

[centos@bastion-1 ~]$ git clone -b aws-nlb https://github.com/superbrothers-sandbox/try-envoy-grpc.git && cd try-envoy-grpc
[centos@bastion-1 try-envoy-grpc]$ kubectl apply -k deploy/base
[centos@bastion-1 try-envoy-grpc]$ kubectl get all
NAME                          READY   STATUS    RESTARTS   AGE
pod/envoy-7575d7d9fb-j655c    1/1     Running   0          21s
pod/envoy-7575d7d9fb-rxhvp    1/1     Running   0          21s
pod/envoy-7575d7d9fb-xgvpl    1/1     Running   0          21s
pod/server-7bbdd8b697-6qj49   1/1     Running   0          21s
pod/server-7bbdd8b697-9zxh5   1/1     Running   0          21s
pod/server-7bbdd8b697-bb5mf   1/1     Running   0          21s

NAME                 TYPE           CLUSTER-IP       EXTERNAL-IP                                                                     PORT(S)                         AGE
service/envoy        LoadBalancer   172.20.114.181   a3d9812d7b7eb41afb413248e3d9c40e-6cb71b7d839cb127.elb.us-west-1.amazonaws.com   8080:31401/TCP,9901:31241/TCP   21s
service/kubernetes   ClusterIP      172.20.0.1       <none>                                                                          443/TCP                         38m
service/server       ClusterIP      None             <none>                                                                          8888/TCP                        21s

NAME                     READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/envoy    3/3     3            3           21s
deployment.apps/server   3/3     3            3           21s

NAME                                DESIRED   CURRENT   READY   AGE
replicaset.apps/envoy-7575d7d9fb    3         3         3       21s
replicaset.apps/server-7bbdd8b697   3         3         3       21s

envoy Service が LoadBalancer type となっており、a3d9812d7b7eb41afb413248e3d9c40e-6cb71b7d839cb127.elb.us-west-1.amazonaws.com が LB のアドレスとして払い出されている。

gRPC クライアントの実行

gRPC クライアントをコンテナで実行するため Docker をインストールする。

[centos@bastion-1 ~]$ sudo docker version
Client: Docker Engine - Community
 Version:           20.10.7
 API version:       1.41
 Go version:        go1.13.15
 Git commit:        f0df350
 Built:             Wed Jun  2 11:58:10 2021
 OS/Arch:           linux/amd64
 Context:           default
 Experimental:      true

Server: Docker Engine - Community
 Engine:
  Version:          20.10.7
  API version:      1.41 (minimum version 1.12)
  Go version:       go1.13.15
  Git commit:       b0f5bc3
  Built:            Wed Jun  2 11:56:35 2021
  OS/Arch:          linux/amd64
  Experimental:     false
 containerd:
  Version:          1.4.6
  GitCommit:        d71fcd7d8303cbf684402823e425e9dd2e99285d
 runc:
  Version:          1.0.0-rc95
  GitCommit:        b9ee9c6314599f1b4a7f497e1f1f856fe433d3b7
 docker-init:
  Version:          0.19.0
  GitCommit:        de40ad0

20個のクライアントをコンテナとして実行する。各クライアントはコネクションを維持しながら3秒間隔でサーバにリクエストする。つまり20コネクションが クライアント -> Envoy 間で確立される。

[centos@bastion-1 ~]$ for i in `seq 20`; do sudo docker run -d ghcr.io/superbrothers-sandbox/try-envoy-grpc/hello -client -addr a3d9812d7b7eb41afb413248e3d9c40e-6cb71b7d839cb127.elb.us-west-1.amazonaws.com:8080 -client.interval 3s; done

その後、一度 Envoy Pods を更新する。これにより Envoy Pod が更新されるため、よりコネクションがある特定の Pod に偏るはず。

[centos@bastion-1 ~]$ kubectl rollout restart deploy envoy

次に、Envoy で max-connection-duration1s として設定を更新する。この設定により、クライアントから Envoy のコネクションは最長で1秒間しか保持されなくなり、切断されたコネクションは自動的に再接続される。これによりある期間で平均するとコネクション数が偏らなくなる。

[centos@bastion-1 try-envoy-grpc]$ diff -u deploy/base/config/envoy.yaml deploy/envoy-max-connection-duration/config/envoy.yaml
--- deploy/base/config/envoy.yaml       2021-06-12 02:49:45.482457956 +0000
+++ deploy/envoy-max-connection-duration/config/envoy.yaml      2021-06-12 02:49:45.483457955 +0000
@@ -11,6 +11,9 @@
           "@type": type.googleapis.com/envoy.extensions.filters.network.http_connection_manager.v3.HttpConnectionManager
           codec_type: AUTO
           stat_prefix: ingress_http
+          common_http_protocol_options:
+            # https://www.envoyproxy.io/docs/envoy/latest/api-v3/config/core/v3/protocol.proto#envoy-v3-api-msg-config-core-v3-httpprotocoloptions
+            max_connection_duration: 1s
           route_config:
             name: local_route
             virtual_hosts:
[centos@bastion-1 try-envoy-grpc]$ kubectl apply -k deploy/envoy-max-connection-duration/
configmap/envoy-config-c4t76h25d4 created
service/envoy unchanged
service/server unchanged
deployment.apps/envoy configured
deployment.apps/server unchanged
servicemonitor.monitoring.coreos.com/envoy unchanged

この一連の変更を含む期間のメトリクスを可視化したものが下記で、前半は20のコネクションが1つの Pod に偏っていることがわかる。その後設定変更により、コネクションが3つ全ての Envoy Pods に分散されていることがわかる。なお、max-connection-duration の設定変更前にも一定簡単でコネクションの切断が起きているが、これは Envoy Pod のヘルスチェックで、きれいなグラフを作るためにはヘルスチェックを切っておくべきだったが、環境をクリーンアップしてから気づいたため、残ってしまった。

screencapture-localhost-8001-graph-2021-06-12-12_45_39

まとめ

ここでは Envoy max-connection-duration を用いて定期的にコネクションを切断することである期間で平均すると分散することが確認できた。コネクションを切断しない場合は、コネクションは無制限に維持されるため、Envoy Pods 間でコネクション数が偏る可能性がある。アクティブなコネクションを切断することに抵抗がある場合は idle_timeout を使うこともできる。

Service type LoadBalancer はどのように機能するのか

Service type LoadBalancer は upstream として Service NodePort を使用する。そのため、直接 Pod に対して分散されるわけではなく、upstream はノードの IP になるため、ロードバランサの負荷分散アルゴリズムは直接関連がない(しかしながら、モノによっては直接 Pod IP を upstream とする場合もある)。AWS NLB の場合は、Node Port が対象となる。NodePort に届いたパケットは ClusterIP による負荷分散と同じ仕組みで Service のラベルセレクタに一致する Pod IP にラウンドロビンで分散する。

Service NodePort type

AWS ALB の場合は upstream に NodePort 以外に直接 Pod IP に転送することができる。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment