Amazon Web Services ブログ

Karpenter による Kubernetes のスケーリング: Pod Affinity と Volume Topology Awareness による高度なスケジューリング

この記事は Scaling Kubernetes with Karpenter: Advanced Scheduling with Pod Affinity and Volume Topology Awareness (記事公開日: 2022 年 7 月 18 日) を翻訳したものです。

この記事は、SUSE の Principal Technical Evangelist、AWS の Container Hero、そして HashiCorp Ambassador であるLukonde Mwila と共同で執筆したものです。

イントロダクション

クラウドネイティブのテクノロジーはますますユビキタスになってきており、Kubernetes はこのムーブメントの最前線にいます。現在、Kubernetes は様々な業種の組織での採用が広がっています。Kubernetes を適切に導入することで、これらの組織のワークロードにおいて、高い可用性、スケーラビリティ、回復力を実現できます。Kubernetes をクラウドコンピューティングの特徴である比類のないスケーラビリティ、弾力性と組み合わせることで、コンテナ化されたアプリケーションの回復力と可用性を強化することができます。

この紹介記事 (日本語翻訳:Karpenter のご紹介 – オープンソースの高性能 Kubernetes Cluster Autoscaler) で詳しく説明していますが、Karpenter の目的は、クラスターのワークロードが必要な時に必要なだけのコンピュートを利用できるようにすることです。

直近のアップデートで Karpenter は、Pod Affinity、Pod Anti-affinity、Pod Topology Spread、Node Affinity、nodeSelector、Resource Requests など、より高度なスケジューリング制約のサポートを追加しています。この記事では特に podAffinitypodAntiAffinity 、Volume Topology Awareness について掘り下げ、それらが最も適しているユースケースについて詳しく説明します。

前提

この記事のサンプルを実行するためには、Karpenter がインストールされた AWS 上の Kubernetes クラスターが必要です。今回は検証のために Amazon EKS を使用します。Karpenter をアドオンとして、Terraform EKS blueprints を活用することで、 EKS クラスターの構築プロセスを自動化できます。

Pod Affinity と Pod Anti-affinity のスケジューリング

スケジューリング制約の Pod への適用は、Pod と特定の Node、または Pod 間の関係を確立することで実現されます。後者は Inter-Pod Affinity として知られています。Inter-Pod Affinity を使用することで、他の Pod との関係に基づいて、どの Pod をどの Node に配置するかを決定する際のスケジューラーのアプローチに情報を提供するルールを割り当てることができます。Inter-Pod Affinity には、Pod Affinity と Pod Anti-affinity の両方が含まれます。

Node Affinity のように、要件に応じてrequiredDuringSchedulingIgnoredDuringExecutionpreferredDuringSchedulingIgnoredDuringExecution のルールを使用して実行することができます。その名のとおり required (必須) と preferred (優先) は、スケジューリング制約がどの程度厳密か、あるいは柔軟かを表す言葉です。Pod をスケジューリングする基準が必須のルールとして設定されたら、Kubernetes は Pod がそれを満たす Node に配置されることを保証します。同様に、優先ルールを含む Pod は最も高い優先度にマッチする Node にスケジュールされます。

Pod Affinity: podAffinity ルールは、ラベルに基づく相互に関連性のある Pod をマッチングするようスケジューラーに通知します。新しい Pod が作成されたら、スケジューラーは新しい Pod のラベルセレクターのラベル指定に一致する Pod が動いている Node の探索を引き受けます。

Pod Anti-affinity: 対照的に podAntiAffinity ルールは一致するラベルの条件を満たした場合に、同じ Node 上で特定の Pod が実行されることを防ぎます。

これらのルールは様々な場面で役に立ちます。例えば podAffinity は、同じ AZ あるいは Node 内に Pod を配置し、あらゆる内部的な依存関係をサポートしたり、サービス間のネットワークレイテンシを削減する際に役に立ちます。一方で podAntiAffinity は、高可用性を目的として、 Pod を AZ あるいは Node に分散することで単一障害点を取り除く際に役に立ちます。このようなユースケースの場合、Pod Anti-affinity のために推奨される Topology Spread Constraints は、ゾーンまたはホスト名にすることができます。これは、クラスターノードの検索範囲を決定する topologyKey プロパティを使って実装することができます。topologyKey は、Node に付与されたラベルのキーです。

