APISIX Ingress Controller 如何支持数千个 Pod 副本?

Xin Rong

October 21, 2022

Technology

1,000以上のKubernetes Pod

PodはKubernetesでデプロイ可能な最小のオブジェクトです。アプリケーションの単一インスタンスを実行するためにPodを使用するため、スケーラビリティの観点からPodには限られたリソースしか割り当てません。しかし、高トラフィックのシナリオに遭遇した場合、水平スケーリングを使用して対応します。

例えば、オンライン小売業者にとって、ブラックフライデーはトラフィックの急増を引き起こします。このシナリオに対応するため、サービスを自動スケーリングしてより多くのトラフィックを処理する必要があります。自動スケーリング戦略に基づいてより多くのレプリケーションをデプロイし、結果としてより多くのPodが作成されます。

各Podには動的なIPアドレスが割り当てられます。Endpoints APIは、Kubernetes内のネットワークエンドポイントを追跡するための簡単な方法を提供しているため、PodのIPの変更をタイムリーに監視することでロードバランシングを実現できます。しかし、Kubernetesクラスターとサービスがより多くのトラフィックを処理するために成長し、例えば前述のブラックフライデーのシナリオのように、Podの数が増え続けると、Endpoints APIも大きくなります。その結果、Endpoints APIの制限がより顕著になり、パフォーマンスのボトルネックになることさえあります。

Endpoints APIの制限問題を解決するため、Kubernetesはバージョンv1.21からEndpointSlice APIのサポートを開始しました。EndpointSlice APIは、Endpoints APIで巨大なネットワークエンドポイントを処理する際のパフォーマンス問題を解決し、優れたスケーラビリティと拡張性を提供します。

以下の図から、それらの違いを直接確認できます:

  • トラフィック急増時のEndpointsの変化

トラフィック急増時のEndpoints

  • トラフィック急増時のEndpointSlicesの変化

トラフィック急増時のEndpointSlices

Kubernetesでは、アプリケーションはどのように互いに通信するのでしょうか?EndpointsとEndpointSliceの具体的な違いは何でしょうか?PodとEndpoints/EndpointSliceの関係はどのようなものでしょうか?APISIXはこれらの機能をどのようにサポートし、どのようにインストールして使用するのでしょうか?この記事ではこれらの質問に焦点を当てます。

Kubernetes内のアプリケーションへのアクセス方法

Service経由

Kubernetesでは、各Podには独自のIPアドレスがあります。通常、Serviceはselectorを使用してPodと接続を確立し、同じDNSサブドメイン名を提供し、ロードバランシングを実現します。さらに、Kubernetesクラスター内のアプリケーションはDNSを使用して互いに通信できます。

Serviceが作成されると、KubernetesはServiceをEndpointsリソースと接続します。ただし、Serviceがセレクターを指定していない場合、Kubernetesは自動的にEndpointsを作成しません。

Endpointsとは何か、Podとの関係は何か

EndpointsはKubernetesのリソースオブジェクトで、etcdに保存され、Serviceに一致する一連のPodのアクセスアドレスへの参照を含みます。したがって、各Serviceは1つのEndpointsリソースしか持つことができません。EndpointsリソースはPodクラスターを監視し、Service内のPodが変更されると同期して更新されます。

  1. 3つのhttpbinレプリケーションをデプロイし、PodのステータスとIP情報を確認します
$ kubectl get pods -o wide
NAME                                 READY   STATUS    RESTARTS        AGE   IP            NODE             NOMINATED NODE   READINESS GATES
httpbin-deployment-fdd7d8dfb-8sxxq   1/1     Running   0               49m   10.1.36.133   docker-desktop   <none>           <none>
httpbin-deployment-fdd7d8dfb-bjw99   1/1     Running   4 (5h39m ago)   23d   10.1.36.125   docker-desktop   <none>           <none>
httpbin-deployment-fdd7d8dfb-r5nf9   1/1     Running   0               49m   10.1.36.131   docker-desktop   <none>           <none>
  • httpbinサービスを作成し、Endpointsの状態を確認します
