テナントの分離 - Amazon EKS

翻訳は機械翻訳により提供されています。提供された翻訳内容と英語版の間で齟齬、不一致または矛盾がある場合、英語版が優先します。

テナントの分離

マルチテナンシーについて考えた場合、共有インフラストラクチャで実行されている他のユーザーやアプリケーションからユーザーやアプリケーションを分離することがよくあります。

Kubernetes は単一テナントオーケストレーターです。つまり、コントロールプレーンの単一インスタンスがクラスター内のすべてのテナント間で共有されます。ただし、マルチテナンシーのアセンブリを作成するために使用できるさまざまな Kubernetes オブジェクトがあります。例えば、名前空間とロールベースのアクセスコントロール (RBAC) を実装して、テナントを相互に論理的に分離できます。同様に、クォータと制限範囲を使用して、各テナントが消費できるクラスターリソースの量を制御できます。それでも、クラスターは強力なセキュリティ境界を提供する唯一のコンストラクトです。これは、 を管理する攻撃者が、そのホストにマウントされたすべてのシークレット、ConfigMapsボリュームを取得できるためです。また、Kubelet を偽装して、ノードの属性を操作したり、クラスター内で横方向に移動したりすることもできます。

以下のセクションでは、Kubernetes などの単一テナントオーケストレーターを使用するリスクを軽減しながら、テナント分離を実装する方法について説明します。

ソフトマルチテナンシー

ソフトマルチテナンシーでは、名前空間、ロールとロールのバインディング、ネットワークポリシーなどのネイティブ Kubernetes コンストラクトを使用して、テナント間で論理的な分離を作成します。例えば、RBAC は、テナントが互いのリソースにアクセスまたは操作できないようにすることができます。クォータと制限範囲は、各テナントが消費できるクラスターリソースの量を制御しますが、ネットワークポリシーは、異なる名前空間にデプロイされたアプリケーションが相互に通信するのを防ぐのに役立ちます。

ただし、これらのコントロールのいずれも、異なるテナントのポッドがノードを共有できないようにします。より強力な分離が必要な場合は、ノードセレクタ、アンチアフィニティルール、テイントや許容値を使用して、異なるテナントのポッドを別々のノードに強制的にスケジュールできます。これは、多くの場合、唯一のテナントノードと呼ばれます。テナントが多い環境では、これはかなり複雑でコストがかかります。

重要

名前空間で実装されたソフトマルチテナンシーでは、名前空間がグローバルスコープ型であるため、フィルタリングされた名前空間のリストをテナントに提供できません。テナントが特定の名前空間を表示できる場合は、クラスター内のすべての名前空間を表示できます。

警告

soft-multi-tenancyでは、テナントはデフォルトでクラスター内で実行されるすべてのサービスの CoreDNS をクエリできます。攻撃者は、クラスター内の任意のポッド ..svc.cluster.localから dig SRV を実行することで、これを悪用する可能性があります。クラスター内で実行されるサービスの DNS レコードへのアクセスを制限する必要がある場合は、 CoreDNS のファイアウォールまたはポリシープラグインの使用を検討してください。詳細については、https://github.com/coredns/policy#kubernetes-metadata-multi-tenancy-policy を参照してください。

Kiosk は、ソフトマルチテナンシーの実装に役立つオープンソースプロジェクトです。これは、以下の機能を提供する一連の CRDs とコントローラーとして実装されています。

  • 共有 Kubernetes クラスター内のテナントを分離するアカウントとアカウントユーザー

  • アカウントユーザーのセルフサービス名前空間プロビジョニング

  • クラスターを共有するときにサービスの品質と公平性を確保するためのアカウント制限

  • 安全なテナント分離とセルフサービス名前空間の初期化のための名前空間テンプレート

Loft は Kiosk と DevSpace の保守者からの商用サービスで、次の機能を追加します。

  • 異なるクラスター内のスペースへのアクセスを許可するためのマルチクラスターアクセス

  • スリープモードは、非アクティブ期間中にスペース内のデプロイをスケールダウンします。

  • GitHub などの OIDC 認証プロバイダーによるシングルサインオン

ソフトマルチテナンシーで対処できる主なユースケースは 3 つあります。

エンタープライズ設定