podAntiAffinity の実装の例としては、CoreDNS の Deployment が挙げられます。Deployment リソース は、podAntiAffinity ポリシーを指定しています。これは、スケジューラーが HA と VPC DNS スロットリングを避けるために、異なる Node で CoreDNS Pod を起動するからです。Deployment の Pod Anti-affinity の topologyKey が hostname を指定していることがわかります。さらに podAntiAffinity を指定すると、排他的な Node 上で Pod あるいは Pod のリソースのセットを隔離することも、一部の Pod が他の Pod のパフォーマンスに干渉するリスクを軽減することもできます。

Karpenter を使うことで、追加のインフラストラクチャの設定をすることなく、ワークロードの規模に応じて、クラスター用にプロビジョニングされた新しいコンピュートが Pod Affinity ルールを満たすようにすることができます。Karpenter はスケジュールされなかった Pod を追跡し、リソースのマニフェストで定義された必須あるいは推奨の Affinity ルールに応じて、コンピュートリソースをプロビジョニングします。

Karpenter と Pod Affinity の例

この例では、同じ AZ (アベイラビリティゾーン) の Node に Pod をスケジューリングすることを必須とする podAffinity ルールを指定した Deployment リソースを作成します。このプロセスでは、Karpenter はスケジューリングする必要がある Pod の要件を解釈し、それらの Affinity ルールを最適な形で満たすことができる Node をプロビジョニングします。

出発点として、クラスターに Karpenter Provisioner をインストールする必要があります。Provisioner は、設定の詳細とパラメータ (Node Type、ラベル、Taint、タグ、kubelet の構成、Resource Limits、サブネットとセキュリティグループアソシエーションによるクラスターへの接続など) を指定する CRD です。この例で使用される Provisioner マニフェストは以下のとおりです。

apiVersion: karpenter.sh/v1alpha5
kind: Provisioner
metadata:
  name: default
spec:
  # Requirements that constrain the parameters of provisioned nodes.
  # These requirements are combined with pod.spec.affinity.nodeAffinity rules.
  # Operators { In, NotIn } are supported to enable including or excluding values
  requirements:
    - key: "karpenter.sh/capacity-type" # If not included, the webhook for the AWS cloud provider will default to on-demand
      operator: In
      values: ["spot", "on-demand"]
  # Resource limits constrain the total size of the cluster.
  # Limits prevent Karpenter from creating new instances once the limit is exceeded.
  limits:
    resources:
      cpu: 1000 
      memory: 1000Gi
  provider:
    subnetSelector:
      karpenter.sh/discovery: alpha
    securityGroupSelector:
      karpenter.sh/discovery: alpha
    tags:
      karpenter.sh/discovery: alpha
  ttlSecondsAfterEmpty: 30

ターミナルで kubectl get nodes コマンドを使用し、クラスターのすべての Node を取得するところから始めます。
これにより、じきにクラスターにデプロイするアプリケーションに対応して Karpenter が新しい Node を起動する前に、すでにある Node の状態を把握することができます。

NAME                                       STATUS   ROLES    AGE    VERSION
ip-10-0-0-126.eu-west-1.compute.internal   Ready    <none>   2d2h   v1.21.12-eks-5308cf7
ip-10-0-0-193.eu-west-1.compute.internal   Ready    <none>   2d2h   v1.21.12-eks-5308cf7
ip-10-0-0-35.eu-west-1.compute.internal    Ready    <none>   47h    v1.21.12-eks-5308cf7
ip-10-0-1-12.eu-west-1.compute.internal    Ready    <none>   2d2h   v1.21.12-eks-5308cf7
ip-10-0-1-73.eu-west-1.compute.internal    Ready    <none>   2d2h   v1.21.12-eks-5308cf7
ip-10-0-3-30.eu-west-1.compute.internal    Ready    <none>   2d2h   v1.21.12-eks-5308cf7

その後、以下のマニフェストを使って Deployment リソースの作成にすすみます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: inflate
spec:
  replicas: 8
  selector:
    matchLabels:
      app: inflate
  template:
    metadata:
      labels:
        app: inflate
    spec:
      affinity:
        podAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
          - labelSelector:
              matchExpressions:
              - key: app
                operator: In
                values:
                - inflate
            topologyKey: "topology.kubernetes.io/zone"
      terminationGracePeriodSeconds: 0
      containers:
        - name: inflate
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
          resources:
            requests:
              cpu: 1

Karpenter はスケジュールされてない Pod を検知し、この Deployment の Inter-Pod Affinity の要件を満たす Node をプロビジョニングします。

NAME                                       STATUS   ROLES    AGE    VERSION
ip-10-0-0-126.eu-west-1.compute.internal   Ready    <none>   2d3h   v1.21.12-eks-5308cf7
ip-10-0-0-193.eu-west-1.compute.internal   Ready    <none>   2d3h   v1.21.12-eks-5308cf7
ip-10-0-0-35.eu-west-1.compute.internal    Ready    <none>   2d     v1.21.12-eks-5308cf7
ip-10-0-1-12.eu-west-1.compute.internal    Ready    <none>   2d3h   v1.21.12-eks-5308cf7
ip-10-0-1-233.eu-west-1.compute.internal   Ready    <none>   32m    v1.21.12-eks-5308cf7 // New node
ip-10-0-1-73.eu-west-1.compute.internal    Ready    <none>   2d3h   v1.21.12-eks-5308cf7
ip-10-0-3-30.eu-west-1.compute.internal    Ready    <none>   2d3h   v1.21.12-eks-5308cf7

新しく作成された Node のホスト名は ip-10-0-1-233.eu-west-1.compute.internal です。

以下は、新しい Node の詳細の一部を抜粋したものです。

Name:               ip-10-0-1-233.eu-west-1.compute.internal
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=c6i.2xlarge
                    beta.kubernetes.io/os=linux
                    failure-domain.beta.kubernetes.io/region=eu-west-1
                    failure-domain.beta.kubernetes.io/zone=eu-west-1b
                    karpenter.sh/capacity-type=spot
                    karpenter.sh/provisioner-name=default
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=ip-10-0-1-233.eu-west-1.compute.internal
                    kubernetes.io/os=linux
                    node.kubernetes.io/instance-type=c6i.2xlarge
                    topology.ebs.csi.aws.com/zone=eu-west-1b
                    topology.kubernetes.io/region=eu-west-1
                    topology.kubernetes.io/zone=eu-west-1b
Annotations:        csi.volume.kubernetes.io/nodeid: {"ebs.csi.aws.com":"i-00725be7dfa8ef814"}
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
                    ...

次に、適切なラベル (app=inflate) を使用して関連する Pod を取得し、Pod がどのようにスケジュールされたかを確認することができます。

kubectl get pods -l app=inflate -o wide
NAME                       READY   STATUS    RESTARTS   AGE    IP           NODE                                       NOMINATED NODE   READINESS GATES
inflate-588d96b7f7-2lmzb   1/1     Running   0          104s   10.0.1.197   ip-10-0-1-233.eu-west-1.compute.internal   <none>           <none>
inflate-588d96b7f7-gv68n   1/1     Running   0          104s   10.0.1.11    ip-10-0-1-233.eu-west-1.compute.internal   <none>           <none>
inflate-588d96b7f7-jffqh   1/1     Running   0          104s   10.0.1.248   ip-10-0-1-233.eu-west-1.compute.internal   <none>           <none>
inflate-588d96b7f7-ktjrg   1/1     Running   0          104s   10.0.1.81    ip-10-0-1-12.eu-west-1.compute.internal    <none>           <none>
inflate-588d96b7f7-mhjrl   1/1     Running   0          104s   10.0.1.133   ip-10-0-1-233.eu-west-1.compute.internal   <none>           <none>
inflate-588d96b7f7-vhjl7   1/1     Running   0          104s   10.0.1.21    ip-10-0-1-233.eu-west-1.compute.internal   <none>           <none>
inflate-588d96b7f7-zb7l8   1/1     Running   0          104s   10.0.1.18    ip-10-0-1-73.eu-west-1.compute.internal    <none>           <none>
inflate-588d96b7f7-zz2g4   1/1     Running   0          104s   10.0.1.207   ip-10-0-1-233.eu-west-1.compute.internal   <none>           <none>

このように、6 つの Pod が新しい Node ip-10-0-1-233.eu-west-1.compute.internal にスケジュールされており、一方で他の 2 つは podAffinity ルールの topologyKey にしたがって同一 AZ (eu-west-1b) にスケジュールされていることがわかるでしょう。これらの Node はすべて同じ AZ にあるため、同じトポロジーの一部となり、スケジューリングの要件を満たすことができます。

Karpenter と Pod Anti-affinity の例

2 つ目の例では、podAntiAffinity ルールを適用し、AZ に基づいてクラスター内の異なる Node に Pod を望ましくスケジュールします。前回と同様に、Karpenter は Pod の要件を読み取り、podAntiAffinity の設定をサポートする Node を起動します。

前の例と同様に、クラスター内のすべての Node を取得するところから始めます。

NAME                                       STATUS   ROLES    AGE    VERSION
ip-10-0-0-126.eu-west-1.compute.internal   Ready    <none>   2d2h   v1.21.12-eks-5308cf7
ip-10-0-0-193.eu-west-1.compute.internal   Ready    <none>   2d2h   v1.21.12-eks-5308cf7
ip-10-0-0-35.eu-west-1.compute.internal    Ready    <none>   47h    v1.21.12-eks-5308cf7
ip-10-0-1-12.eu-west-1.compute.internal    Ready    <none>   2d2h   v1.21.12-eks-5308cf7
ip-10-0-1-73.eu-west-1.compute.internal    Ready    <none>   2d2h   v1.21.12-eks-5308cf7
ip-10-0-3-30.eu-west-1.compute.internal    Ready    <none>   2d2h   v1.21.12-eks-5308cf7

その後、Deployment リソースの作成にすすみます。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: inflate
spec:
  replicas: 8
  selector:
    matchLabels:
      app: inflate
  template:
    metadata:
      labels:
        app: inflate
    spec:
      affinity:
        podAntiAffinity:
          preferredDuringSchedulingIgnoredDuringExecution:
          - weight: 50
            podAffinityTerm:
              labelSelector:
                matchExpressions:
                - key: app
                  operator: In
                  values:
                  - inflate
              topologyKey: "topology.kubernetes.io/zone"
      terminationGracePeriodSeconds: 0
      containers:
        - name: inflate
          image: public.ecr.aws/eks-distro/kubernetes/pause:3.2
          resources:
            requests:
              cpu: 1

Pod の要件に応じて Karpenter は新しい Node を起動します。

NAME                                       STATUS   ROLES    AGE    VERSION
ip-10-0-0-126.eu-west-1.compute.internal   Ready    <none>   2d3h   v1.21.12-eks-5308cf7
ip-10-0-0-193.eu-west-1.compute.internal   Ready    <none>   2d3h   v1.21.12-eks-5308cf7
ip-10-0-0-35.eu-west-1.compute.internal    Ready    <none>   2d     v1.21.12-eks-5308cf7
ip-10-0-1-12.eu-west-1.compute.internal    Ready    <none>   2d3h   v1.21.12-eks-5308cf7
ip-10-0-1-69.eu-west-1.compute.internal    Ready    <none>   73s    v1.21.12-eks-5308cf7
ip-10-0-1-73.eu-west-1.compute.internal    Ready    <none>   2d3h   v1.21.12-eks-5308cf7
ip-10-0-3-30.eu-west-1.compute.internal    Ready    <none>   2d3h   v1.21.12-eks-5308cf7

新しく作成された Node のホスト名は ip-10-0-1-69.eu-west-1.compute.internal です。

以下は、新しい Node の詳細の一部を抜粋したものです。

Name:               ip-10-0-1-69.eu-west-1.compute.internal
Roles:              <none>
Labels:             beta.kubernetes.io/arch=amd64
                    beta.kubernetes.io/instance-type=c5.xlarge
                    beta.kubernetes.io/os=linux
                    failure-domain.beta.kubernetes.io/region=eu-west-1
                    failure-domain.beta.kubernetes.io/zone=eu-west-1b
                    karpenter.sh/capacity-type=spot
                    karpenter.sh/provisioner-name=default
                    kubernetes.io/arch=amd64
                    kubernetes.io/hostname=ip-10-0-1-69.eu-west-1.compute.internal
                    kubernetes.io/os=linux
                    node.kubernetes.io/instance-type=c5.xlarge
                    topology.ebs.csi.aws.com/zone=eu-west-1b
                    topology.kubernetes.io/region=eu-west-1
                    topology.kubernetes.io/zone=eu-west-1b
Annotations:        csi.volume.kubernetes.io/nodeid: {"ebs.csi.aws.com":"i-0617fbc688949e367"}
                    node.alpha.kubernetes.io/ttl: 0
                    volumes.kubernetes.io/controller-managed-attach-detach: true
...

前回と同様に、関連するラベルが付与された Pod を取得します。

kubectl get pods -l app=inflate -o wide
NAME                       READY   STATUS    RESTARTS   AGE    IP           NODE                                       NOMINATED NODE   READINESS GATES
inflate-54cd576f79-88rt4   1/1     Running   0          101s   10.0.0.128   ip-10-0-0-35.eu-west-1.compute.internal    <none>           <none>
inflate-54cd576f79-bpcc7   1/1     Running   0          101s   10.0.0.49    ip-10-0-0-126.eu-west-1.compute.internal   <none>           <none>
inflate-54cd576f79-jdcfc   1/1     Running   0          101s   10.0.1.247   ip-10-0-1-12.eu-west-1.compute.internal    <none>           <none>
inflate-54cd576f79-nh8zg   1/1     Running   0          101s   10.0.1.120   ip-10-0-1-69.eu-west-1.compute.internal    <none>           <none>
inflate-54cd576f79-qm8dc   1/1     Running   0          101s   10.0.1.236   ip-10-0-1-69.eu-west-1.compute.internal    <none>           <none>
inflate-54cd576f79-vvgkg   1/1     Running   0          101s   10.0.1.18    ip-10-0-1-73.eu-west-1.compute.internal    <none>           <none>
inflate-54cd576f79-vzzjx   1/1     Running   0          101s   10.0.0.123   ip-10-0-0-193.eu-west-1.compute.internal   <none>           <none>
inflate-54cd576f79-xtwkr   1/1     Running   0          101s   10.0.1.147   ip-10-0-1-69.eu-west-1.compute.internal    <none>           <none>

3 つの Pod 以外のすべての Pod は、Affinity ルールの指定に従って、選択したリージョン (eu-west-1) の異なる AZ にまたがって他の Node に分散されます。

次に、Karpenter をもうひとつの高度なスケジューリング技術である Volume Topology Awareness と一緒に使うことを検討します。

Volume Topology を意識したスケジューリング

Volume Topology Awareness 以前は、Node への Pod のスケジューリングと動的なボリュームのプロビジョニングのプロセスは独立していました。ご想像のとおり、ワークロードに予測不可能な結果が生じるという課題をもたらしていました。例えば、Persistent Volume Claim を作成すると、特定のAZ (eu-west-1a) にボリュームが動的に作成されます。一方でそのボリュームを使用する必要がある Pod が別の AZ (eu-west-1b) の Node に配置されると、結果的に Pod は起動に失敗します。

永続的なデータを提供するストレージボリュームに依存するステートフルなワークロードにとって、これは特に問題があります。然るべき AZ にストレージボリュームを手動でプロビジョニングすることは、非効率的かつ動的なプロビジョニングとは直感的に異なります。そこで登場するのが Topology Awareness です。

Topology Awareness は、Pod がトポロジーの要件 (今回のケースではストレージボリューム) を満たすよう Node に配置されることを担保することで、動的なプロビジョニングを補完します。Topology Awareness のスケジューリングのゴールは、 Topology リソースとワークロードの連携を提供することです。それによって、より高い信頼性と予測可能な結果を得ることができます。これは kubelet のコンポーネントであるTopology Manager によって処理されます。つまり Topology Manager は、ステートフルなワークロードと動的に作成された Persistent Volume が正しい AZ に配置されることを担保します。

Volume Topology Awareness を使用するには、 StorageClass の volumeBindingModeWaitForFirstConsumer に設定する必要があります。このプロパティは、Persistent Volume を使用する Pod によって Persistent Volume Claim が作成されるまで、Persistent Volume のプロビジョニングを遅延させます。

スケーリングイベントでは、Karpenter、スケジューラー、Topology Manager がうまく連携して動作します。これらの組み合わせにより、適切なコンピュートリソースをプロビジョニングするプロセスを最適化し、スケジュールされたワークロードを動的に作成された Persistent Volume と一致させます。

これらの技術は複数の AZ でステートフルなワークロードを実行し、信頼性のあるスケールを実現します。クラスター内のアプリケーションやデータベースをゾーンに分散させ、AZ が影響を受けた場合でも単一障害点になるのを防ぐことができます。Elastic Block Store (EBS) が AZ 固有であることを考慮した場合、ワークロードは再接続が成功したときに最初にスケジュールされたのと同じ AZ にプロビジョニングされるよう、nodeAffinity で設定する必要があります。

Karpenter と Volume Topology を考慮した例