$ kubectl get endpoints httpbin
NAME      ENDPOINTS                                      AGE
httpbin   10.1.36.125:80,10.1.36.131:80,10.1.36.133:80   23d

上記の2つの例から、Endpoints内のhttpbinリソースの各ネットワークエンドポイントがPodのIPアドレスと一致していることがわかります。

Endpointsの欠点

  1. Endpointsにはストレージ制限があり、Endpointsリソースが1000以上のエンドポイントを持つ場合、Endpointsコントローラーはエンドポイントを1000に切り捨てます。
  2. 1つのServiceは1つのEndpointsリソースしか持つことができないため、Endpointsリソースは対応するServiceをサポートするすべてのPodのIPアドレスやその他のネットワーク情報を保存する必要があります。その結果、Endpoint APIリソースが巨大になり、リソース内の単一のネットワークエンドポイントが変更されると更新する必要があります。ビジネスが頻繁にエンドポイントの変更を必要とする場合、巨大なAPIリソースが互いに送信され、Kubernetesコンポーネントのパフォーマンスに影響を与えます。

EndpointSlicesとは何か

EndpointSlicesは、Endpointsのよりスケーラブルで拡張可能な代替手段であり、巨大なネットワークエンドポイントを処理する際のパフォーマンス問題を解決します。また、トポロジカルルーティングなどの追加機能のための拡張可能なプラットフォームを提供します。この機能はKubernetes v1.21でサポートされています。

EndpointSlice APIは、シャーディングに似たアプローチでこの問題に対処するために設計されました。単一のEndpointsリソースでServiceのすべてのPod IPを追跡する代わりに、それらを複数の小さなEndpointSlicesに分割します。

デフォルトでは、コントロールプレーンは各EndpointSliceが100以下のエンドポイントを持つように作成および管理します。これは--max-endpoints-per-slice kube-controller-managerフラグで設定でき、最大1000まで設定可能です。

なぜそれが必要なのか

シナリオを考えてみる

2000のPodによってサポートされるServiceがあると仮定します。これにより、1.0 MBのEndpointsリソースが生成される可能性があります。本番環境では、このServiceがローリングアップデートやエンドポイントの移行を行う場合、Endpointリソースが頻繁に更新されます。ローリングアップデートにより、すべてのPodが置き換えられることを考えてみてください。etcdのリクエストサイズの最大制限により、KubernetesはEndpointsの最大エンドポイント数を1000に設定しています。1000以上のエンドポイントがある場合、Endpointsリソースは追加のネットワークエンドポイントへの参照を持ちません。

特別なニーズによりServiceが複数のローリングアップデートを必要とする場合、巨大なAPIリソースオブジェクトがKubernetesコンポーネント間で転送され、Kubernetesコンポーネントのパフォーマンスに大きな影響を与えます。

EndpointSliceを使用するとどうなるか

2000のPodによってサポートされるServiceがあり、設定で各EndpointSlicesに100のエンドポイントを割り当てると、20のEndpointSlicesが生成されます。これにより、Podが追加または削除されると、1つの小さなEndpointSliceのみが更新されます。明らかに、これはスケーラビリティとネットワーク拡張性の大幅な改善です。各Serviceが自動スケーリングを必要とする場合、Serviceはより多くのPodをデプロイし、トラフィック急増時に増加するトラフィックを処理するためにEndpointsリソースが頻繁に更新され、その違いがより顕著になります。さらに重要なのは、ServiceのすべてのPod IPを単一のリソースに保存する必要がなくなったため、etcdに保存されるオブジェクトのサイズ制限を心配する必要がなくなります。

EndpointsとEndpointSliceの結論

EndpointSliceはKubernetes v1.21以降でサポートされているため、すべての結論はKubernetes v1.21を参照しています。

Endpointsの使用例:

  • 自動スケーリングが必要だが、Podの数が比較的少なく、リソースの転送が大きなネットワークトラフィックや追加の処理ニーズを引き起こさない場合。
  • 自動スケーリングが必要なく、Podの数が大きくならない場合。ただし、Podの数が固定されていても、Serviceはローリングアップデートや障害を省略できません。