1 つ目は、「テナント」が従業員、請負業者、または組織によって承認されているという点で半信頼されているエンタープライズ設定です。各テナントは通常、部門やチームなどの管理部門と連携します。

このタイプの設定では、通常、クラスター管理者が名前空間の作成とポリシーの管理を担当します。また、特定の個人が名前空間を監督される委任管理モデルを実装し、デプロイ、サービス、ポッド、ジョブなどのポリシー関連以外のオブジェクトに対して CRUD オペレーションを実行できるようにする場合もあります。

コンテナランタイムによって提供される分離は、この設定内で許容される場合もあれば、ポッドセキュリティのための追加のコントロールで拡張する必要がある場合もあります。また、より厳密な分離が必要な場合は、異なる名前空間内のサービス間の通信を制限する必要があります。

Kubernetes as a Service

対照的に、Kubernetes as a Service (KaaS) を提供する設定では、ソフトマルチテナンシーを使用できます。KaaS では、アプリケーションは一連の PaaS サービスを提供するコントローラーと CRDs のコレクションとともに共有クラスターでホストされます。テナントは Kubernetes API サーバーと直接やり取りし、非ポリシーオブジェクトに対して CRUD オペレーションの実行が許可されます。には、テナントが独自の名前空間を作成および管理できるセルフサービスの要素もあります。このタイプの環境では、テナントは信頼できないコードを実行していると見なされます。

このタイプの環境でテナントを分離するには、多くの場合、厳密なネットワークポリシーとポッドサンドボックスを実装する必要があります。サンドボクシングは、Firecracker やユーザースペースカーネルなどのマイクロ VM 内でポッドのコンテナを実行する場所です。現在、EKS Fargate を使用してサンドボックスポッドを作成できます。

Software as a Service (SaaS)

ソフトマルチテナンシーの最終的なユースケースは、Software-as-a-Service (SaaS) 設定です。この環境では、各テナントはクラスター内で実行されているアプリケーションの特定のインスタンスに関連付けられます。多くの場合、各インスタンスには独自のデータがあり、通常は Kubernetes RBAC から独立した個別のアクセスコントロールを使用します。

他のユースケースとは異なり、SaaS 設定のテナントは Kubernetes API と直接インターフェイスしません。代わりに、SaaS アプリケーションは Kubernetes API とやり取りして、各テナントをサポートするために必要なオブジェクトを作成します。

Kubernetes コンストラクト

これらの各インスタンスでは、次のコンストラクトを使用してテナントを分離します。

名前空間

名前空間は、ソフトマルチテナンシーを実装するための基本です。これにより、クラスターを論理パーティションに分割できます。マルチテナンシーの実装に必要なクォータ、ネットワークポリシー、サービスアカウント、およびその他のオブジェクトは、名前空間に限定されます。

ネットワークポリシー

デフォルトでは、Kubernetes クラスター内のすべてのポッドは相互に通信できます。この動作は、ネットワークポリシーを使用して変更できます。

ネットワークポリシーは、ラベルまたは IP アドレス範囲を使用してポッド間の通信を制限します。テナント間の厳密なネットワーク分離が必要なマルチテナント環境では、ポッド間の通信を拒否するデフォルトのルールと、すべてのポッドが名前解決のために DNS サーバーをクエリできるようにする別のルールから始めることをお勧めします。そうすることで、名前空間内での通信を許可する、より寛容なルールの追加を開始できます。これは、必要に応じてさらに絞り込むことができます。

注記

Amazon VPC CNI は、Kubernetes ネットワークポリシーをサポートし、AWS で Kubernetes を実行するときに機密性の高いワークロードを分離し、不正アクセスから保護できるポリシーを作成できるようになりました。つまり、Amazon EKS クラスター内でネットワークポリシー API のすべての機能を使用できます。このレベルのきめ細かな制御により、最小特権の原則を実装できます。これにより、承認されたポッドのみが相互に通信できるようになります。

重要

ネットワークポリシーは必須ですが、十分ではありません。ネットワークポリシーを適用するには、Calico や Cilium などのポリシーエンジンが必要です。

ロールベースのアクセスコントロール (RBAC)