この例では、 volumeBindingModeWaitForFirstConsumer に設定されているStorageClass を使用して、20 個のレプリカを持つアプリケーションの StatefulSet を作成し、それぞれに Persistent Volume Claim を設定します。さらに、ワークロードのnodeAffinity は、eu-west-1a AZ のトポロジーリソースにスケジュールするように指定します。

デフォルトの StorageClass を確認するためには kubectl get storageclass コマンドを実行します。

NAME            PROVISIONER             RECLAIMPOLICY   VOLUMEBINDINGMODE      ALLOWVOLUMEEXPANSION   AGE
gp2 (default)   kubernetes.io/aws-ebs   Delete          WaitForFirstConsumer   false                  3d11h

次に、それぞれの AZ の Node を取得します。

kubectl get nodes -l topology.kubernetes.io/zone=eu-west-1a を実行すると、以下の出力が得られます。

NAME                                       STATUS   ROLES    AGE     VERSION
ip-10-0-0-126.eu-west-1.compute.internal   Ready       4d23h   v1.21.12-eks-5308cf7
ip-10-0-0-193.eu-west-1.compute.internal   Ready       4d23h   v1.21.12-eks-5308cf7
ip-10-0-0-35.eu-west-1.compute.internal    Ready       4d21h   v1.21.12-eks-5308cf7

一方で、kubectl get nodes -l topology.kubernetes.io/zone=eu-west-1b を実行すると、以下の出力が得られます。

NAME                                      STATUS   ROLES    AGE     VERSION
ip-10-0-1-12.eu-west-1.compute.internal   Ready       4d23h   v1.21.12-eks-5308cf7
ip-10-0-1-73.eu-west-1.compute.internal   Ready       4d23h   v1.21.12-eks-5308cf7
ip-10-0-3-30.eu-west-1.compute.internal   Ready       4d23h   v1.21.12-eks-5308cf7

Node の構成が決まったら、アプリケーションのための StatefulSet と付随する LoadBalancer Service の作成にすすみます。

apiVersion: v1
kind: Service
metadata:
  name: express-nodejs-svc
spec:
  selector:
    app: express-nodejs
  type: LoadBalancer
  ports:
  - protocol: TCP
    port: 8080
    targetPort: 8080
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: express-nodejs
spec:   
  serviceName: express-nodejs-svc
  replicas: 20
  selector:
    matchLabels:
      app: express-nodejs
  template:
    metadata:
      labels:
        app: express-nodejs
    spec:
      affinity:
        nodeAffinity:
          requiredDuringSchedulingIgnoredDuringExecution:
            nodeSelectorTerms:
            - matchExpressions:
              - key: topology.kubernetes.io/zone
                operator: In
                values:
                - eu-west-1a
      containers:
      - name: express-nodejs
        image: lukondefmwila/express-test:1.1.4
        resources:
          requests:
            memory: "512Mi"
            cpu: "500m"
          limits:
            memory: "512Mi"
            cpu: "500m"
        ports:
        - containerPort: 8080
          name: express-nodejs
        volumeMounts:
        - name: express-nodejs
          mountPath: /data
  volumeClaimTemplates:
    - metadata:
        name: express-nodejs
      spec:
        accessModes: [ "ReadWriteOnce" ]
        storageClassName: gp2
        resources:
          requests:
            storage: 10Gi

アプリケーションがデプロイされた後、Persistent Volume は StatefulSet のレプリカからの要求に応じて動的に作成されます。各々は適切な AZ に作成され、結果的に各 Pod レプリカの作成が完了します。これに併せて、Karpenter はStatefulSet のコンピュート要件を満たすために eu-west-1a に新しい Node をプロビジョニングします。

ここで kubectl get nodes -l topology.kubernetes.io/zone=eu-west-1a を実行すると、以下の出力が得られます。

NAME                                       STATUS   ROLES    AGE     VERSION
ip-10-0-0-126.eu-west-1.compute.internal   Ready    <none>   5d      v1.21.12-eks-5308cf7
ip-10-0-0-176.eu-west-1.compute.internal   Ready    <none>   4m18s   v1.21.12-eks-5308cf7
ip-10-0-0-193.eu-west-1.compute.internal   Ready    <none>   5d      v1.21.12-eks-5308cf7
ip-10-0-0-35.eu-west-1.compute.internal    Ready    <none>   4d21h   v1.21.12-eks-5308cf7
ip-10-0-0-4.eu-west-1.compute.internal     Ready    <none>   6m12s   v1.21.12-eks-5308cf7
ip-10-0-0-53.eu-west-1.compute.internal    Ready    <none>   8m8s    v1.21.12-eks-5308cf7
ip-10-0-0-96.eu-west-1.compute.internal    Ready    <none>   2m28s   v1.21.12-eks-5308cf7