EndpointSliceの使用例:

  • 自動スケーリングが必要で、Podの数が非常に多い場合(数百のPod)。
  • Endpointsの最大エンドポイント数が1000であるため、1000以上のエンドポイントを持つPodはEndpointSliceを使用する必要があります。

APISIX Ingress Controllerでの実践

APISIX Ingress Controllerは、EndpointsまたはEndpointSliceリソースの変更を監視することで、ロードバランシングとヘルスチェックを実現します。Kubernetes v1.16+をサポートするために、APISIX Ingress Controllerはデフォルトでインストール時にEndpointsを使用します。

クラスターのバージョンがKubernetes v1.21+の場合、APISIX Ingress Controllerのインストール時にwatchEndpointSlice=trueフラグを指定してEndpointSlice機能をサポートする必要があります。

注意: Kubernetes v1.21+のクラスターでは、Endpointslice機能を使用することを推奨します。そうでない場合、Podの数が--max-endpoints-per-sliceフラグの設定値を超えると、APISIX Ingress ControllerがEndpointsリソースオブジェクトを監視しているため、設定が失われる可能性があります。

20のPodレプリケーションでサポートされるServiceの作成

KubernetesでhttpbinアプリケーションServiceを設定し、20のPodレプリケーションを作成します

  • - kubectl apply -f httpbin-deploy.yaml
# htppbin-deploy.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: httpbin-deployment
spec:
  replicas: 20
  selector:
    matchLabels:
      app: httpbin-deployment
  strategy:
    rollingUpdate:
      maxSurge: 50%
      maxUnavailable: 1
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: httpbin-deployment
    spec:
      terminationGracePeriodSeconds: 0
      containers:
        - livenessProbe:
            failureThreshold: 3
            initialDelaySeconds: 2
            periodSeconds: 5
            successThreshold: 1
            tcpSocket:
              port: 80
            timeoutSeconds: 2
          readinessProbe:
            failureThreshold: 3
            initialDelaySeconds: 2
            periodSeconds: 5
            successThreshold: 1
            tcpSocket:
              port: 80
            timeoutSeconds: 2
          image: "kennethreitz/httpbin:latest"
          imagePullPolicy: IfNotPresent
          name: httpbin-deployment
          ports:
            - containerPort: 80
              name: "http"
              protocol: "TCP"

---
apiVersion: v1
kind: Service
metadata:
  name: httpbin
spec:
  selector:
    app: httpbin-deployment
  ports:
    - name: http
      port: 80
      protocol: TCP
      targetPort: 80
  type: ClusterIP

APISIX Ingress経由でのプロキシ

  • Helmを使用してAPISIX Ingress Controllerをインストールします

--set ingress-controller.config.kubernetes.watchEndpointSlice=trueを使用して、EndpointSlice機能のサポートを有効にします。

helm repo add apisix https://charts.apiseven.com
helm repo add bitnami https://charts.bitnami.com/bitnami
helm repo update

kubectl create ns ingress-apisix
helm install apisix apisix/apisix \
  --set gateway.type=NodePort \
  --set ingress-controller.enabled=true \
  --namespace ingress-apisix \
  --set ingress-controller.config.apisix.serviceNamespace=ingress-apisix \
  --set ingress-controller.config.kubernetes.watchEndpointSlice=true
  • CRDリソースを使用してプロキシします

ユーザーはAPISIX Ingress ControllerでのEndpointsとEndpointSlice機能のサポートに気づくことはなく、それらの設定は同じです

apiVersion: apisix.apache.org/v2
kind: ApisixRoute
metadata:
  name: httpbin-route
spec:
  http:
    - name: rule
      match:
        hosts:
          - httpbin.org
        paths:
          - /get
      backends:
        - serviceName: httpbin
          servicePort: 80
  • APISIX Podを確認すると、APISIXのupstreamオブジェクトのnodesフィールドに20のPodのIPアドレスが含まれていることがわかります。
