Kubernetes APIは、Pod・Service・Deploymentなどクラスタ内のすべてのリソースを操作するHTTPのREST APIで、kube-apiserverが公開しています。kubectlもCI/CDツールもOperatorも、このAPIのURLへリクエストを送ってクラスタを動かしています。この記事では、APIサーバーのURLを手元・Pod内・EKS/GKE/AKSで調べる方法、/api/v1と/apisから始まるパスの読み方、curlで叩いたときの401と403の違いを、kube-apiserver v1.37.0を手元で起動して取った実際の応答とあわせて整理します。Kubernetesそのものの全体像はKubernetes(クバネティス)とは?仕組み・メリット・デメリットとDockerとの違いをわかりやすく解説が入口です。
まとめ:Kubernetes APIのURL・パス・認証と拡張の要点
- URL:手元はkubeconfigの
server欄(kubectl cluster-infoで表示)、Pod内はhttps://kubernetes.default.svc。自己構築クラスタの既定ポートは6443。 - パス:コアは
/api/v1、それ以外は/apis/<group>/<version>。Namespacedのリソースだけ/namespaces/{ns}が挟まる。 - 処理順:認証、認可(RBAC)、Admission、etcd保存。不正なトークンは401、権限不足は403。資格情報なしの要求は、匿名認証が有効なら
system:anonymousとして403、無効なら401。 - バージョン:GAはメジャー版の中で削除されない。Betaは非推奨から9か月または3マイナーの長い方で提供終了。削除済みのURLは404になる。
- 使い分け:人はkubectl、プログラムはクライアントライブラリ、curlは学習と障害の切り分けに限る。
- 拡張:CRDで独自リソースを足すか、API集約で別サーバーを
/apis配下に組み込む。
Kubernetes APIの位置づけ:kube-apiserverが公開するREST API
Kubernetes APIは、リソースの取得をGET、作成をPOST、全体の置き換えをPUT、部分更新をPATCH、削除をDELETEで行うRESTful APIです。これに変更を流し続けるwatchが加わり、コントローラーやスケジューラーはwatchでリソースの変化を受け取って動きます。RESTの原則そのものはRESTとは?REST APIの仕組みと6原則・SOAP/GraphQLとの違いを実装目線で解説で整理しています。
APIを処理するkube-apiserverは、クラスタの状態を保存するetcdに読み書きできる唯一のコンポーネントです。スケジューラーもkubeletもetcdには触れず、必ずAPIサーバーを経由します。混同されやすいのが、コンテナ内のアプリがServiceやIngressで公開する業務用のAPIです。Kubernetes APIはクラスタを管理するための別の窓口で、URLも認証も別物です。
リクエストの処理順:認証・認可・Admissionとetcd保存
たとえばPOST /api/v1/namespaces/default/podsでPodを作るリクエストは、公式「Controlling Access to the Kubernetes API」のとおり次の順に通ります。
- 認証:クライアント証明書、Bearerトークン、OIDCなどで送信者を確かめる。
- 認可:主にRBACで、その送信者がその操作をしてよいかを判定する。設計の考え方はRBACとは?ロールベースアクセス制御の仕組みと導入判断を実装視点で解説と共通です。
- Admission:既定値の補完やサイドカーの注入(Mutating)と、ポリシー違反やクォータ超過の拒否(Validating)を行う。旧PodSecurityPolicyもこの段の仕組みで、v1.25で削除されPod Security Admissionに置き換わった。
- etcd保存:通過したオブジェクトだけが保存され、watchしている各コントローラーへ変更が届く。
401と403の見分け方:kube-apiserver v1.37.0での実際の応答
トークン認証とRBACを有効にしたkube-apiserver v1.37.0に、同じURL(/api/v1/namespaces/default/pods)を4通りで送った結果です。
| 送り方 | HTTP | 応答のmessage |
|---|---|---|
| Authorizationヘッダ無し | 403 | User “system:anonymous” cannot list… |
| 存在しないトークン | 401 | Unauthorized |
| 正しいトークン・権限無し | 403 | User “dev-user” cannot list… |
| system:mastersのトークン | 200 | PodList |
読み方は3つです。401は「誰か分からない」、403は「誰かは分かったが許可が無い」。今回の環境では匿名認証が有効なため、ほかの資格情報も送らずにトークンを省くと、匿名ユーザーとして扱われて403になりました。匿名認証が無効な環境では401になります。403のmessageにsystem:anonymousと出ていたら、権限ではなくトークンの渡し方を疑ってください。なお同じ匿名リクエストでも/version・/livez・/readyzは200で返りました。既定のRBACが疎通確認用のパスを未認証のユーザーにも開けているためです。
Admissionの使い分け:Webhookと組み込みのCELポリシー
独自のAdmissionルールを足す方法は、外部サーバーを呼ぶAdmission Webhookと、APIサーバー内でCEL式を評価するポリシーの2つです。ValidatingAdmissionPolicyはv1.30で、MutatingAdmissionPolicyはv1.36でGAになり、v1.37.0では両方がadmissionregistration.k8s.io/v1で提供されていることをkubectl api-resourcesで確認しました。ラベル必須やレプリカ数の上限のように、リクエストの中身だけで判定できるルールはCELポリシーで書けます。Webhookサーバーの運用が要らず、そのサーバーが落ちてAPIリクエストが止まる事故も起きません。外部のデータを参照する判定や、OPA・Kyvernoのような既存のポリシーエンジンを使う場合はWebhookを残します。KyvernoがCELへ寄せている動きはKyvernoとは?Kubernetesのポリシー適用の仕組みとCEL移行を解説で扱っています。
APIサーバーのURLを確認する方法:kubeconfig・Pod内・クラウド別
調べる場所は、どこから接続するかで3つに分かれます。
手元の端末:kubeconfigとkubectl cluster-info
kubectlの接続先は、kubeconfig(既定は~/.kube/config)のclusters[].cluster.serverに書かれています。kube-apiserverの--secure-portの既定値は6443なので、自己構築クラスタではhttps://<IP>:6443の形になりやすく、マネージドではポート表記の無いURLが入ります。
# 現在のコンテキストが指すAPIサーバーのURL
kubectl cluster-info
kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}'
# 登録済みの全クラスタの名前とURL
kubectl config view -o jsonpath='{range .clusters[*]}{.name}{"\t"}{.cluster.server}{"\n"}{end}'
最後のコマンドは公式「Access Clusters Using the Kubernetes API」の例を短くしたものです。複数のクラスタを切り替える環境では、操作の前にこの一覧で向き先を確かめます。
Pod内:kubernetes.default.svcとServiceAccountのトークン
公式「Accessing the Kubernetes API from a Pod」によると、APIサーバーはdefault Namespaceのkubernetes Serviceとして公開され、kubernetes.default.svcで名前解決できます。環境変数KUBERNETES_SERVICE_HOSTとKUBERNETES_SERVICE_PORT_HTTPSにも同じ宛先が入るので、URLを設定ファイルに書く必要はありません。
SA=/var/run/secrets/kubernetes.io/serviceaccount
NS=$(cat $SA/namespace)
curl --cacert $SA/ca.crt \
-H "Authorization: Bearer $(cat $SA/token)" \
"https://kubernetes.default.svc/api/v1/namespaces/$NS/pods?limit=5"
公式ドキュメントは、証明書がkubernetes.default.svcというホスト名に対して有効である保証は無く、$KUBERNETES_SERVICE_HOSTのホストかIPには有効な証明書が提示されると注記しています。証明書エラーが出たら宛先を環境変数側へ切り替えます。
EKS・GKE・AKS:クラウドCLIによるエンドポイント取得
kubeconfigを作る前の段階や、CIで接続先を組み立てる場合は各クラウドのCLIから取ります。返る値の形式がそろっていません。
# Amazon EKS
aws eks describe-cluster --name <クラスタ名> --query cluster.endpoint --output text
# GKE
gcloud container clusters describe <クラスタ名> --location=<リージョンまたはゾーン> --format="value(endpoint)"
# AKS:公開FQDN
az aks show -g <リソースグループ> -n <クラスタ名> --query fqdn -o tsv
# プライベートFQDNを確認する場合
az aks show -g <リソースグループ> -n <クラスタ名> --query privateFqdn -o tsv
| サービス | 返る値 | URLにするには |
|---|---|---|
| Amazon EKS | https付きのURL | そのまま使う |
| GKE | IPアドレスのみ | https://を前に付ける |
| AKS | ホスト名(FQDN)のみ | https://を前に付ける |
EKSのdescribe-clusterリファレンスは、クラスタがACTIVEになるまでエンドポイントとCAデータを取得できないと書いています。GKEはgcloud container clusters describe、AKSはaz aks showが入口です。AKSの料金や作成手順はAKS(Azure Kubernetes Service)とは?料金の実額・Automaticとの違い・az aks createの手順【2026年10月】にまとめています。
エンドポイントのURL構造:/apiと/apis・サブリソース・クエリ
パスはグループとスコープで決まります。マニフェストのapiVersionとkindを手掛かりに、ディスカバリーでリソース名とスコープを確認するとURLを組み立てられます。
# コアグループ・Namespacedの一覧
/api/v1/namespaces/{ns}/pods
# 名前付きグループ・Namespacedの個別
/apis/apps/v1/namespaces/{ns}/deployments/{name}
# 全Namespace横断の一覧
/apis/apps/v1/deployments
# クラスタースコープ(namespacesが入らない)
/api/v1/nodes/{name}
# サブリソース
/api/v1/namespaces/{ns}/pods/{name}/log
コアグループ(Pod・Service・Node・Secretなど)だけが/api/v1で、apps・batch・networking.k8s.ioなどの名前付きグループは/apis/<group>/<version>です。API上のリソース種別は、グループ・バージョン・リソース名(GVR)で特定します。個々のオブジェクトを指定するには、さらに名前と、Namespacedの場合はNamespaceが必要です。マニフェストのapiVersionはこのグループとバージョンをつないだ値で、Deploymentならapps/v1、Podならグループが空なのでv1だけになります。
サブリソースとRBAC:pods/logはpodsと別の権限
個別リソースの後ろに続く/status・/log・/scale・/execはサブリソースで、RBACでは別の権限として扱われます。Podのget・list・watchだけを許したRoleを付けたServiceAccountのトークンで試すと、Pod一覧は200、同じNamespaceの/pods/<name>/logは403でした。kubectl auth can-i get pods --subresource=logもnoを返します。ログは読ませるがexecはさせない、という線引きはこの仕組みで作ります。
一覧の分割取得とwatch:limit・continue・resourceVersion
公式「Kubernetes API Concepts」にあるとおり、大きな一覧はlimitとcontinueで分割して取ります(分割取得はv1.29でGA)。ConfigMapを5個作って?limit=2で取ると、2件とremainingItemCount: 3、次ページ用のcontinueトークンが返りました。kubectlもkubectl get podsの裏で?limit=500を付けて分割取得しています。
変更を受け取り続けるには?watch=1を付けます。fieldSelector=metadata.name%3Dcm1で1件に絞ってwatchし、途中でそのConfigMapを書き換えると、ADDED(resourceVersion 222)に続いてMODIFIED(同244)が1行ずつ流れてきました。切断後は最後に受け取ったresourceVersionから再開できますが、履歴の保持範囲を過ぎると410 Goneになります。その場合は一覧を再取得して状態を同期し、その一覧のresourceVersionからwatchを再開します。過去の全イベントの再取得は保証されません。絞り込みにはlabelSelectorとfieldSelectorの2種類があります。
疎通確認と仕様取得:/livez・/readyz・/openapi/v3
/versionはサーバーの版、/livezと/readyzはAPIサーバー自身の死活と受付可否を返します。公式「Kubernetes API health endpoints」によれば/healthzはv1.16から非推奨で、機械判定ではHTTPステータスコードを見ます。人が中身を見るならkubectl get --raw='/readyz?verbose'で、項目ごとに[+]etcd okのような行が並びます。
APIの機械可読な仕様は/openapi/v3から取れます。英語版の公式ページではOpenAPI v3はv1.27から安定版(機能ゲートは撤去済み)ですが、2026年10月時点の日本語版ページは「Kubernetes v1.37では、OpenAPI v3によるAPI仕様をベータサポートとして提供しています」のまま更新されていません。版に関わる記述は英語版を正としてください。OpenAPIの読み方はOpenAPIとは?Swaggerとの違い(仕様とツール)とYAMLの書き方・3.0〜3.2の差分が参考になります。
kubectlが送っているHTTPリクエスト:詳細ログ・rawモード・api-resources
kubectlは、kubeconfigのURLと資格情報を使ってAPIを呼ぶクライアントです。kubectl get pods -v=6で実際のリクエストが見えます。v1.37.0では次の3本が出ました。
verb="GET" url="https://127.0.0.1:36443/api?timeout=32s" status="200 OK"
verb="GET" url="https://127.0.0.1:36443/apis?timeout=32s" status="200 OK"
verb="GET" url="https://127.0.0.1:36443/api/v1/namespaces/default/pods?limit=500" status="200 OK"
先の2本は、どのリソースがどのグループにあるかを調べるディスカバリーです。v1.30でGAになったAggregated discoveryにより、/apisへの1回の要求で、名前付きグループのリソース一覧(今回のv1.37.0環境では21グループ)が返ります。これはkubectlが集約ディスカバリー用のAcceptヘッダを送るためで、ヘッダを指定しないcurlの要求では非集約形式が返ります。コアグループは別途/apiから取得します。逆に任意のパスを取りたいときはkubectl get --raw /apis/apps/v1を使います。
リソースの一覧はkubectl api-resourcesです。アドオンを入れていないv1.37.0では71種類で、NAMESPACED列がtrueのものが34、false(クラスタースコープ)が37でした。APIVERSION列がそのままマニフェストのapiVersionとURLのグループ部分に対応します。正確なフィールドはkubectl explain deploymentで引けます。
curlで直接叩く手順:URL・CA証明書・トークン
トークン発行のkubectl create tokenはv1.24で追加されました。--durationを省くと有効期間はサーバーが決め、検証環境では3600秒、--duration=10mでは600秒のトークンが出ました。
APISERVER=$(kubectl config view --minify -o jsonpath='{.clusters[0].cluster.server}')
kubectl config view --minify --raw --flatten \
-o jsonpath='{.clusters[0].cluster.certificate-authority-data}' | base64 -d > ca.crt
TOKEN=$(kubectl create token default -n default)
curl --cacert ca.crt -H "Authorization: Bearer $TOKEN" "$APISERVER/version"
curl --cacert ca.crt -H "Authorization: Bearer $TOKEN" \
"$APISERVER/api/v1/namespaces/default/pods?limit=5"
2本目は403になるのが普通です。default ServiceAccountにはPod一覧の権限が無く、認証は通って認可で止まっています。試す場合は、default NamespaceのPodに対するlistを許すRoleを作り、RoleBindingでdefault ServiceAccountに関連付けます。検証後は作成したRoleBindingとRoleを削除します。認証を丸ごと任せたいならkubectl proxyを起動し、curl http://localhost:8001/api/v1/namespaces/default/podsのように既定ポート8001へ送ります。
プログラムから使う:公式クライアントライブラリ
アプリから呼ぶなら生のHTTPを組み立てず、公式クライアントライブラリ(Goのclient-go、Pythonのkubernetesなど)を使います。kubeconfigやPod内のServiceAccountからURLと資格情報を読み込み、トークンの更新も任せられます。
# pip install kubernetes
from kubernetes import client, config
config.load_kube_config() # Pod内なら config.load_incluster_config()
print(client.Configuration.get_default_copy().host) # 接続先APIサーバーのURL
v1 = client.CoreV1Api()
for pod in v1.list_namespaced_pod("default", limit=5).items:
print(pod.metadata.name, pod.status.phase)
Server-Side Apply:フィールドごとの所有者と競合
kubectl apply --server-sideは、差分の計算をAPIサーバー側で行うServer-Side Apply(v1.22でGA)を使います。APIサーバーはフィールドごとに書いた主体(マネージャー)をmetadata.managedFieldsに記録します。ConfigMapのdata.colorをkubectlで適用したあと、別のマネージャー名(--field-manager=other-tool)で同じフィールドを書き換えようとすると、Apply failed with 1 conflict: conflict with "kubectl": .data.colorで止まりました。CIがServer-Side Applyで、HPAやOperatorなど別のマネージャーが持つフィールドの値を変えようとすると、この競合として表に出ます。kubectl editやapply以外のPUT・PATCH(managedFields上はUpdate)は所有権の競合では拒否されず、そのまま上書きして所有者が入れ替わります。上書きしてよいと判断したときだけ--force-conflictsを付けます。
APIグループとバージョン:Alpha・Beta・GAと非推奨ポリシー
APIは安定度に応じてAlpha、Beta、GAと進みます。公式「Kubernetes Deprecation Policy」のRule #4aは次のとおりです。
| 段階 | 既定 | 削除の扱い |
|---|---|---|
| Alpha | 無効 | 予告なく削除され得る |
| Beta | v1.24以降の新規Betaは無効 | 非推奨から9か月か3マイナーの長い方で終了 |
| GA(v1など) | 有効 | メジャー版の中では削除されない |
Betaは導入から9か月または3マイナーの長い方を超えないうちに非推奨にされる決まりです。v1.24より前からある既存のBetaは既定で有効のまま残っています。
削除済みAPIのURL:404応答と移行先
公式「Deprecated API Migration Guide」に削除の実例が並んでいます。extensions/v1beta1のDeploymentはv1.16、networking.k8s.io/v1beta1のIngressとCRDのapiextensions.k8s.io/v1beta1はv1.22、flowcontrol.apiserver.k8s.io/v1beta3はv1.32で提供を終えました。v1.37.0にこのうちDeployment・Ingress・FlowSchemaの旧URLを送ると3本とも404で、/apis/apps/v1/.../deploymentsは200です。古いURLを直書きしたスクリプトは、アップグレードした日から404を返し始めます。
削除される前に気づく手段は2つあります。非推奨のAPIを呼ぶと応答にWarningヘッダが付き、kubectlは画面に警告を出します。もう1つはAPIサーバーのメトリクスapiserver_requested_deprecated_apisで、どのグループ・バージョンが呼ばれ、どの版で削除予定かが記録されます。どちらもv1.19で入った仕組みです。
上流でサポートされているのは直近3マイナーで、2026年10月31日時点では1.35〜1.37です。1.34は2026年10月27日にサポートを終え、最新のパッチは2026年10月28日にGitHubで公開された1.37.2です。版ごとの期限と確認方法はKubernetesの最新バージョンは1.37|確認方法・EOL一覧とアップグレード手順で更新しています。
Kubernetes APIの拡張:CRDとAPI集約レイヤー
APIを拡張する方法は2つあります。CustomResourceDefinition(CRD)は、マニフェストを適用するだけでAPIサーバー自身に新しいリソースを足す方法です。apiextensions.k8s.io/v1はv1.16でGAになりました。手順の一次情報は公式「Extend the Kubernetes API with CustomResourceDefinitions」です。
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: applications.example.com
spec:
group: example.com
scope: Namespaced
names:
plural: applications
singular: application
kind: Application
versions:
- name: v1
served: true
storage: true
schema:
openAPIV3Schema:
type: object
required: ["spec"]
properties:
spec:
type: object
properties:
image: { type: string }
required: ["image"]
適用すると/apis/example.com/v1/namespaces/{ns}/applicationsが生え、get・list・watch・create・patchなど8種類の操作が使えるようになりました。spec: {}のまま作ろうとするとspec.image: Required value、specごと省くとspec: Required valueで拒否されます。apiextensions.k8s.io/v1のCRDは構造的スキーマが必須ですが、requiredや値の制約を書かなければ、その条件に反する入力は保存されてしまいます。
CRDを定義しただけでは、オブジェクトが保存されるだけで何も動きません。作成を検知して処理するコントローラー(Operator)が要ります。Goでの雛形づくりはKubebuilderとは?kubectlとの違い、コントローラーとOperatorの違いと開発手順【v4.16】で扱っています。
もう1つのAPI集約レイヤーは、自作のAPIサーバーをAPIServiceで登録し、/apis/<group>へのリクエストをそのサーバーへ中継させる方法です。kubectl topが使うmetrics.k8s.io(metrics-server)が代表例です。etcd以外に保存したい、取得のたびに計算したいといった要件が無ければ、運用するサーバーが増えないCRDを選びます。
判断基準:呼び出し方とエンドポイントの公開範囲
| 手段 | 採用する場面 | 見送る場面 |
|---|---|---|
| kubectl | 人の日常操作・CIのデプロイ | アプリからの常時呼び出し |
| クライアントライブラリ | Operator・社内ツール・Pod内アプリ | 一度きりの調査 |
| curl直叩き | 学習・障害の切り分け | 本番の定常処理 |
curlを本番の定常処理に組み込むのはやめるべきです。トークンの更新、CA証明書の管理、非推奨APIへの追従を自前で抱えることになり、前述の削除済みURLのように版を上げた日に止まります。
APIサーバーのエンドポイントをインターネットに出すかも決めておきます。運用者の接続元を固定できるなら許可IPを絞り、社内ネットワークやVPN経由に限れるならプライベートエンドポイントにします。クラウドの外にあるCIから接続元を絞れない場合だけ、パブリックのまま短い有効期間のトークンと最小権限のRBACで守ります。URLが漏れても認証と認可の2段で止まることが前提です。クラスタ構築からエンドポイントの公開範囲、EKS・GKE・AKSの選定までの相談はインフラ構築(AWS・Google Cloud・Azure)で受けています。手元で同じ実験をしたい場合は、kindでクラスタを作るk8sをローカルで動かす手順|kindでクラスタ構築からDeployment公開・撤収までが使えます。
よくある質問
Kubernetes APIとkubectlの違いは?
Kubernetes APIはクラスタ側の窓口(kube-apiserverが公開するHTTPのAPI)で、kubectlはそれを呼ぶCLIクライアントです。kubectl get pods -v=6を付けると、送っているURLとHTTPメソッドが表示されます。
Kubernetes APIサーバーのURLとポート番号はどこで分かる?
手元ではkubectl cluster-infoか、kubeconfigのserver欄で分かります。kube-apiserverの既定ポートは6443で、EKS・GKE・AKSはポート表記の無いHTTPSのURLです。Pod内からはhttps://kubernetes.default.svcか環境変数KUBERNETES_SERVICE_HOSTを使います。
curlでKubernetes APIを叩くと403になるのはなぜ?
認証は通ったが、そのユーザーやServiceAccountに操作の権限が無いためです。messageにsystem:anonymousと出ている場合は、トークンが送られていないか、Authorizationヘッダの書き方が誤っています。トークン自体が不正なら403ではなく401が返ります。
apiVersionには何を書けばいい?
リソースが属する「グループ/バージョン」を書きます。Pod・Service・ConfigMapはv1、Deployment・StatefulSetはapps/v1、Ingress・NetworkPolicyはnetworking.k8s.io/v1、CronJobはbatch/v1です。kubectl api-resourcesのAPIVERSION列で確認できます。
Alpha版のAPIを本番で使ってよい?
使わないでください。Alphaは既定で無効で、予告なく変更・削除されます。本番はGAを使い、Betaを使う場合も非推奨から9か月または3マイナーで提供が終わる前提で、アップグレードのたびにリリースノートとapiserver_requested_deprecated_apisを確認します。