ロールとロールバインディングは、Kubernetes でロールベースのアクセスコントロール (RBAC) を適用するために使用される Kubernetes オブジェクトです。ロールには、クラスター内のオブジェクトに対して実行できるアクションのリストが含まれます。ロールバインディングは、ロールが適用される個人またはグループを指定します。エンタープライズおよび KaaS 設定では、RBAC を使用して、選択したグループまたは個人によるオブジェクトの管理を許可できます。

クォータ

クォータは、クラスターでホストされているワークロードの制限を定義するために使用されます。クォータでは、ポッドが消費できる CPU とメモリの最大量を指定するか、クラスターまたは名前空間に割り当てることができるリソースの数を制限できます。制限範囲を使用すると、各制限の最小値、最大値、デフォルト値を宣言できます。

共有クラスターでリソースを上書きすると、リソースを最大化できるため、多くの場合有益です。ただし、クラスターへの無制限のアクセスはリソース不足を引き起こし、パフォーマンスの低下やアプリケーションの可用性の低下につながる可能性があります。ポッドのリクエストの設定が低すぎて、実際のリソース使用率がノードの容量を超えた場合、ノードは CPU またはメモリ負荷を経験し始めます。この場合、ポッドが再起動されたり、ノードから削除されたりすることがあります。

これを防ぐには、マルチテナント環境の名前空間にクォータを課して、テナントがクラスターでポッドをスケジュールするときにリクエストと制限を指定するよう強制する計画を立てる必要があります。また、ポッドが消費できるリソースの量を制限することで、潜在的なサービス拒否を軽減します。

クォータを使用して、テナントの支出に合わせてクラスターのリソースを割り当てることもできます。これは、KaaS シナリオで特に役立ちます。

ポッドの優先度とプリエンプション

Pod の優先度とプリエンプションは、他の Pod よりも Pod を重視する場合に役立ちます。例えば、ポッドの優先度では、顧客 A のポッドを顧客 B よりも高い優先度で実行するように設定できます。使用可能な容量が不足している場合、スケジューラは顧客 A の優先度の高いポッドに対応するために、顧客 B の優先度の低いポッドを削除します。これは、顧客がプレミアムの支払いを希望する SaaS 環境で特に便利です。

重要

ポッドの優先度は、優先度の低い他のポッドに望ましくない影響を与える可能性があります。例えば、犠牲者ポッドは正常に終了されますが、PodDisruptionBudget は保証されません。これにより、ポッドのクォーラムに依存する優先度の低いアプリケーションが破損する可能性があります。「先制の制限」を参照してください。

コントロールの軽減

マルチテナント環境の管理者としての主な懸念は、攻撃者が基盤となるホストにアクセスできないようにすることです。このリスクを軽減するには、次のコントロールを検討する必要があります。

コンテナのサンドボックス実行環境

サンドボクシングは、各コンテナを独自の分離された仮想マシンで実行する手法です。ポッドサンドボックスを実行するテクノロジーには、Firecracker や Weave's Firekube などがあります。

Firecracker を EKS でサポートされているランタイムにする作業の詳細については、https://threadreaderapp.com/thread/1238496944684597248.html を参照してください。

オープンポリシーエージェント (OPA) とゲートキーパー

Gatekeeper は、OPA で作成されたポリシーを適用する Kubernetes アドミッションコントローラーです。OPA を使用すると、別のインスタンスまたは他のテナントよりも高い優先度でテナントからポッドを実行するポリシーを作成できます。一般的な OPA ポリシーのコレクションは、このプロジェクトの GitHub リポジトリにあります。

CoreDNS の実験的な OPA プラグインもあり、OPA を使用して CoreDNS から返されたレコードをフィルタリング/制御できます。

キバーノ

Kyverno は、Kubernetes リソースとしてポリシーを使用して設定を検証、変更、生成できる Kubernetes ネイティブポリシーエンジンです。Kyverno は検証に Kustomize スタイルのオーバーレイを使用し、ミューテーションの JSON パッチと戦略的マージパッチをサポートし、柔軟なトリガーに基づいて名前空間間でリソースをクローンできます。

Kyverno を使用して、名前空間を分離し、ポッドセキュリティやその他のベストプラクティスを適用し、ネットワークポリシーなどのデフォルト設定を生成できます。このプロジェクトの GitHub リポジトリには、いくつかの例が含まれています。その他多くの は、Kyverno ウェブサイトのポリシーライブラリに含まれています。