kubectl exec -it ${Pod for APISIX} -n ingress-apisix -- curl "http://127.0.0.1:9180/apisix/admin/upstreams" -H 'X-API-KEY: edd1c9f034335f136f87ad84b625c8f1'
{
    "action": "get",
    "count": 1,
    "node": {
        "key": "\/apisix\/upstreams",
        "nodes": [
            {
                "value": {
                    "hash_on": "vars",
                    "desc": "Created by apisix-ingress-controller, DO NOT modify it manually",
                    "pass_host": "pass",
                    "nodes": [
                        {
                            "weight": 100,
                            "host": "10.1.36.100",
                            "priority": 0,
                            "port": 80
                        },
                        {
                            "weight": 100,
                            "host": "10.1.36.101",
                            "priority": 0,
                            "port": 80
                        },
                        {
                            "weight": 100,
                            "host": "10.1.36.102",
                            "priority": 0,
                            "port": 80
                        },
                        {
                            "weight": 100,
                            "host": "10.1.36.103",
                            "priority": 0,
                            "port": 80
                        },
                        {
                            "weight": 100,
                            "host": "10.1.36.104",
                            "priority": 0,
                            "port": 80
                        },
                        {
                            "weight": 100,
                            "host": "10.1.36.109",
                            "priority": 0,
                            "port": 80
                        },
                        {
                            "weight": 100,
                            "host": "10.1.36.92",
                            "priority": 0,
                            "port": 80
                        }
                        ... // 以下の13ノードは無視
                        // 10.1.36.118
                        // 10.1.36.115
                        // 10.1.36.116
                        // 10.1.36.106
                        // 10.1.36.113
                        // 10.1.36.111
                        // 10.1.36.108
                        // 10.1.36.114
                        // 10.1.36.107
                        // 10.1.36.110
                        // 10.1.36.105
                        // 10.1.36.112
                        // 10.1.36.117
                    ],
                    "labels": {
                        "managed-by": "apisix-ingress-controller"
                    },
                    "type": "roundrobin",
                    "name": "default_httpbin_80",
                    "scheme": "http"
                },
                "key": "\/apisix\/upstreams\/5ce57b8e"
            }
        ],
        "dir": true
    }
}
  • EndpointSliceのネットワークエンドポイントと一致します
addressType: IPv4
apiVersion: discovery.k8s.io/v1
endpoints:
- addresses:
  - 10.1.36.92
  ...
- addresses:
  - 10.1.36.100
  ...
- addresses:
  - 10.1.36.104
  ...
- addresses:
  - 10.1.36.102
  ...
- addresses:
  - 10.1.36.101
  ...
- addresses:
  - 10.1.36.103
  ...
- addresses:
  - 10.1.36.109
  ...
- addresses:
  - 10.1.36.118
  ...
- addresses:
  - 10.1.36.115
  ...
- addresses:
  - 10.1.36.116
  ...
- addresses:
  - 10.1.36.106
  ...
- addresses:
  - 10.1.36.113
  ...
- addresses:
  - 10.1.36.111
  ...
- addresses:
  - 10.1.36.108
  ...
- addresses:
  - 10.1.36.114
  ...
- addresses:
  - 10.1.36.107
  ...
- addresses:
  - 10.1.36.110
  ...
- addresses:
  - 10.1.36.105
  ...
- addresses:
  - 10.1.36.112
  ...
- addresses:
  - 10.1.36.117
  ...
kind: EndpointSlice
metadata:
  labels:
    endpointslice.kubernetes.io/managed-by: endpointslice-controller.k8s.io
    kubernetes.io/service-name: httpbin
  name: httpbin-dkvtr
  namespace: default
ports:
- name: http
  port: 80
  protocol: TCP

結論

この記事では、Kubernetesが大量のPodをデプロイする必要があるシナリオと、それに伴う問題について紹介しました。また、EndpointsとEndpointSliceの違いを比較し、APISIX Ingress Controllerのインストール時にEndpointSlice機能を有効にする方法を紹介しました。クラスターのバージョンがKubernetes v1.21+の場合、APISIX Ingress Controllerのインストール時にEndpointSlice機能を有効にすることを推奨します。これにより、設定の損失を防ぎ、--max-endpoints-per-sliceフラグの設定値を心配する必要がなくなります。

Tags: