kube-apiserver がリクエストを処理する流れ
v1.37 時点(2026年10月28日 確認)
目次
この記事では、kube-apiserver が API のリクエストを受け取ってから保存するまでに行う 認証・認可・アドミッション の流れを、図で説明します。それぞれの段階で誰が何を判断するのか、通らなかったリクエストがどう断られるのかがわかります。後半では kind のクラスタで、リクエストが段階ごとに拒否される様子を確かめる手順を示します。手順では先にアドミッションでの拒否を見てから、同じマニフェストがアドミッションより前の認可で止まることを確かめます。そのため、認証、アドミッション、認可の順に試します。
第38回の ServiceAccount と RBAC、第39回の Pod Security Admission の内容を前提にしています。第38回では認証と認可を、第39回ではアドミッションコントローラーの 1 つである PodSecurity を扱いました。まだの方は先にこちらをどうぞ。
ServiceAccount と RBAC の仕組みPod の中のプログラムが API を使うときの身元になる ServiceAccount と、誰にどの操作を許すかを決める RBAC の仕組みを図で説明します。トークンが Pod に届く流れ、Role と RoleBinding の書き方、権限が足し算だけで決まることを押さえ、権限の無い ServiceAccount が断られる様子を kind のクラスタで確かめる手順を紹介します。以下の図は、kubectl を使う人が送ったリクエストが、kube-apiserver の中の 3 つの段階を通って etcd に保存されるまでの流れを表しています。
リクエストは 1 から 3 の順に確かめられ、どこかの段階で断られると、その先には進みません。どこでどう断られるかは、「リクエストの処理の仕組み」の 2 枚の図で示します。
kube-apiserver のリクエストの処理とは
第4回で見たとおり、ノードの kubelet もコントローラーも kubectl も、API を使うときの接続先は kube-apiserver です。公式ドキュメントの Controlling Access to the Kubernetes API によると、利用者は kubectl、クライアントライブラリ、REST のリクエストで API にアクセスします。人の利用者も ServiceAccount も、API へのアクセスを認可され得ます。リクエストはいくつかの段階を通ります。
最初の段階は通信の保護です。第3回で触れたとおり、kube-apiserver は TLS で保護された HTTPS で待ち受けていて、公式ドキュメントによると既定のポートは 6443 です。クライアントはこの段階で、TLS のクライアント証明書を出せます。
TLS の接続ができたあとの段階を、この記事の図の番号に合わせて次の表にまとめます。
| 段階 | すること | 通らなかったとき |
|---|---|---|
| 1. 認証(Authentication) | 認証のモジュールが HTTP のリクエスト全体を調べ、利用者名を決める | HTTP の状態コード 401 で拒否される |
| 2. 認可(Authorization) | 利用者名、求める操作、対象のオブジェクトを、決まり(ポリシー)と照らし合わせる | 許可するモードが無ければ、403 で拒否される |
| 3. アドミッション(Admission control) | アドミッションコントローラーがリクエストを書き換えたり拒否したりする。その途中で、kube-apiserver がオブジェクトを検証する | どこかで 1 つでも拒否されると、リクエスト全体がすぐに拒否される |
| そのあと保存 | etcd に書き込む | この記事では扱わない |
参考:Controlling Access to the Kubernetes API | Kubernetes、Controlling Access to the Kubernetes API | Kubernetes、Controlling Access to the Kubernetes API | Kubernetes、Dynamic Admission Control | Kubernetes
それぞれの段階の細かい判断の仕方は、「リクエストの処理の仕組み」の小節で見ます。HTTP の状態コードは、第30回で見たとおり、リクエストの結果を表す 3 桁の数です。
どの段階も kube-apiserver の中で行われます。ただし公式ドキュメントによると、認証や認可では外部のサービスに問い合わせることもあり(Webhook のトークン認証、Webhook の認可モード)、アドミッションにも外部の HTTP のサーバーを呼ぶ Webhook があります。Controlling Access のページは最後に監査(Auditing)も挙げています。監査では、利用者、Kubernetes の API を使うアプリケーション、コントロールプレーン自身が行った操作を、クラスタが時間の順に記録します(第39回の監査ログ)。
参考:Authenticating | Kubernetes、Authorization | Kubernetes、Dynamic Admission Control | Kubernetes、Controlling Access to the Kubernetes API | Kubernetes
なぜ段階に分けて確かめるのか
第38回で見たとおり、認証で身元がわかっても、それだけでは何もできません。認可が照らし合わせるのは利用者、グループ、操作(verb)、リソース、Namespace などのリクエストの属性です。一方、認可のページの注記によると、特定の種類のオブジェクトの特定のフィールドに基づく決まりは、アドミッションコントローラーが扱います。第39回の Pod Security Admission はその例です。RBAC が Pod を作る操作を許したあとで、PodSecurity が Pod の SecurityContext を確かめていました。
アドミッションは拒否するだけでなくフィールドに既定値を入れることもできます。第38回の ServiceAccount のアドミッションコントローラーは、serviceAccountName の無い Pod に default を入れていました。公式ドキュメントは、Kubernetes の重要な機能のいくつかはアドミッションコントローラーが有効でないと正しく動かないとしています。正しいアドミッションコントローラーの組で設定されていない kube-apiserver は不完全で、期待する機能のすべてには対応しない、とも書いています。
参考:Controlling Access to the Kubernetes API | Kubernetes、Admission Control in Kubernetes | Kubernetes
段階の順番にも意味があります。認可のページによると、アドミッションは認可が終わったあと、つまり認可が許したときにだけ行われます。認証できないリクエストや、認可で許されない操作のリクエストは、アドミッションで中身を確かめる前に断られます。
リクエストの処理の仕組み
認証:誰からのリクエストかを決める
公式ドキュメントによると、Kubernetes はクライアント証明書、bearer トークン(第38回で見た Authorization: Bearer <トークン> のヘッダーで送るトークン)、認証のプロキシのいずれかで、API のリクエストを認証します。認証のプラグインはリクエストに利用者名、UID、グループ、追加の情報を結び付けようとします。これらの値は認証の仕組みにとっては意味を持たず、認可の段階で解釈されて初めて意味を持ちます。認証に成功した利用者は自動で system:authenticated のグループにも入ります。
v1.37 時点の公式ドキュメントが挙げる認証の方法には、X.509 のクライアント証明書と ServiceAccount のトークンのほか、ブートストラップトークンや、外部との連携(OpenID Connect のトークンなど)があります。この回で扱うのは第38回で見た 2 つだけで、ほかは名前を挙げるにとどめます。2 つとは、証明書の CN が利用者名、O がグループになるクライアント証明書(第38回の推測では、kind の利用者もこれを使います)と、Pod の中のプログラムが使う ServiceAccount のトークンです。公式ドキュメントは、普通は少なくとも 2 つの方法を使うべきだ(should)としています。その 2 つは、ServiceAccount のための ServiceAccount のトークンと、人の利用者のための少なくとも 1 つの方法です。
いくつもの方法を同時に有効にできます。Controlling Access のページは、複数の認証のモジュールを指定すると、どれかが成功するまで順に試すと説明しています。認証のページによると、最初に認証に成功したモジュールで評価は打ち切られますが、モジュールがどの順に動くかは保証されません。2 つのページを合わせると、どれか 1 つで認証できれば認証の段階を通り、試す順番は保証されません。
参考:Controlling Access to the Kubernetes API | Kubernetes、Authenticating | Kubernetes
認証できないリクエストは HTTP の 401 で拒否されます。名前は Unauthorized(英語で「認可されていない」)ですが、401 は認証の失敗を表します。認可の失敗は 403 Forbidden です。
ただし、認証情報を付けないリクエストは 401 になるとは限りません。匿名のリクエスト(anonymous requests)が有効なら、認証情報が無いなど、どの認証の方法にも拒否されなかったリクエストは匿名として扱われます。利用者名は system:anonymous、グループは system:unauthenticated です。公式ドキュメントの例では、トークンの認証と匿名のアクセスを有効にしたサーバーで、無効な bearer トークンを付けたリクエストは 401 Unauthorized になります。一方、bearer トークンを付けないリクエストは匿名のリクエストとして扱われます。
匿名のアクセスは AlwaysAllow 以外の認可モードを使っていれば既定で有効で、kube-apiserver に --anonymous-auth=false を渡すと無効にできます。匿名のリクエストも次の認可の段階で許されなければ先へ進めません。組み込みの RBAC は system:anonymous か system:unauthenticated への明示的な許可を求めます。kind のクラスタで匿名のアクセスが有効かは、確かめた範囲の kind と kubeadm の公式ドキュメントには書かれていません。設定を変えていなければ、既定のとおり有効だと考えられます。
認可:その操作をしてよいかを決める
認可は kube-apiserver の中で行われ、リクエストの属性をすべての決まりと照らし合わせます(第38回)。リクエストは、すべての部分がいずれかの認可の仕組みに許されなければ先へ進めず、どの仕組みも許さなければ拒否されます(既定で拒否)。kube-apiserver が使える認可モードを、公式ドキュメントの説明に沿って次の表にまとめます。
| 認可モード | 公式ドキュメントの説明 | 回 |
|---|---|---|
| Node | kubelet に、そのノードに割り当てられた Pod をもとに権限を与える特別な目的のモード | 第31回 |
| RBAC | rbac.authorization.k8s.io の API グループで決まりを作り、Kubernetes の API で変えられる | 第38回 |
| Webhook | 外部の HTTP のサービスに同期で問い合わせ、答えが返るまでリクエストを止める | なし |
| ABAC | 属性を組み合わせたポリシーで権限を与える | なし |
| AlwaysAllow | すべて許す。セキュリティの危険があり、使うのは認可が要らない場合(たとえばテスト)だけにする | なし |
| AlwaysDeny | すべて拒否する。使うのはテストだけにする | なし |
モードは --authorization-mode=Node,RBAC,Webhook のように並べられ、コマンドラインに書いた順に調べられます。どれかのモードが許可か拒否を決めればその結果がすぐに返り、すべてのモードが「意見なし」なら 403(Forbidden)で拒否されます(第38回)。公式ドキュメントは、API サーバーに公開のインターネットから届くクラスタでは AlwaysAllow を使うべきではない(should not)としています。なお、--authorization-mode を指定せず、--authorization-config も使わずに kube-apiserver を起動したときの既定は AlwaysAllow ですが、kubeadm や kind はこれを変えていると考えられます(次の段落)。上の「既定で拒否」は、有効にした仕組みがどれも許さないときの扱いで、この起動時の既定とは別の話です。
参考:Authorization | Kubernetes、Authorization | Kubernetes、kube-apiserver | Kubernetes
kind のクラスタでどのモードが有効かは、確かめた範囲の kind の公式ドキュメントには書かれていません。kind は各ノードを kubeadm で初期化し(第2回)、kubeadm の設計の原則には「RBAC を強制する」と「Node の認可を使う」があります。そのため、kind のクラスタでは Node と RBAC が有効だと考えられます(第38回と同じ根拠による推測です)。
第31回で触れた Node の認可モードは、kubelet が使う API を許します。Node のモードで認可されるには、kubelet は system:nodes のグループに入り、利用者名が system:node:<ノード名> の認証情報を使わなければなりません(must)。このモードは kubelet にノードや Pod の書き込みを許しますが、その書き込みを自分のノードと、自分のノードに割り当てられた Pod だけに絞るには、NodeRestriction のアドミッションコントローラーを有効にします。認可で許された書き込みを、アドミッションでさらに絞る組み合わせだと読めます。NodeRestriction は既定で有効なアドミッションコントローラーの一覧にはありませんが、kubeadm の実装のページには kubeadm が有効にするものとして挙がっています。そのため kind のクラスタでも有効だと考えられます。
参考:Using Node Authorization | Kubernetes、Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes、Implementation details | Kubernetes
以下の図は、リクエストが認証と認可で拒否される地点と、そのときの HTTP の状態コードを表しています。
赤の実線が拒否の道です。この図では 401 と 403 を出す高さを分けるため、冒頭の図と違って 1 と 2 を縦に並べていますが、順番は同じです。認証で断られたリクエストは認可に進まず、認可で断られたリクエストはアドミッションに進みません。匿名のアクセスが有効なら、匿名として扱われたリクエストは認証で断られずに 2 の認可へ進みます。第38回で使った --as のなりすましでは、認証は送った人の認証情報で行われ、認可はなりすました利用者で行われます。
アドミッション:書き換えてから確かめる
アドミッションコントローラー は kube-apiserver の中で、リソースを変えるリクエストのデータを確かめるコード です。公式ドキュメントによると、オブジェクトを作る、消す、変えるリクエストに働きます。Pod への API サーバーのプロキシを通した接続(connect)のような、独自の操作を止めることもできます。一方、読み取り(get、watch、list)のリクエストは、アドミッションの層を通らないので止められません。
参考:Admission Control in Kubernetes | Kubernetes、Controlling Access to the Kubernetes API | Kubernetes
v1.37 のアドミッションコントローラーは kube-apiserver のプログラムに組み込まれていて、設定できるのはクラスタの管理者だけです。種類は mutating(変更)、validating(検証)、その両方のどれかです。mutating のものはリソースのデータを書き換えられますが、validating のものは書き換えられません。
アドミッションは 2 つの段階で進みます。1 つ目の段階では mutating のアドミッションコントローラーが、2 つ目の段階では validating のものが動きます(両方を兼ねるものもあります)。どちらかの段階でアドミッションコントローラーが 1 つでも拒否すると、リクエスト全体がすぐに拒否され、利用者にエラーが返ります。Controlling Access のページは、この点を認証や認可のモジュールとの違いとして挙げています。
参考:Admission Control in Kubernetes | Kubernetes、Controlling Access to the Kubernetes API | Kubernetes
mutating と validating のあいだには、オブジェクトの検証があります。Dynamic Admission Control のページによると、mutating の Webhook が先に呼ばれ、オブジェクトの変更がすべて終わり、届いたオブジェクトを API サーバーが検証したあとで、validating の Webhook が呼ばれます。オブジェクトの検証は API サーバー(kube-apiserver)が行うものとして書かれていて、アドミッションコントローラーとは別のものと読めます。Controlling Access のページは検証の位置の書き方がこれと違うので、この記事では細かいほうのページ(Dynamic Admission Control)に合わせて図にしました。アドミッションの中の順番を、図の枝番に合わせて次の表にまとめます。
| 順 | 名前 | 行うもの | 例 |
|---|---|---|---|
| 3-1 | mutating(変更) | mutating のアドミッションコントローラーと Webhook | ServiceAccount が default を入れる(第38回)、LimitRanger が既定の requests を入れる(第30回) |
| 3-2 | オブジェクトの検証 | kube-apiserver(アドミッションコントローラーとは別と読める) | 第30回で invalid になった例がこれにあたると読める |
| 3-3 | validating(検証) | validating のアドミッションコントローラーと Webhook、ValidatingAdmissionPolicy | PodSecurity、ResourceQuota、この回の手順のポリシー |
参考:Dynamic Admission Control | Kubernetes、Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes、Controlling Access to the Kubernetes API | Kubernetes
第30回の invalid の例は、LimitRanger が入れた既定の limits と、マニフェストに書いた requests が食い違ったものです。3-1 で書き換えたあと、3-2 の検証で止まった例だと読めます。第39回で使った --dry-run=server もこの流れの中にあります。公式ドキュメントによると、dry-run のリクエストはアドミッションと検証などを通りますが、保存はされません。認可の扱いは dry-run ではないリクエストと同じです。
参考:Limit Ranges | Kubernetes、Kubernetes API Concepts | Kubernetes
以下の図は、認可を通ったリクエストがアドミッションの中で処理され、etcd に保存されるまでを表しています。
3-1〜3-3 のどこで断られても、赤の実線の道でリクエスト全体が拒否されます。3-3 まで通ったリクエストだけが etcd に保存されます。Webhook のサーバーは kube-apiserver の外にあり、3-1 と 3-3 から呼ばれます。手順で使う ValidatingAdmissionPolicy は 3-3 の中で動き、Webhook のサーバーは呼びません。
v1.37 で既定で有効なアドミッションコントローラーのうち、これまでの回に出てきたものを、公式ドキュメントの説明に沿って次の表にまとめます。
| アドミッションコントローラー | 種類 | すること | 回 |
|---|---|---|---|
| NamespaceLifecycle | validating | 削除中の Namespace に新しいオブジェクトを作らせず、無い Namespace へのリクエストを拒否する。default、kube-system、kube-public の削除も防ぐ | 第37回 |
| LimitRanger | mutating と validating | LimitRange の制約に違反しないかを確かめる。requests を書いていない Pod に既定の requests を入れるのにも使える | 第30回 |
| ResourceQuota | validating | ResourceQuota の制約に違反しないかを確かめる | 第30回 |
| ServiceAccount | mutating と validating | ServiceAccount の自動化を行う(default の割り当てやトークンのボリューム) | 第38回 |
| PodSecurity | validating | 新しい Pod を、Namespace に許された Pod Security Standards の制限に照らす | 第39回 |
| DefaultStorageClass | mutating | クラスを求めない PersistentVolumeClaim に既定の StorageClass を足す | 第27回 |
| DefaultIngressClass | mutating | クラスを求めない Ingress に既定の IngressClass を足す | 第24回 |
参考:Admission Control in Kubernetes | Kubernetes、Managing Service Accounts | Kubernetes、Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes
公式ドキュメントは、推奨のアドミッションコントローラーは既定で有効なので明示しなくてよいとしています。足すときは kube-apiserver の --enable-admission-plugins に並べ、順番は関係ありません。
アドミッションで拒否されたときの状態コードは、拒否した仕組みによって違います。第30回で見た LimitRange と ResourceQuota の拒否は 403 Forbidden で、認可の拒否と同じコードでした。ValidatingAdmissionPolicy では、拒否の理由(reason)を書かなければ Invalid の理由と、それにあたる状態コードが使われます。reason は、操作が失敗した理由を機械が読める形で表す短い語で、API の応答(Status)に入ります。Invalid にあたる状態コードの番号は、このメディアで確かめた範囲の公式ドキュメントには書かれていません。
参考:Limit Ranges | Kubernetes、Configure Memory and CPU Quotas for a Namespace | Kubernetes、ValidatingAdmissionPolicy | Kubernetes API Reference、Status | Kubernetes API Reference
Webhook と ValidatingAdmissionPolicy で決まりを足す
組み込みのアドミッションコントローラーのうち 4 つは、クラスタに独自の決まりを足すための特別なものです。MutatingAdmissionWebhook と ValidatingAdmissionWebhook は API で設定した Webhook を呼び、MutatingAdmissionPolicy と ValidatingAdmissionPolicy は外部の HTTP の呼び出しを使わずに、宣言的な決まりを API の中に書けるようにします。v1.37 では 4 つとも既定で有効な一覧に入っています。
参考:Admission Control in Kubernetes | Kubernetes、Admission Control in Kubernetes | Kubernetes
アドミッションの Webhook は、アドミッションのリクエストを受け取って処理する HTTP のコールバックです。Webhook のサーバーは自分で書き、クラスタの中か外で動かします。どのリソースにどの Webhook を当てるかは、MutatingWebhookConfiguration と ValidatingWebhookConfiguration で決めます。第37回で見たとおり、これらの設定にも Namespace の分離は当てはまりません。failurePolicy の既定は Fail で、Webhook の呼び出しでエラーが起きるとリクエストは拒否されます。公式ドキュメントはアドミッションの Webhook を実質的にコントロールプレーンの一部だとし、とても慎重に書いて動かすべきだ(should)としています。第44回で扱う CRD にも Webhook で検証を足せます。
参考:Dynamic Admission Control | Kubernetes、Dynamic Admission Control | Kubernetes、Dynamic Admission Control | Kubernetes、Dynamic Admission Control | Kubernetes、Extend the Kubernetes API with CustomResourceDefinitions | Kubernetes
ValidatingAdmissionPolicy は validating の Webhook の代わりに、kube-apiserver の中で宣言的に検証する仕組み で、v1.30 から Stable です。決まりは CEL(Common Expression Language)という式の言語で書きます。同じ名前のアドミッションコントローラー(ValidatingAdmissionPolicy)が、次の表のリソースに書いた決まりを 3-3 の段階で実行します。
| リソース | 役割 |
|---|---|
| ValidatingAdmissionPolicy | 決まりの中身(どのリクエストに、どの式を当てるか) |
| ValidatingAdmissionPolicyBinding | 決まりの結び付け。当てる範囲(Namespace など)と、違反したときの動き(validationActions)を決める |
参考:Validating Admission Policy | Kubernetes、Validating Admission Policy | Kubernetes、Admission Control in Kubernetes | Kubernetes
ほかに、パラメータのリソースで決まりに値を渡すこともできますが、この回では使いません。決まりを効かせるには、少なくとも ValidatingAdmissionPolicy と、それに対応する ValidatingAdmissionPolicyBinding の両方が要ります(must)。validationActions には Deny(拒否する)、Warn(クライアントに警告を返す)、Audit(監査のイベントに含める)を書けます。Deny と Warn は一緒に使えません。ポリシーの failurePolicy(既定は Fail)が決めるのは、設定の誤りや CEL の式のエラーの扱いです。式が false になった検証は、validationActions のとおりに扱われます。2 つのリソースは Namespace に属さないと読めます。
参考:Validating Admission Policy | Kubernetes、Validating Admission Policy | Kubernetes、Validating Admission Policy | Kubernetes、ValidatingAdmissionPolicy | Kubernetes API Reference、ValidatingAdmissionPolicyBinding | Kubernetes API Reference
ポリシーは API のリソースなので、作る権限(RBAC)があれば、管理者でなくても API で作れると考えられます。手順で kind の利用者がポリシーを作れるのも、cluster-admin の権限を持つと考えられるからです(第38回の推測)。第30回で触れた、ResourceQuota の削除や更新の権限を ValidatingAdmissionPolicy で制限する例も、この仕組みを使っています。
変更の側には MutatingAdmissionPolicy があり、v1.36 から Stable で、既定で有効です。こちらは CEL でリソースの書き換えを決めます。公式ドキュメントは、決まりを拡張できる形で決めたい場合は Webhook を、Webhook のサーバーを動かす手間をかけずに簡単な決まりを書きたい場合は CEL の仕組みを使うとしています。そのうえで、できる場合は CEL の仕組みを使うことを Kubernetes のプロジェクトとして勧めています。
参考:Mutating Admission Policy | Kubernetes、Admission Webhook Good Practices | Kubernetes
手元で試す
ここまでの内容を手元の kind のクラスタで試し、リクエストが拒否される地点を段階ごとに確かめます。認可より先にアドミッションを試すのは、同じマニフェストが認可で先に止まることを、あとで比べるためです。流れは次の 4 つです。
- 無効なトークンの利用者でリクエストを送り、認証で拒否されることを確かめる
- 公式ドキュメントの ValidatingAdmissionPolicy を作り、
replicasが 6 の Deployment がアドミッションで拒否されることを確かめる - 同じ Deployment を権限の無い ServiceAccount として作り、アドミッションより先に認可で拒否されることを確かめる
replicasを 3 に直すと作れること、6 に変える更新は拒否されることを確かめる
この節で引用する出力の例は、公式ドキュメントに載っている、ほかのクラスタで取ったものです。名前や時間などの値は手元とは違います。各段に書く成功の目安は公式ドキュメントから予想される結果で、手元で実行して確かめたものではありません。
この節では、エラーが出ることが成功の目安になる段がいくつもあります。どの段階で断られたかを見分ける手がかりを、公式ドキュメントの例をもとに次の表にまとめます。
| 段階 | 出力の目安 | 見るところ |
|---|---|---|
| 認証 | 認証できなかったことを知らせるエラー(401)。kubectl の出力の例は公式ドキュメントに無い | 利用者名や、拒否した仕組みの名前は出ないと考えられる |
| 認可 | Error from server (Forbidden): User "clark" cannot get nodes at the cluster scope. (get nodes mynode) の形(User Impersonation の例) | User "..." cannot ... の形で、利用者名と操作が出る |
| アドミッション | 拒否した仕組みによる。第30回の exceeded quota の例のように Error from server (Forbidden) で始まることもある | 違反した決まりの説明が出る。仕組みや決まりの名前が出ることもある(例:ValidatingAdmissionPolicy '...' ... denied request、exceeded quota: mem-cpu-demo) |
参考:User Impersonation | Kubernetes、Configure Memory and CPU Quotas for a Namespace | Kubernetes、Validating Admission Policy | Kubernetes
Forbidden の語は認可でもアドミッションでも出るので、その語だけでは見分けられません。User "..." cannot ... の形で利用者名と操作が出ていれば認可、出ていなければアドミッションだと考えられます。
コマンドは macOS や Linux のシェル(bash や zsh)で実行する前提で書いています。Windows では WSL(Windows の中で Linux を動かす仕組み)などを使ってください。
試す環境は次のとおりです。
| 項目 | 内容 |
|---|---|
| kind | v0.33.0 |
| Kubernetes | v1.37.0(kind v0.33.0 の既定のノードイメージ) |
| ノード構成 | シングルノード(クラスタ名 kind):コントロールプレーン 1 |
| 必要なもの | Docker、kubectl(v1.36〜v1.38)、テキストエディタ、端末(ターミナル)2 つ |
参考:Release v0.33.0 · kubernetes-sigs/kind、Version Skew Policy | Kubernetes
この回では第37回〜第39回と同じく、シングルノードのクラスタを使います。リクエストを確かめるのは kube-apiserver の仕事で、ノードが 1 つでも確かめられるからです。kind でクラスタを作る手順は、以下の記事で解説しています。
kind で検証用のクラスタを作るDocker のコンテナをノードにして、手元のマシンに Kubernetes のクラスタを作るツール kind の仕組みを図で説明します。kind create cluster で何が起きるかを押さえ、シングルノードとマルチノードのクラスタを作って、ノードの正体が Docker のコンテナであることを確かめる手順を紹介します。この回では端末を 2 つ使います。これまでの回と同じく、1 つ目の端末は変化の監視(この回では Pod)に取っておきます。監視を始めるのは「Pod を監視する」の段からで、手順どおりに進めば、Pod が現れるのは「許される Deployment を作る」の段だけです。クラスタの準備も含めて、操作は 2 つ目の端末で行います。2 つ目の端末を開いたら、cd でマニフェストを保存するディレクトリに移動しておきます。
シングルノードのクラスタを用意する
2 つ目の端末で、前の回のクラスタが残っていないかを確かめます。
$ kind get clusters
kind が表示されたら、作り直さずにそのまま使えるので、クラスタを作る次のコマンドは飛ばします。表示されなければ、クラスタを作ってから、もう一度一覧します。前の回までの multi-node が表示されても、この回では使いません。手元のマシンの資源(メモリなど)に余裕がなければ、kind delete cluster --name multi-node で消してから進めてください。
$ kind create cluster
$ kind get clusters
kind get clusters に kind が表示されれば、クラスタができています。第3回で決めたとおり、ここからのコマンドには --context kind-kind を付けて、操作するクラスタをはっきりさせます。続けて、ノードを一覧します。
$ kubectl get nodes --context kind-kind
ノードが 1 つ表示され、STATUS 列が Ready になっていれば準備ができています。Ready になっていなければ、少し待ってからもう一度実行します。
前の回のクラスタを使い続けている場合は、この回で使う flow-demo の Namespace が無いことも確かめます。
$ kubectl get namespace flow-demo --context kind-kind
見つからないことを知らせる 1 行が出れば、この回の手順をそのまま進められます。表示されたら、自分で作ったものかを確かめます。消すと中のリソースも一緒に消えるので(第37回)、残したいものが無いかも先に確かめてください。そのうえで kubectl delete namespace flow-demo --context kind-kind で消します。
続けて、kind の利用者が ValidatingAdmissionPolicy を作れるかを、第38回の kubectl auth can-i で確かめます。
$ kubectl auth can-i create validatingadmissionpolicies --context kind-kind
yes が表示されるはずです。第38回の推測のとおり kind の利用者が cluster-admin の権限を持つなら、この回の操作はすべてでき、--as のなりすましもできると考えられます。no が表示されたら、--context kind-kind を付け忘れていないかを見直します。付けていても no なら推測が外れていて、この回の手順はこの記事のとおりには進まないと考えられます。
作業用の Namespace を作る
以下はこの回で使う Namespace flow-demo のマニフェストです。後で作るポリシーの結び付けは、公式ドキュメントの例のとおり environment: test のラベルを持つ Namespace を選ぶので、そのラベルを付けておきます。flow-demo.yaml という名前で保存します。
apiVersion: v1
kind: Namespace
metadata:
name: flow-demo
labels:
environment: test参考:Namespace | Kubernetes API Reference、Validating Admission Policy | Kubernetes
2 つ目の端末で、マニフェストを渡し、ラベルを確かめます。
$ kubectl apply -f flow-demo.yaml --context kind-kind
$ kubectl get namespace flow-demo --show-labels --context kind-kind
namespace/flow-demo が作られたことを知らせる 1 行が出るはずです。続く一覧の LABELS 列に、environment=test と、第37回で見た kubernetes.io/metadata.name=flow-demo が並べば、この段は成功です。
Pod を監視する
1 つ目の端末で、flow-demo の Pod を監視します。この監視は片付けの前まで止めずに続けます。
$ kubectl get pods -n flow-demo --watch --context kind-kind
まだ Pod が無いので、何も表示されないか、Pod が無いことを知らせる 1 行が出て、止まったように見えると考えられます。Pod が作られると、この端末に行が書き足されます。
認証で拒否される
認証情報を付けないリクエストは、匿名のアクセスが有効なら匿名として扱われ、認可へ進みます(「認証:誰からのリクエストかを決める」)。そこで、この段では無効なトークンだけを持つ利用者を kubeconfig に足し、認証で断られるリクエストを送ります。
まず 2 つ目の端末で、kubeconfig にある利用者を一覧し、bad-token という名前の利用者が無いことを確かめます。
$ kubectl config get-users
bad-token が並んでいなければ先へ進めます。並んでいたら、このあとの手順で同じ名前の利用者に値が重ねられてしまうので、自分で作ったものかを確かめます。自分で作った覚えが無ければ、以降の bad-token を別の名前(例:bad-token-40)に置き換えて進めます。
手元の設定を書き換えるのが心配なら、一般的な方法として、先に kubeconfig のファイルの写しを取っておけます。KUBECONFIG を設定していなければ、ファイルは $HOME/.kube/config です(第3回)。戻したいときは cp ~/.kube/config.bak ~/.kube/config で戻せます。写しを取るには、次のコマンドを実行します。
$ cp ~/.kube/config ~/.kube/config.bak
続けて、利用者 bad-token を足します。kubectl config set-credentials は kubeconfig に利用者の項目を設定するコマンドで、--token で bearer トークンを書けます。kubeconfig のファイルを書き換えるだけでクラスタには接続しないと考えられるので、--context は付けません。
$ kubectl config set-credentials bad-token --token=not-a-valid-token
利用者 bad-token を設定したことを知らせる 1 行が出るはずです。リファレンスの説明は利用者の項目の設定だけなので、kind-kind のコンテキストやその利用者、current-context は変わらないと読めます。このトークンは kube-apiserver が発行したものではないので、どの認証の方法でも認証できないはずです。
この段ではエラーが出るのが正しい結果です。flow-demo の Pod を、bad-token の利用者で一覧します。第3回で見たとおり、--user を付けると、コンテキストが指す利用者の代わりにその利用者が使われます。接続先のクラスタは kind-kind のコンテキストのものです。
$ kubectl get pods -n flow-demo --context kind-kind --user=bad-token
参考:Organizing Cluster Access Using kubeconfig Files | Kubernetes、Authenticating | Kubernetes
このリクエストは認証できないので、「認証:誰からのリクエストかを決める」の公式の例と同じく 401 で拒否されるはずです。kubectl の出力の例は公式ドキュメントに無いので、Unauthorized を含むエラーか、認証できなかったことを知らせるエラーが出て、Forbidden の語は出ないと考えられます。エラーが何行か出てもかまいません。1 つ目の端末には変化が無いはずです。この段では、認証できなかったことを知らせるエラーが出て、Pod の一覧が表示されなければ成功だと考えられます。
ポリシーを作る
以下は公式ドキュメントの ValidatingAdmissionPolicy と ValidatingAdmissionPolicyBinding の例を、--- で 1 つのファイルにまとめたものです。demo-policy.yaml という名前で保存します。
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: "demo-policy.example.com"
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: ["apps"]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["deployments"]
validations:
- expression: "object.spec.replicas <= 5"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: "demo-binding-test.example.com"
spec:
policyName: "demo-policy.example.com"
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
environment: testポリシーと結び付けの主な項目を、次の表にまとめます。
| 項目 | 意味 |
|---|---|
matchConstraints | どの API のリクエストに当てはめるか。ここでは apps/v1 の Deployment を作る(CREATE)か変える(UPDATE)リクエスト |
validations の expression | 確かめる CEL の式。object は届いたリクエストのオブジェクトで、object.spec.replicas はマニフェストの spec: の下の replicas: をたどった値 |
failurePolicy: Fail | 式が false になったときではなく、設定の誤りや式のエラーが起きたときの扱い。Fail ならそのときも拒否される |
結び付けの validationActions | 式が false になったときの動き。Deny なので拒否する |
結び付けの matchResources | どの Namespace に当てはめるか。environment: test のラベルを持つ Namespace だけを選ぶ |
参考:Validating Admission Policy | Kubernetes、Validating Admission Policy | Kubernetes、Validating Admission Policy | Kubernetes、ValidatingAdmissionPolicy | Kubernetes API Reference、ValidatingAdmissionPolicyBinding | Kubernetes API Reference
式の <= は一般に「以下」を表すので、replicas が 5 以下なら式は true になり、リクエストは通ります。ほかの Namespace の Deployment には、このポリシーは効きません。このポリシーが動くのは、図の 3-3 の validating の段階です。
2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f demo-policy.yaml --context kind-kind
ポリシーと結び付けが作られたことを知らせる 2 行が出れば、この段は成功です。どちらも Namespace に属さないと読めるので、-n は付けていません。1 つ目の端末には変化が無いはずです。作った直後は、ポリシーがまだ効いていないことも考えられます。次の段で Deployment が拒否されずに作られてしまったら、その段に書いた手順でやり直します。
アドミッションで拒否される
以下は公式ドキュメントの Deployment の例に namespace を足し、replicas を 3 から 6 に変えたマニフェストです。nginx-deployment.yaml という名前で保存します。このファイルはあとでエディタで書き換えます。
apiVersion: apps/v1
kind: Deployment
metadata:
name: nginx-deployment
namespace: flow-demo
labels:
app: nginx
spec:
replicas: 6
selector:
matchLabels:
app: nginx
template:
metadata:
labels:
app: nginx
spec:
containers:
- name: nginx
image: nginx:1.14.2
ports:
- containerPort: 80この段ではエラーが出るのが正しい結果です。kind の利用者は認証も認可も通るはずなので、replicas が 5 を超えるこの Deployment は、アドミッションの 3-3(validating)でポリシーに拒否されるはずです。2 つ目の端末で、マニフェストを渡します。
$ kubectl apply -f nginx-deployment.yaml --context kind-kind
公式ドキュメントは、replicas が式を満たさない Deployment を作ろうとすると、次のメッセージを含むエラーが返ると説明しています。
この手順のポリシーと結び付けは例と同じ名前なので、手元でもこのメッセージがそのまま入ると考えられます。公式ドキュメントが示すのはメッセージの部分だけで、その前に kubectl が付ける部分の例はありません。このポリシーは拒否の理由(reason)を書いていないので既定の Invalid が使われ、第30回の例のような Forbidden にはならないと考えられます。先頭の語が何であっても、ポリシーのメッセージが入っていれば成功です。
拒否されずに Deployment が作られたら、ポリシーがまだ効いていなかったと考えられます。kubectl delete -f nginx-deployment.yaml --context kind-kind で消し、少し待ってからもう一度渡します。
1 つ目の端末を見ます。Deployment が作られていないので、Pod の行は増えないはずです。念のため、2 つ目の端末で Deployment を探します。
$ kubectl get deployment nginx-deployment -n flow-demo --context kind-kind
見つからないことを知らせる 1 行が出れば、Deployment は保存されていません。この段では、ポリシーのメッセージを含むエラーが出て、nginx-deployment が見つからなければ成功です。
認可で拒否される
今度は同じマニフェストを、権限の無い ServiceAccount として渡します。flow-demo の default の ServiceAccount は、第38回で見たとおり、既定では API の情報を読む権限しか持ちません。まず、Deployment を作れるかを問い合わせます。
$ kubectl auth can-i create deployments -n flow-demo --as=system:serviceaccount:flow-demo:default --context kind-kind
参考:kubectl auth can-i | Kubernetes、Using RBAC Authorization | Kubernetes
no が表示されるはずです。この段でもエラーが出るのが正しい結果です。第7回で見たとおり、kubectl apply はオブジェクトがまだ無ければ作り、あれば変更します。ここでは、すでにあるかどうかに左右されずに作成のリクエストを送るため、第5回で見た kubectl create -f で、同じマニフェストを default として渡します。
$ kubectl create -f nginx-deployment.yaml --as=system:serviceaccount:flow-demo:default --context kind-kind
参考:kubectl create | Kubernetes、User Impersonation | Kubernetes
このマニフェストはポリシーにも違反していますが、アドミッションは認可が許したときにだけ行われます。そのため、今度は認可で拒否されるはずです。見分け方の表の認可の行のとおり、Error from server (Forbidden): で始まる 1 行が出ると考えられます。例の User "clark" cannot get nodes にあたる部分は、利用者名が system:serviceaccount:flow-demo:default で、操作が Deployment の作成になると考えられます。前の段のポリシーのメッセージ(denied request: failed expression)は出ないはずです。1 つ目の端末には変化が無いはずです。この段では、kubectl auth can-i が no を返し、作成が Forbidden で拒否されて利用者名が出て、ポリシーのメッセージが出なければ成功です。
許される Deployment を作る
nginx-deployment.yaml をテキストエディタで開き、replicas: 6 の行を replicas: 3 に書き換えて保存します。ほかの行は変えません。2 つ目の端末で、kind の利用者として渡します。
$ kubectl apply -f nginx-deployment.yaml --context kind-kind
今度は認証、認可、アドミッションのすべてを通るはずなので、Deployment の nginx-deployment が作られたことを知らせる 1 行が出るはずです。公式ドキュメントによると、この Deployment は ReplicaSet を作り、3 つの nginx の Pod を立ち上げます。1 つ目の端末に nginx-deployment- で始まる Pod の行が 3 つ現れ、STATUS 列が Running になるまで待ちます。初めて使うイメージは取得に時間がかかることがあり、そのあいだは ContainerCreating のような値が出ると考えられます(第9回)。なかなか Running にならなければ、kubectl describe pod <Pod の名前> -n flow-demo --context kind-kind の Events: で理由を見ます。
Pod を作るリクエストを送るのは、kubectl ではなくコントローラーです(第12回)。認証のページによると、クラスタの中のものも外のものも、API サーバーにリクエストするものはすべて認証されるか、匿名として扱われます。コントローラーのリクエストもアドミッションまで同じ段階を通ると考えられますが、ポリシーは Deployment だけに当てはまるので、Pod は拒否されないはずです。
最後に、作った Deployment を変えるリクエストも確かめます。この段でもエラーが出るのが正しい結果です。nginx-deployment.yaml の replicas: 3 を replicas: 6 に戻して保存し、もう一度渡します。
$ kubectl apply -f nginx-deployment.yaml --context kind-kind
すでにある Deployment を変えるこのリクエストは、ポリシーの UPDATE にあたると考えられます。公式ドキュメントの AdmissionRequest の説明にも、patch のリクエストは CREATE にも UPDATE にもなり得るとあります。
そのため、作るときと同じポリシーのメッセージを含むエラーで拒否されるはずです。2 つ目の端末で、Deployment を確かめます。
$ kubectl get deployment nginx-deployment -n flow-demo --context kind-kind
READY 列が 3/3 のままなら、変更は保存されていません。この一覧のような読み取りのリクエストには、アドミッションは働きません。1 つ目の端末の Pod も 3 つのまま増えないはずです。この段では、replicas: 3 で Deployment が作られて 3 つの Pod が Running になり、replicas: 6 への変更が拒否されれば成功です。
片付け
1 つ目の端末で Ctrl+C を押して、Pod の監視を止めます。
2 つ目の端末で、flow-demo の Namespace を消します。第37回で見たとおり、中の Deployment と Pod も一緒に消えます。
$ kubectl delete namespace flow-demo --context kind-kind
中の Pod が止まるのを待つと考えられるので、コマンドが終わるまでに少し時間がかかることがあります。コマンドが終わったら、残っていないかを確かめます。
$ kubectl get namespace flow-demo --context kind-kind
見つからないことを知らせる 1 行が出れば、削除できています。STATUS 列が Terminating で表示されたら、少し待ってからもう一度実行します。
ポリシーと結び付けは Namespace に属さないと読めるので、Namespace を消しても残ると考えられます。マニフェストを渡して消します。
$ kubectl delete -f demo-policy.yaml --context kind-kind
ポリシーと結び付けが消えたことを知らせる 2 行が出るはずです。続けて、kubeconfig に足した利用者 bad-token を消し、利用者を一覧します。クラスタを残す場合も、bad-token は消しておきます。
$ kubectl config delete-user bad-token
$ kubectl config get-users
参考:kubectl config delete-user | Kubernetes、kubectl config get-users | Kubernetes
一覧に bad-token が並んでいなければ、利用者の一覧は手順の前と同じに戻っています。別の名前に置き換えた場合は、その名前で消して確かめます。kubeconfig の写しを取った場合、戻したいときは cp ~/.kube/config.bak ~/.kube/config で戻せます。要らなければ写しのファイルは消してかまいません。
最後にクラスタを削除し、消えたことを確かめます。続けて次の回を試す場合は、クラスタを残しておいてもかまいません。
$ kind delete cluster
$ kind get clusters
kind delete cluster は存在しないクラスタを指定してもエラーを返しません。kind get clusters に kind が表示されなくなったことを確かめます。kubeconfig に kind-kind のコンテキストが残っていたら、第3回の片付けと同じく kubectl config delete-context kind-kind で消します。保存した 3 つのマニフェストのファイルも、要らなければ消してかまいません。
注意点
API のリクエストの処理でつまずきやすい点を、次の表にまとめます。
| 項目 | 内容 |
|---|---|
| 認証の順番は決まっていない | どれか 1 つで認証できれば認証の段階を通るが、モジュールがどの順に動くかは保証されない |
| 認証情報が無くても 401 とは限らない | 匿名のアクセスが有効なら system:anonymous として扱われ、認可で判断される。無効なトークンは 401 になる |
| アドミッションは認可のあと | 認可で拒否されたリクエストは、アドミッションの段階まで進まない |
| 読み取りにはアドミッションが効かない | get、watch、list はアドミッションの層を通らない |
| 拒否の状態コードは仕組みで違う | 認証は 401、認可は 403。アドミッションは拒否した仕組みによる(LimitRange と ResourceQuota は 403、ValidatingAdmissionPolicy は既定で Invalid の理由にあたるコード) |
Webhook の failurePolicy の既定は Fail | Webhook の呼び出しでエラーが起きると、リクエストは拒否される |
| ValidatingAdmissionPolicy は結び付けが要る | ポリシーと結び付けの両方が無いと効かない。どちらも Namespace に属さないと読めるので、Namespace を消しても残ると考えられる |
参考:Authenticating | Kubernetes、Authenticating | Kubernetes、Authorization | Kubernetes、Admission Control in Kubernetes | Kubernetes、Dynamic Admission Control | Kubernetes、Validating Admission Policy | Kubernetes
関連記事
次の回では起動しない Pod の原因を調べる方法を扱います。
起動しない Pod の原因を調べる思いどおりに動かない Pod の原因を探す Pod のデバッグの進め方を図で説明します。kubectl describe の状態とイベント、kubectl logs、終了メッセージで見る場所を押さえ、kubectl debug でエフェメラルコンテナや Pod のコピーを使って調べる手順を kind のクラスタで紹介します。アドミッションで働く LimitRanger と ResourceQuota、403 Forbidden で拒否される例については、以下の記事で解説しています。
kubeconfig の利用者とコンテキスト、--user などで接続先が決まる順番については、以下の記事で解説しています。