このように、Karpenter は以下の追加の Node を起動しました。

  • ip-10-0-0-176.eu-west-1.compute.internal
  • ip-10-0-0-4.eu-west-1.compute.internal
  • ip-10-0-0-53.eu-west-1.compute.internal
  • ip-10-0-0-96.eu-west-1.compute.internal

さらに、作成された Persistent Volume Claim、Persistent Volume、Pod を以下のコードで示したような適切なコマンドを実行して確認しました。

kubectl get pv
NAME                                       CAPACITY   ACCESS MODES   RECLAIM POLICY   STATUS   CLAIM                                      STORAGECLASS   REASON   AGE
pvc-07ad8f9f-e14f-4760-a908-58d2c48c49ac   10Gi       RWO            Delete           Bound    default/express-nodejs-express-nodejs-11   gp2                     9m53s
pvc-0c3a949a-aa2f-4244-9988-d650b409698a   10Gi       RWO            Delete           Bound    default/express-nodejs-express-nodejs-3    gp2                     13m
pvc-32dfc65e-9d10-42ea-a1c1-946f25500766   10Gi       RWO            Delete           Bound    default/express-nodejs-express-nodejs-7    gp2                     11m

pvc-3cf7afb9-8bf4-4a0c-8064-cc97f0cdcbd5   10Gi       RWO            Delete           Bound    default/express-nodejs-express-nodejs-9    gp2                     11m
pvc-3d8d0cb8-c5f4-43ad-a3a4-a95922a13c53   10Gi       RWO            Delete           Bound    default/express-nodejs-express-nodejs-0    gp2
...
kubectl get pvc

NAME                               STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
express-nodejs-express-nodejs-0    Bound    pvc-3d8d0cb8-c5f4-43ad-a3a4-a95922a13c53   10Gi       RWO            gp2            14m
express-nodejs-express-nodejs-1    Bound    pvc-af841cf3-634a-4314-bfd1-a1d6da9d4101   10Gi       RWO            gp2            13m
express-nodejs-express-nodejs-10   Bound    pvc-741c0d72-7311-4063-9d9b-15942f71a9a9   10Gi       RWO            gp2            10m
express-nodejs-express-nodejs-11   Bound    pvc-07ad8f9f-e14f-4760-a908-58d2c48c49ac   10Gi       RWO            gp2            10m
express-nodejs-express-nodejs-12   Bound    pvc-4a91fb0a-778b-42d2-b4b7-33d5d3dc8a87   10Gi       RWO            gp2            9m45s
...
kubectl get pods -l app=express-nodejs -o wide
NAME                READY   STATUS    RESTARTS   AGE     IP           NODE                                       NOMINATED NODE   READINESS GATES
express-nodejs-0    1/1     Running   0          16m     10.0.0.174   ip-10-0-0-193.eu-west-1.compute.internal              

express-nodejs-1    1/1     Running   0          16m     10.0.0.30    ip-10-0-0-35.eu-west-1.compute.internal               
express-nodejs-10   1/1     Running   0          12m     10.0.0.235   ip-10-0-0-53.eu-west-1.compute.internal               
express-nodejs-11   1/1     Running   0          12m     10.0.0.137   ip-10-0-0-53.eu-west-1.compute.internal               
express-nodejs-12   1/1     Running   0          12m     10.0.0.161   ip-10-0-0-4.eu-west-1.compute.internal                
...

クリーンアップ

追加コストが発生することを避けるために、この記事で紹介したサンプルに関連するプロビジョニングされたインフラストラクチャをすべて削除するようにしてください。

結論

この記事では、Karpenter を使った Kubernetes のスケジューリングについて、特に Inter-Pod Affinity と Volume Topology Awareness の高度なスケジューリング技術をサポートするためのハンズオンアプローチを取り上げました。

Karpenter についてさらに学ぶためには、ドキュメントを参照し、Kubernetes Slack ワークスペースのコミュニティチャンネル #karpenter に参加するとよいでしょう。またこのプロジェクトが気に入ったら、GitHub リポジトリに Star をつけてください。

翻訳はプロフェッショナルサービスの後藤が担当しました。原文はこちらです。