テナントワークロードを特定のノードに分離する

テナントワークロードを特定のノードで実行するように制限することで、ソフトマルチテナンシーモデルの分離を強化できます。このアプローチでは、テナント固有のワークロードは、それぞれのテナント用にプロビジョニングされたノードでのみ実行されます。この分離を実現するために、ネイティブ Kubernetes プロパティ (ノードアフィニティ、テイントと許容範囲) を使用して、ポッドスケジューリングのために特定のノードをターゲットにし、他のテナントからのポッドがテナント固有のノードでスケジュールされないようにします。

パート 1 - ノードアフィニティ

Kubernetes ノードアフィニティは、ノードラベルに基づいて、スケジューリングするノードをターゲットにするために使用されます。ノードアフィニティルールでは、ポッドはセレクタ条件に一致する特定のノードに引き付けられます。以下のポッド仕様では、requiredDuringSchedulingIgnoredDuringExecutionノードアフィニティがそれぞれのポッドに適用されます。その結果、ポッドは というキー/値でラベル付けされたノードをターゲットにしますnode-restriction.kubernetes.io/tenant: tenants-x

... spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: node-restriction.kubernetes.io/tenant operator: In values: - tenants-x ...

このノードアフィニティでは、ラベルはスケジューリング中に必要になりますが、実行時には必要ではありません。基になるノードのラベルが変更された場合、そのラベルが変更されてもポッドは削除されません。ただし、将来のスケジューリングが影響を受ける可能性があります。

警告

のラベルプレフィックスnode-restriction.kubernetes.io/は、Kubernetes で特別な意味を持ちます。EKS クラスターに対して有効になっている NodeRestriction は、このプレフィックスを持つラベルadding/removing/updatingkubeletすることを に禁止します。攻撃者は、これらのラベルを変更kubeletできないkubelet’s credentials to update the node object or modify the system setup to pass these labels into `kubeletため、 を使用できません。このプレフィックスをすべてのポッドからノードへのスケジューリングに使用すると、攻撃者がノードラベルを変更して別のワークロードセットをノードに引き付けるシナリオを防ぐことができます。

ノードアフィニティの代わりに、ノードセレクタを使用できます。ただし、ノードアフィニティはより表現力が高く、ポッドスケジューリング中により多くの条件を考慮できます。相違点とより高度なスケジューリングの選択肢の詳細については、アドバンスト Kubernetes ポッドからノードへのスケジューリングに関するこの CNCF ブログ記事を参照してください。

パート 2 - テイントと許容範囲

ポッドをノードに引き付けることは、この 3 つの部分からなるアプローチの最初の部分にすぎません。このアプローチを機能させるには、ポッドが承認されていないノードにポッドがスケジューリングされないようにする必要があります。不要なポッドや不正なポッドを撃退するために、Kubernetes はノードテイントを使用します。テイントは、ポッドのスケジュールを妨げる条件をノードに配置するために使用されます。以下のテイントでは、 のキーと値のペアを使用しますtenant: tenants-x

... taints: - key: tenant value: tenants-x effect: NoSchedule ...

上記のノード の場合taint、テイントを許容するポッドのみをノードでスケジュールできます。承認されたポッドをノードにスケジュールできるようにするには、以下に示すように、それぞれのポッド仕様にテイントtolerationへの を含める必要があります。

... tolerations: - effect: NoSchedule key: tenant operator: Equal value: tenants-x ...

上記のポッドは、少なくともその特定のテイントのために、ノードでのスケジューリングを停止tolerationされません。テイントは、ノードリソースのプレッシャーなど、特定の条件下でポッドスケジューリングを一時的に停止するために Kubernetes でも使用されます。ノードアフィニティ、テイント、許容度により、目的のポッドを特定のノードに効果的に引き付け、不要なポッドを反発できます。

重要

すべてのノードで を実行するには、特定の Kubernetes ポッドが必要です。これらのポッドの例は、コンテナネットワークインターフェイス (CNI)kube-proxy デーモンセットによって開始されるポッドです。そのために、これらのポッドの仕様には、さまざまなテイントを許容するための非常に寛容な許容範囲が含まれています。これらの許容範囲を変更しないように注意する必要があります。これらの許容値を変更すると、クラスターオペレーションが正しくない可能性があります。さらに、OPA/GatekeeperKyverno などのポリシー管理ツールを使用して、不正なポッドがこれらの許容値を使用することを防ぐ検証ポリシーを記述できます。

パート 3 - ノード選択のポリシーベースの管理

CICD パイプラインでのルールの適用など、ポッド仕様のノードアフィニティと許容範囲の管理に役立つツールがいくつかあります。ただし、分離の適用は Kubernetes クラスターレベルで行う必要があります。この目的のために、ポリシー管理ツールを使用して、リクエストペイロードに基づいてインバウンド Kubernetes API サーバーリクエストを変更し、上記のそれぞれのノードアフィニティルールと許容値を適用できます。

例えば、tentains-x 名前空間宛てのポッドには、tentens-x ノードでスケジューリングできるように、正しいノードアフィニティと許容値でスタンプを付けることができます。Kubernetes Mutating Admission Webhook を使用して設定されたポリシー管理ツールを使用して、ポリシーを使用してインバウンドポッドの仕様を変更できます。ミューテーションは、必要なスケジューリングを可能にするために必要な要素を追加します。ノードアフィニティを追加する OPA/Gatekeeper ポリシーの例を以下に示します。

apiVersion: mutations.gatekeeper.sh/v1alpha1 kind: Assign metadata: name: mutator-add-nodeaffinity-pod annotations: aws-eks-best-practices/description: >- Adds Node affinity - https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#node-affinity spec: applyTo: - groups: [""] kinds: ["Pod"] versions: ["v1"] match: namespaces: ["tenants-x"] location: "spec.affinity.nodeAffinity.requiredDuringSchedulingIgnoredDuringExecution.nodeSelectorTerms" parameters: assign: value: - matchExpressions: - key: "tenant" operator: In values: - "tenants-x"

上記のポリシーは、Kubernetes API サーバーリクエストに適用され、テナント x 名前空間にポッドを適用します。ポリシーはrequiredDuringSchedulingIgnoredDuringExecutionノードアフィニティルールを追加して、ポッドが tenant: tenants-xラベルを持つノードに引き付けられるようにします。

以下に示す 2 番目のポリシーでは、ターゲット名前空間とグループ、種類、バージョンの同じ一致基準を使用して、同じポッド仕様に許容範囲を追加します。

apiVersion: mutations.gatekeeper.sh/v1alpha1 kind: Assign metadata: name: mutator-add-toleration-pod annotations: aws-eks-best-practices/description: >- Adds toleration - https://kubernetes.io/docs/concepts/scheduling-eviction/taint-and-toleration/ spec: applyTo: - groups: [""] kinds: ["Pod"] versions: ["v1"] match: namespaces: ["tenants-x"] location: "spec.tolerations" parameters: assign: value: - key: "tenant" operator: "Equal" value: "tenants-x" effect: "NoSchedule"

上記のポリシーはポッドに固有です。これは、ポリシーの 要素の変更されたlocation要素へのパスが原因です。デプロイやジョブのリソースなど、ポッドを作成するリソースを処理する追加のポリシーを記述できます。リストされているポリシーやその他の例は、このガイドのコンパニオン GitHub プロジェクトで確認できます。

これらの 2 つのミューテーションの結果、ポッドは目的のノードに引き付けられますが、同時に特定のノードテイントによって反発されることはありません。これを検証するには、2 つのkubectl呼び出しからの出力のスニペットを表示して、 というラベルが付いたノードを取得しtenant=tenants-xtenants-x名前空間のポッドを取得します。

kubectl get nodes -l tenant=tenants-x NAME ip-10-0-11-255... ip-10-0-28-81... ip-10-0-43-107... kubectl -n tenants-x get pods -owide NAME READY STATUS RESTARTS AGE IP NODE tenant-test-deploy-58b895ff87-2q7xw 1/1 Running 0 13s 10.0.42.143 ip-10-0-43-107... tenant-test-deploy-58b895ff87-9b6hg 1/1 Running 0 13s 10.0.18.145 ip-10-0-28-81... tenant-test-deploy-58b895ff87-nxvw5 1/1 Running 0 13s 10.0.30.117 ip-10-0-28-81... tenant-test-deploy-58b895ff87-vw796 1/1 Running 0 13s 10.0.3.113 ip-10-0-11-255... tenant-test-pod 1/1 Running 0 13s 10.0.35.83 ip-10-0-43-107...

上記の出力からわかるように、すべてのポッドは というラベルの付いたノードでスケジュールされますtenant=tenants-x。簡単に言うと、ポッドは目的のノードでのみ実行され、他のポッド (必要なアフィニティと許容値なし) は実行されません。テナントワークロードは効果的に分離されます。

変更後のポッド仕様の例を以下に示します。

apiVersion: v1 kind: Pod metadata: name: tenant-test-pod namespace: tenants-x spec: affinity: nodeAffinity: requiredDuringSchedulingIgnoredDuringExecution: nodeSelectorTerms: - matchExpressions: - key: tenant operator: In values: - tenants-x ... tolerations: - effect: NoSchedule key: tenant operator: Equal value: tenants-x ...
重要

Kubernetes API サーバーリクエストフローに統合されているポリシー管理ツールは、変更と検証アドミッションウェブフックを使用して、指定された期間内に API サーバーのリクエストに応答するように設計されています。これは通常 3 秒以下です。ウェブフック呼び出しが設定された時間内にレスポンスを返さない場合、インバウンド API サーバーリクエストのミューテーションや検証が発生する場合と発生しない場合があります。この動作は、アドミッションウェブフック設定が Fail Open または Fail Close に設定されているかどうかに基づいています。

上記の例では、OPA/Gatekeeper 用に記述されたポリシーを使用しました。ただし、ノード選択のユースケースを処理する他のポリシー管理ツールもあります。たとえば、この Kyverno ポリシーを使用してノードアフィニティミューテーションを処理できます。

注記

正しく動作している場合、ポリシーを変更すると、インバウンド API サーバーのリクエストペイロードに必要な変更が反映されます。ただし、変更の保持を許可する前に、ポリシーの検証を含めて、必要な変更が行われることを確認する必要があります。これは、tenant-to-node分離にこれらのポリシーを使用する場合に特に重要です。また、監査ポリシーを含めて、不要な設定がないかクラスターを定期的にチェックすることをお勧めします。

リファレンス

ハードマルチテナンシー

ハードマルチテナンシーは、テナントごとに個別のクラスターをプロビジョニングすることで実装できます。これはテナント間で非常に強力な分離を提供しますが、いくつかの欠点があります。

まず、テナントが多い場合、このアプローチはすぐに高価になる可能性があります。クラスターごとにコントロールプレーンのコストを支払う必要があるだけでなく、クラスター間でコンピューティングリソースを共有することはできません。これにより、最終的に断片化が発生し、クラスターのサブセットが十分に活用されず、他のサブセットが過剰に活用されます。

次に、これらのすべてのクラスターを管理するための特別なツールを購入または構築する必要があります。時間が経つと、数百または数千のクラスターを管理するだけでは扱いにくい場合があります。

最後に、テナントごとのクラスターの作成は、名前空間の作成と比較して遅くなります。ただし、規制の厳しい業界や強力な分離が必要な SaaS 環境では、ハードテナンシーアプローチが必要になる場合があります。

今後の方向性

Kubernetes コミュニティは、ソフトマルチテナンシーの現在の欠点とハードマルチテナンシーの課題を認識しています。Multi-Tenancy Special Interest Group (SIG) は、階層名前空間コントローラー (HNC) や仮想クラスターなど、いくつかの冪定プロジェクトを通じてこれらの欠点に対処しようとしています。

HNC 提案 (KEP) では、〔ポリシー] オブジェクト継承を持つ名前空間間に親子関係を作成する方法と、テナント管理者がサブ名前空間を作成する機能について説明します。

仮想クラスター提案では、クラスター内の各テナント (Kubernetes on Kubernetes とも呼ばれます) に対して、API サーバー、コントローラーマネージャー、スケジューラなど、コントロールプレーンサービスの個別のインスタンスを作成するメカニズムについて説明します。

マルチテナンシーベンチマーク提案では、分離とセグメンテーションに名前空間を使用してクラスターを共有するためのガイドラインと、ガイドラインへの準拠を検証するためのコマンドラインツール kubectl-mtb を提供します。

マルチクラスター管理ツールとリソース