クォータ・デフォルト値・QoSを異なる指標として読み解く
一言でいうと
ResourceQuotaはテナントが使える上限であり、requestsはスケジューリングの入力であり、実際の使用量は観測値です。上限・request・実使用を同じ数字として扱いません。
なぜ必要なのか
練習用クラスターのallocatable CPUが24コアで、3つのテナントのrequests.cpuクォータがそれぞれ12・9・9コアだとします。上限の合計は30コア、容量に対する比率は1.25です。これは今30コアが予約されているという意味でも、CPUを125%使っているという意味でもありません。すべてのチームが同時に上限いっぱいまでrequestした場合を受け止められない可能性がある、という計画上のシグナルです。実際に配置されたPodのrequestsの合計と使用量は、別に調べる必要があります。
チームごとの上限を足し合わせる計算は出発点にすぎません。特定のノードプールやストレージ領域に偏ったワークロードは、全体に余裕があっても起動できないことがありますし、ノード1台の障害時の余裕も必要です。クォータのhardだけを足してサーバーの購入量を決めてはいけません。
どう動くのか
アドミッション審査と席の割り当て
ResourceQuotaは、ネームスペース範囲のリソース・オブジェクト数の制限をadmissionで検査します。CPUの実使用量を継続的に測定する仕組みではありません。count/appclaims.platform.labhub.ioのようなキーで、CRの個数を制限することもできます。RBAC・ポリシー・プラットフォーム独自のロジックも払い出しを制限できるため、クォータが唯一の手段ではありません。公式ResourceQuotaドキュメントで計算対象を確認してください。
LimitRangeは、オブジェクトごとの最小値・最大値・デフォルト値などを扱います。defaultはlimit、defaultRequestはrequestのデフォルト値です。Podの作成時に適用され、ポリシーを変えても既存のPodは自動では修正されません。複数のLimitRangeで矛盾するデフォルト値を与えないでください。公式LimitRangeドキュメントをもとに、提出前のファイルとadmission後のオブジェクトを比較する習慣をつけます。
Guaranteedには条件があります
この説明は、コンテナごとにresourcesを使い、Podレベルのresourcesは宣言しない例です。Guaranteedになるには、すべての通常コンテナとinitコンテナで、CPUとメモリのそれぞれについてrequestとlimitが存在し、かつ同じ値である必要があります。メモリだけが同じ値で埋められていても、CPUの条件が欠けていればGuaranteedではありません。リソースの宣言が1つでもあってGuaranteedの条件に届かなければ、Burstableです。公式QoSタスクページと最終的なstatus.qosClassを突き合わせます。
| 例のコンテナの最終的なリソース | QoS | 判断の理由 |
|---|---|---|
| CPU 100m/100m、メモリ64Mi/64Mi | Guaranteed | 2つのリソースのrequest/limitがすべて同じ |
| CPUなし、メモリ64Mi/64Mi | Burstable | CPUの条件が欠けている |
| CPU 25m/100m、メモリ32Mi/64Mi | Burstable | requestがlimitより小さい |
| すべてのコンテナでCPU・メモリの宣言なし | BestEffort | 2つのリソースともrequest/limitがない |
コンテナがlimitを明示し、対応するrequestを省略した場合は、requestがlimitの値に設定されます。この場合と、「両方を省略してLimitRangeのデフォルト値を受け取る場合」を区別します。すでに書かれているrequestは、デフォルト値で上書きする対象ではありません。公式のメモリデフォルト値の例を読むときは、この2つの入力を入れ替えて予測してみてください。
読み取りの練習: 入力ではなく保存されたオブジェクトを見てください
以下は、課金対象の本番クラスターではなく、自分専用の学習クラスターでだけ実行する例です。cnpe-default-demoネームスペースがすでにある場合は再利用せず、新しい名前を選んでください。まず、デフォルト値を2つ受け取るPodを予測してから、実際の結果を読みます。
kubectl create namespace cnpe-default-demo
kubectl -n cnpe-default-demo apply -f - <<'YAML'
apiVersion: v1
kind: LimitRange
metadata:
name: defaults
spec:
limits:
- type: Container
default:
cpu: 100m
memory: 64Mi
YAML
kubectl -n cnpe-default-demo run sample --image=busybox:1.36 -- sleep 3600
kubectl -n cnpe-default-demo get pod sample -o jsonpath='{.spec.containers[0].resources}'
kubectl -n cnpe-default-demo get pod sample -o jsonpath='{.status.qosClass}'
予想は、CPU 100m・メモリ64Miのrequestとlimit、そしてGuaranteedです。イメージのダウンロード失敗とリソースのデフォルト値の適用失敗は別の問題です。コンテナの実行まで見る場合は、イメージにアクセスできる環境と実際のkubeletが必要です。kwokのRunning表示は、実際に実行された証拠としては使いません。
反例は、別のネームスペースでCPUのデフォルト値だけを除いて新しいPodを作ることです。メモリのrequestとlimitが同じでもBurstableになることを確認します。元のネームスペースのポリシーだけを変えて既存のPodを読むことは、この反例ではありません。記録を残したあと、自分で作ったPodとポリシーだけが入ったネームスペースを削除します。
kubectl delete namespace cnpe-default-demo
現場での姿
仮想のコスト会議で、requestが4コア・実使用が0.3コアのワークロードだけを見て、すぐにrequestを0.3に下げたとします。ピーク時間・起動コスト・障害時の余裕を見ていなければ、削減額より先に障害が起きるかもしれません。期間ごとの使用分布とSLOを見て、小さな範囲で変更したうえで、スケジューリング・レイテンシ・再起動を合わせて確認します。QoSクラスそのものが、性能や無障害を保証するわけではありません。
クォータの使用量が空だったり、status unknownエラーが出たりする場合は、オブジェクトのstatus、CRDのEstablished、API discovery、コントローラーの状態を確認します。やみくもに削除・再作成すると、admissionによる保護が一時的になくなるおそれがあります。原因と復旧の範囲を確認してから対処し、新しいリクエストが許可される場合と拒否される場合をそれぞれテストします。
観測事例: アドミッションは通ったのに席がありません
2026-09-11、別の単一ノードk3s v1.36.4+k3s1 VMで、次のことを実際に確認しました。運用サービスの負荷テストではなく、小さなsleepコンテナでadmissionとスケジューリングを切り分けた実験です。CPUを9コア使うように負荷をかけたのではなく、9コアをrequestしたのです。
| 確認したフィールド | 観測値 | 意味 |
|---|---|---|
| Node status.allocatable.cpu | 8 | このノードのPod用CPU容量 |
| ResourceQuota status.hard.requests.cpu | 10 | このNamespaceのrequest量の上限 |
| Pod spec.containers[0].resources.requests.cpu | 9 | 単一コンテナがrequestしたCPU |
| ResourceQuota status.used.requests.cpu | 9 | まだ実行されていないPodのrequestも計算される |
| Pod status.phase / spec.nodeName | Pending / なし | APIに保存されたが、ノードへの配置はされていない |
| PodScheduledの条件 | False, Unschedulable, Insufficient cpu | スケジューラーがCPU不足を報告した |
クォータ10の範囲内にrequest 9は収まるのでオブジェクトは作られましたが、allocatable 8のノードにrequest 9を置くことはできませんでした。このときクォータを20に上げても、ノードは大きくなりません。すべてのノードが8コアなら、同じ大きさのノードを追加するだけでは、この単一のPodは入りません。requestの見積もりが間違っていないかを検証するか、十分に大きいノードやアプリケーションの分割といった設計変更を検討する必要があります。公式のリソースのトラブルシューティングドキュメントでノードより大きいPodの事例を突き合わせてください。
実験では、同じ名前のPodを削除したあと、request 25mで作り直しました。新しいUIDが発行され、Running・Readyと実際のexecが確認され、クォータの上限はそのままでした。これは実行可能性の復旧の証拠であって、適正なrequest量やコスト削減の証拠ではありません。sleepコンテナの25mを実際の注文サーバーにそのまま適用しないでください。CPU使用の時系列、スループット、レイテンシとエラー率は、この実験では測定していません。
反対に、別のNamespaceでrequest 50mのPodを2つ作ってクォータ100mを使い切り、3つ目の50mのPodを作ろうとすると、APIがForbiddenとexceeded quotaを返し、3つ目のオブジェクトは保存されませんでした。この場合、スケジューラーが見るPodそのものがありません。DeploymentがPodの作成に失敗した場合は、上位オブジェクトのイベントも確認する必要があります。PodのPendingだけを探すと、admissionの失敗を見逃します。
テナントの境界は、読み取りと変更を別々にテストします
同じVMの別の実験では、ServiceAccountに自分のNamespaceのpods get/listだけを付与しました。そのアカウントで実際に参照を送ると、自分のPodは読めましたが、別のNamespaceのPodの参照と自分のResourceQuotaの変更はForbiddenでした。拒否のあとに管理者が読み直したクォータも、100mのままでした。単にRole YAMLがあるというだけでなく、許可されたリクエスト・拒否されたリクエスト・変更されていない状態を合わせて見る理由です。
この実験は、管理者が--asで代理したAPI権限のチェックです。実際のログイン・トークン発行・ネットワーク遮断・カーネル分離まで検証したものではありません。クォータだけを設定してテナントにクォータの変更権限を与えると、上限を自分で引き上げられてしまいます。反対に、APIの参照を止めたからといって、他のチームのサービスのポートへ通信できないという意味でもありません。Kubernetesマルチテナンシードキュメントのコントロールプレーンとデータプレーンを区別して読んでください。
自分で判断してみてください。① クォータのusedが9のとき、CPUは9コアで実行中でしょうか。② 同じ名前で復旧したPodは元のオブジェクトでしょうか。③ 他のチームの参照が拒否されたら、ネットワークの分離も済んでいるでしょうか。答えはすべて「いいえ」で、根拠はそれぞれ、requestの量の計算・UIDの変更・テスト範囲の違いです。
次のクイズで確認すること
24と30が何の合計か、メモリだけを制限したPodのQoS、既存のPodに対するポリシー変更の効果を判断してください。単一の正常な例の成功を、テナント分離全体やコスト最適化の証拠に拡大しないことが、この単元の目標です。