Kubernetes特集 | 第4章(Day 5 / 6)
Service・DNS・Ingress/Gateway API・NetworkPolicy・PVC。
「繋がらない」「データが消えた」を、層に分けて確かめられるようにします。
5日目です。昨日の第3章では、Deployment api の前にServiceを置き、ReadyなPodにだけ通信が届くところまで確かめました。ただ、そのときは同じNamespaceの中から http://api と呼んだだけです。今日は、別のNamespaceから名前で呼び、外からの入口の考え方を押さえ、NetworkPolicyで通信を絞ります。後半はストレージに移って、Podを消しても残るデータを作ります。
昨日の最後に lab は片付けたので、今日も作るところから始めます。Serviceの port と targetPort があやふやな方は、第3章の「Serviceとラベル選択」を先に読んでおくと、演習1が追いやすくなります。
💡 Kubernetesのネットワークは、Pod IP・Service・DNS名・外からの入口の4つの層に分けて考えます。Pod IPは作り直すと変わり、Serviceがそれを隠して変わらない名前を渡し、IngressやGateway APIが外からのHTTPの入口を表します。NetworkPolicyは許可する通信を狭めますが、効くかどうかはネットワークプラグイン(CNI)しだいです。ストレージでは、PVCは「欲しい」という要求、PVは実体、StorageClassは作り方の方針です。PVCを作っただけではデータは守れません。バックアップと、別の場所への復元テストまでが設計の範囲です。
この章で手に入れてほしいのは、「繋がらない」を1つの症状として扱わない目です。名前が引けないのか、転送先がないのか、ポートが違うのか、ポリシーに止められているのか。それぞれ見る場所が違います。ストレージも同じで、Bound と表示されても、アプリが書けるとは限りません。
今日はNamespaceを2つ使います。lab にサーバー側の api とストレージの演習用Pod、lab-front に呼び出す側の一時Podを置きます。最終的に図4-1の形になります。
図4-1: 今日つくる構成。上半分がネットワーク、下半分がストレージ。
| ファイル | 中身 | 作る節 |
|---|---|---|
api.yaml | Deployment と Service(第3章の簡略版) | 今日つくる構成 |
np-allow-front.yaml | NetworkPolicy | NetworkPolicyで通信を絞る |
notes.yaml | PVC と Pod(emptyDir と PVC の両方を持つ) | Podの一時ファイルとPVC |
np-front-egress.yaml・pvc-bad.yaml | 演習用 | わざと壊す演習 |
まずContextを確かめて、Namespaceを2つ作ります。
kubectl config current-context # kind-xxx / minikube / docker-desktop などが出ればOK
kubectl create namespace lab
kubectl create namespace lab-front
サーバー側は、昨日の完成形からProbeと設定を省いた簡略版です。今日はネットワークの話に集中したいので、2台にしています。
# api.yaml ― 第3章の api を簡略化。Deployment と Service を1ファイルに
apiVersion: apps/v1
kind: Deployment
metadata:
name: api
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: api
template:
metadata:
labels:
app.kubernetes.io/name: api
spec:
containers:
- name: api
image: nginx:stable
ports:
- name: http
containerPort: 80
readinessProbe:
httpGet:
path: /
port: http
periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
name: api
spec:
selector:
app.kubernetes.io/name: api
ports:
- name: http
port: 80
targetPort: http
kubectl -n lab apply -f api.yaml
kubectl -n lab rollout status deployment/api
kubectl -n lab get svc,endpointslice,pod -o wide
⚠️ YAMLの中身は学習用です。 nginx:stable のような動き続けるタグ、requests/limitsのない定義は、本番ではそのまま使いません(第3章の注記を参照)。今日のコマンドは、kindでの動きを基準に書いています。minikubeやDocker Desktopで出力が違う箇所は、その場で注記します。
Kubernetesの通信で迷う原因の多くは、いくつもの層を1つに見てしまうことです。図4-2のように、下から順に分けて考えます。
図4-2: ネットワークは4つの層に分けて見る。障害のときは、どの層まで届いているかを確かめる。
かなり雑に言うと、Pod IPは「今日の席番号」、Serviceは「部署の代表番号」、DNS名は「電話帳の名前」、外からの入口は「会社の受付」です。席替えがあっても代表番号は変わらず、電話帳から代表番号を引けば、いま座っている人に繋がります。
Serviceを作ると、クラスター内のDNS(多くの環境ではCoreDNS)が名前を答えるようになります。名前は <Service名>.<Namespace>.svc.<クラスターのドメイン> の形で、クラスターのドメインは既定で cluster.local です。
| 呼び出す側 | 書ける名前 | 理由 |
|---|---|---|
| 同じNamespace(lab) | api | Podの検索ドメインに lab.svc.cluster.local が入っている |
| 別のNamespace(lab-front) | api.lab | 検索ドメインの svc.cluster.local が補われる |
| どこからでも | api.lab.svc.cluster.local | 省略のない完全な名前(FQDN) |
lab-front に使い捨てのPodを作って、確かめます。
# 名前を引く(DNSの層)
kubectl -n lab-front run dns --image=busybox:1.36 --rm -i --restart=Never -- \
nslookup api.lab.svc.cluster.local
# 名前で呼ぶ(HTTPまで)。同じNamespaceではないので、api だけでは届かない
kubectl -n lab-front run curl --image=curlimages/curl:8.10.1 --rm -i --restart=Never -- \
curl -s -m 5 -o /dev/null -w '%{http_code}\n' http://api.lab
# 出力の例(IPは環境によって違う)
Name: api.lab.svc.cluster.local
Address: 10.96.142.17
pod "dns" deleted
200
pod "curl" deleted
DNSが返したのは、Podではなく Service の ClusterIP(10.96.x.x のような番号)です。実際にどのPodへ届くかは、EndpointSliceが決めます。だから、名前が引けたのに繋がらないときは、次にEndpointSliceを見ます。
kubectl -n lab get endpointslice -l kubernetes.io/service-name=api
kubectl -n lab get pod -o wide # EndpointSlice の IP と、Pod の IP が一致するか
古い入門記事では kubectl get endpoints を使っていますが、Endpoints APIは v1.33 で非推奨になりました。今後はEndpointSliceを見る習慣を付けておくと、表示の警告に戸惑いません。
個々のPodのIPをDNSで知りたい場合は、clusterIP: None のHeadless Serviceを使います。DNSはServiceの仮想IPではなく、ReadyなPodのIPを全部返します。第3章で触れたStatefulSetと組み合わせて、db-0.db のようにPodを1つずつ名前で呼ぶ用途です。通常のWeb APIでは、振り分けをServiceに任せるほうが扱いやすいです。
⚠️ クラスターのドメインは、変えられていることがあります。 cluster.local は既定値です。社内のクラスターで名前が引けないときは、Podの中の /etc/resolv.conf の search 行を見てください(kubectl -n lab-front run r --image=busybox:1.36 --rm -i --restart=Never -- cat /etc/resolv.conf)。
Serviceの type は、どこまで外に開くかを決めます。3つは別物というより、図4-3のように外側へ重ねていく関係です。
図4-3: Serviceの種類は「どこまで外に開くか」。外側ほど、クラウドや費用の都合が増える。
| type | どこから届くか | 主な用途 | 気をつけること |
|---|---|---|---|
| ClusterIP | クラスターの中だけ | アプリ間の通信、Ingress/Gatewayの転送先 | 外から直接は届かない |
| NodePort | NodeのIPと番号 | 外部ロードバランサーからの転送、検証 | Nodeのファイアウォール。Nodeが入れ替わるとIPも変わる |
| LoadBalancer | 外部ロードバランサーのアドレス | TCP/UDPをそのまま外に出す | Serviceごとに1台作られることが多く、台数と費用が増える。アノテーション・サブネット・IPの予約方法がプロバイダーごとに違う |
学習環境で外から試したいだけなら、Serviceの種類を変えずに port-forward を使うのが手軽です。
kubectl -n lab port-forward svc/api 8080:80 # このターミナルは開いたまま
# 別のターミナルで
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8080 # 200
port-forward は、kubectlがAPI Server経由でPodを1つ選んで直接つなぐ仕組みです。Serviceの振り分けやreadinessの確認にはならないので、動作確認用と割り切ってください。Ctrl+Cで止めます。
⚠️ kindでは、NodePortやLoadBalancerは手元から届かないことがあります。 kindのNodeはDockerコンテナなので、クラスター作成時にポートの対応(extraPortMappings)を書いていなければ、NodePortにブラウザから届きません。LoadBalancerは、クラウドでなければ EXTERNAL-IP が <pending> のままになります(minikubeは minikube tunnel、kindは別のツールで補えます)。本番で使うときは、その環境のドキュメントで作り方と費用を確かめてください。
外からのHTTP/HTTPSを、ホスト名とパスでServiceへ振り分けるのが、IngressとGateway APIです。どちらもYAMLだけでは動きません。YAMLを読んで実際に通信を中継するプログラム(Ingress ControllerやGatewayの実装)が、クラスターに入っている必要があります。
図4-4: Ingressは1枚にまとめて書く。Gateway APIは担当者ごとにリソースを分ける。
Ingressは長く使われてきたAPIで、今も使えます。ただし、ホスト名とパス以外の設定(タイムアウト、ボディサイズ、リダイレクトなど)は、Controllerごとのアノテーションで書くことが多く、Controllerを替えるとYAMLも書き直しになります。Gateway APIは、それを標準の項目として持ち、インフラ担当とアプリ担当の責任を分けられるように作られています。
📌 広く使われてきた Ingress NGINX(kubernetes/ingress-nginx)は、2026年3月で保守を終えました。 既存の環境はそのまま動きますが、新しいリリースやセキュリティ修正は出ません。Kubernetesプロジェクトは、Gateway APIか、別のIngress Controllerへの移行を勧めています。終了したのはこのControllerで、IngressというAPIが廃止されたわけではありません。いま新しく入口を作るなら、まずGateway APIに対応した実装を検討するのが順当です。
Gateway APIで書くと、次のような形になります。example-gateway と api.example.com は例です。
# httproute.yaml(例)― api.example.com/ への通信を Service api へ
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: api
spec:
parentRefs:
- name: example-gateway # 運用者が用意した Gateway の名前
namespace: infra
hostnames:
- "api.example.com"
rules:
- matches:
- path:
type: PathPrefix
value: /
backendRefs:
- name: api # Service の名前と port
port: 80
今日はControllerを入れないので、適用はしません。代わりに、自分のクラスターに何が入っているかを確かめます。
kubectl get ingressclass # Ingress Controller が入っていれば一覧が出る
kubectl api-resources | grep gateway.networking # Gateway API の定義(CRD)が入っているか
kindの初期状態では、どちらも何も出ません。この状態で上のHTTPRouteを適用すると、no matches for kind "HTTPRoute" というエラーになります。Gateway APIはKubernetes本体とは別に配布され、定義(CRD)と実装を後から入れる仕組みだからです。Ingressのほうは、定義は最初からあるので適用は成功します。ただしControllerがなければ ADDRESS が空のままで、どこにも繋がりません。「適用できた」と「通信が届く」は別、というのは第3章のProbeと同じ話です。
⚠️ 外部公開のYAMLは、環境しだいです。 DNSのゾーン、証明書の発行、公開サブネット、WAF、ファイアウォール、クラウドのIAMが準備されていなければ、適用に成功しても使えません。Gateway APIは実装ごとに対応している機能と版が違うので、使う実装の対応表で確かめてください。Gateway API自体は2026年6月にv1.6が出ています。
ここまでの状態では、クラスター内のどのPodからでも api に届きます。多くのクラスターでは、Pod同士の通信は初めから全部許可されているからです。NetworkPolicyは、Podに対して「この通信だけ許可する」を書くリソースです。
押さえておく決まりは3つです。
# np-allow-front.yaml ― api への受信を、lab-front の TCP 80 だけに絞る
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-allow-front
spec:
podSelector:
matchLabels:
app.kubernetes.io/name: api # 守る対象のPod
policyTypes: [Ingress]
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: lab-front # Namespaceに自動で付くラベル
ports:
- protocol: TCP
port: 80 # Serviceの番号ではなく、Pod側の番号
このYAMLを適用すると、裏で起きること
kubectl -n lab apply -f np-allow-front.yaml
# lab-front から:許可されているので 200
kubectl -n lab-front run curl --image=curlimages/curl:8.10.1 --rm -i --restart=Never -- \
curl -s -m 5 -o /dev/null -w '%{http_code}\n' http://api.lab
# lab の中から:同じNamespaceでも、許可していないので届かない(5秒で打ち切り)
kubectl -n lab run curl --image=curlimages/curl:8.10.1 --rm -i --restart=Never -- \
curl -s -m 5 -o /dev/null -w '%{http_code}\n' http://api
# 出力の例(NetworkPolicyが効いている場合)
200
pod "curl" deleted
000
pod "curl" deleted
pod lab/curl terminated (Error)
000 は、HTTPの応答が返ってこなかったという意味です。拒否の応答が返るのではなく、パケットが捨てられてタイムアウトします。これがNetworkPolicyで止められたときの典型的な見え方で、「アプリが落ちている」と取り違えやすい点です。
⚠️ NetworkPolicyが効くかどうかは、CNIしだいです。 上の2つ目が 200 になったら、そのクラスターのCNIはNetworkPolicyを実装していません(YAMLは受理されるので、エラーでは気づけません)。最近のkindの既定CNI(kindnet)は対応していますが、古いkindでは効きません。minikubeは minikube start --cni=calico のように対応CNIで作り直します。Docker Desktop内蔵のKubernetesは、効かないことがあります。本番では、使っているCNIのドキュメントで対応を確かめてから設計してください。
本番に入れるときは、いきなり「全部拒否」にしません。DNS、監視、ログ収集、Namespaceをまたぐ必要な通信を先に洗い出し、許可のPolicyを用意してから絞ります。また、NetworkPolicyはアプリの認証の代わりにはなりません。侵入されたときに横へ広がるのを減らす、防御の1層です。
ここからストレージです。コンテナの中に書いたファイルは、コンテナを作り直すと消えます。emptyDir は、同じPodの中のコンテナで共有できる一時領域ですが、Podが消えれば一緒に消えます。Podより長くデータを残すには、PVC(PersistentVolumeClaim)を使います。
図4-5: PVCは要求、PVは実体、StorageClassは作り方の方針。emptyDirはPodの一部。
まず、クラスターにどのStorageClassがあるかを見ます。
kubectl get storageclass
# 出力の例(kind)
NAME PROVISIONER RECLAIMPOLICY VOLUMEBINDINGMODE ALLOWVOLUMEEXPANSION AGE
standard (default) rancher.io/local-path Delete WaitForFirstConsumer false 2d
| 列 | 意味 |
|---|---|
(default) | PVCで storageClassName を省略したときに使われる |
RECLAIMPOLICY | PVCを消したとき、PVと中のデータを消す(Delete)か残す(Retain)か |
VOLUMEBINDINGMODE | WaitForFirstConsumer は、PVCを使うPodの置き場所が決まってからPVを作る。Immediate はすぐ作る |
ALLOWVOLUMEEXPANSION | あとから容量を増やせるか |
次に、PVCと、それを使うPodを作ります。Podには emptyDir とPVCの両方を付けて、違いを並べて確かめます。
# notes.yaml ― PVC と、それを使う Pod
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: notes
spec:
accessModes: [ReadWriteOnce] # 1つのNodeからだけマウントできる、という意味が多い
resources:
requests:
storage: 1Gi
# storageClassName を省略 → (default) の StorageClass を使う
---
apiVersion: v1
kind: Pod
metadata:
name: notes
spec:
terminationGracePeriodSeconds: 5 # sleep は停止の合図を無視するので、待ち時間を短く
containers:
- name: notes
image: busybox:1.36
command: ["sh", "-c", "sleep 3600"]
volumeMounts:
- name: cache
mountPath: /cache
- name: data
mountPath: /data
volumes:
- name: cache
emptyDir: {} # Podと一緒に消える
- name: data
persistentVolumeClaim:
claimName: notes # Podより長く残る
適用して、PVCとPVの状態を見ます。
kubectl -n lab apply -f notes.yaml kubectl -n lab get pvc notes kubectl get pv # PVは Namespace を付けずに見るkindでは、Podが置かれるまでの数秒間、PVCが Pending と表示されることがあります。WaitForFirstConsumer なので、Podの置き場所が決まるまでPVを作らないからです。describe pvc のEventsに waiting for first consumer to be created before binding と出ていれば、正常な待ちです。Podが動き出すと Bound に変わり、get pv に pvc- で始まる名前のPVが1つ現れます。
では、両方に書いて、Podを消して、作り直します。
kubectl -n lab exec notes -- sh -c 'date > /data/hello.txt; date > /cache/hello.txt'
kubectl -n lab delete pod notes
kubectl -n lab apply -f notes.yaml # PVCはそのまま。Podだけ作り直される
kubectl -n lab wait --for=condition=Ready pod/notes --timeout=60s
kubectl -n lab exec notes -- cat /data/hello.txt # さっきの日時が残っている
kubectl -n lab exec notes -- cat /cache/hello.txt # No such file or directory
Podを消しても、PVCを消していなければデータは残ります。逆に言うと、kubectl delete pvc notes を実行すると、RECLAIMPOLICY が Delete のStorageClassでは、PVと中身も一緒に消えます。本番では、大事なデータのStorageClassを Retain にするか、消す手順に承認を入れるかを決めておきます。
⚠️ StorageClassは環境ごとに違います。 minikubeは standard(k8s.io/minikube-hostpath)、Docker Desktopは hostpath が既定で、どちらも Immediate なのでPVCはすぐ Bound になります。クラウドでは、ディスクの種類、Zoneの制約(あるZoneのディスクは別のZoneのNodeに付かない)、暗号化、拡張、スナップショットの対応がStorageClassごとに違います。原稿にある fast-encrypted のような名前は例で、自分のクラスターにあるとは限りません(演習3)。
アクセスモードも、名前どおりに受け取らないようにします。ReadWriteOnce は「1つのNodeからだけ読み書きできる」という意味のことが多く、同じNodeの複数Podからは使えても、別のNodeのPodとは共有できません。複数のNodeで共有したいからと ReadWriteMany を選んでも、アプリが同時書き込みを想定していなければファイルが壊れます。
第3章で触れたStatefulSetは、volumeClaimTemplates でPodごとに専用のPVCを作ります。db-0 が作り直されても、同じ data-db-0 のPVCに繋がります。ただし、壊れたトランザクションを直したり、レプリカ間の整合をとったりするのはDB自身の仕事です。StatefulSetが保証するのは、名前とディスクの対応までです。
「YAMLはGitにあるから大丈夫」は、半分しか正しくありません。YAMLから作り直せるのは定義だけで、PVCの中のデータ、外部のDNSや証明書、Secretの実際の値は戻りません。何を、どこへ、どの頻度で取るかを一覧にします。
| 対象 | 取り方の例 | 見落としやすい点 |
|---|---|---|
| リソースの定義 | Git、GitOpsのリポジトリ | 手で kubectl edit した変更はGitにない |
| Secretの値 | 外部のシークレット管理(第3章) | 平文のバックアップを作らない |
| PVCのデータ | ボリュームスナップショット、アプリのバックアップ機能 | スナップショットは「ある瞬間のディスク」で、DBの整合までは保証しないことがある |
| 外の依存 | DNSゾーン、証明書、ロードバランサーの設定 | クラスターを作り直しても戻らない |
DBなら、スナップショットの前にDBのバックアップモードを使うか、DB自身のダンプ・ログを取ります。そのうえで、2つの数字を決めます。RPO(どの時点まで戻れればよいか)とRTO(何分で戻せればよいか)です。決めた数字は、実際に復元してみるまで当てになりません。
復元テストは、元のPVCに上書きせず、別のNamespaceに戻して、件数やスキーマを確かめます。保存先が元と同じ障害の範囲(同じZone、同じアカウント)にあれば、ディスクと一緒に失う可能性もあります。
kubectl api-resources | grep -i volumesnapshot # スナップショットの定義が入っているか
kindの初期状態では何も出ません。VolumeSnapshot は、スナップショット用の定義(CRD)・コントローラー・対応したCSIドライバーがそろって初めて使えます。クラウドのマネージドKubernetesでは用意されていることが多いので、自分の環境で確かめてください。
📌 障害のときは、まず新しい書き込みを止めるか、読み取り専用に切り替えるかを判断します。急いで古いPVCやPVを消すと、復旧の材料まで失います。削除の手順と、誰が承認するかは、障害が起きる前に決めておきます。
今日作った api・NetworkPolicy・notes を使って、3つ壊します。壊したら、図4-2のどの層で止まっているかを考えてから直してください。
Serviceの転送先を、nginxが待ち受けていない 8080 に変えます。アプリの設定を変えたのに、Serviceを直し忘れた、という状況です。先にNetworkPolicyを外しておきます。Policyが許可しているのは80番だけなので、残したままだと8080番への通信はPolicyに捨てられ、症状がタイムアウトに変わってしまうからです。
kubectl -n lab delete networkpolicy api-allow-front # 演習1の間だけ外す
kubectl -n lab patch svc api --type=json \
-p='[{"op":"replace","path":"/spec/ports/0/targetPort","value":8080}]'
kubectl -n lab-front run curl --image=curlimages/curl:8.10.1 --rm -i --restart=Never -- \
curl -sS -m 5 http://api.lab
curl: (7) Failed to connect to api.lab port 80 ... Connection refused のような表示になります。名前は引けていて(DNSの層はOK)、接続を試みた結果、相手から断られています。NetworkPolicyでタイムアウトしたときの 000 とは、見え方が違います。
kubectl -n lab get endpointslice -l kubernetes.io/service-name=api
# PORTS が 8080、ENDPOINTS には Pod の IP が2つ。転送先はあるのに、番号が違う
Serviceの層で、番号を間違えています。EndpointSliceは空ではないので、第3章の「selectorの不一致(転送先が0)」とは別の症状です。
実際の障害では、ポートの取り違えとPolicyが重なって、タイムアウトに見えることもあります。その場合も、EndpointSliceの PORTS とPolicyの port を突き合わせれば、原因にたどり着けます。
kubectl -n lab apply -f api.yaml # ファイルの定義(targetPort: http)に戻す
kubectl -n lab apply -f np-allow-front.yaml # 外したPolicyを戻す
lab-front から外へ出る通信(Egress)を、全部止めます。「呼び出し側からも、必要な相手にしか送らせない」つもりで、許可を書き忘れた状態です。
# np-front-egress.yaml ― lab-front の全Podの送信を止める(許可の欄なし)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: front-deny-egress
spec:
podSelector: {} # Namespace内の全Pod
policyTypes: [Egress]
kubectl -n lab-front apply -f np-front-egress.yaml
kubectl -n lab-front run curl --image=curlimages/curl:8.10.1 --rm -i --restart=Never -- \
curl -sS -m 5 http://api.lab
curl: (6) Could not resolve host: api.lab になります(CNIがNetworkPolicyに対応している場合)。HTTPの前に、DNSへの問い合わせ(UDP/TCPの53番)が止められています。送信を絞るときに、いちばん忘れやすいのがDNSです。
DNSと api への送信だけを許可するPolicyを足します。Policyは足し算なので、さっきのPolicyは残したままで構いません。
# np-front-egress.yaml に追記する2つ目のPolicy
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: front-allow-dns-and-api
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to: # クラスターのDNS(CoreDNS)へ
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
- to: # lab の api へ
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: lab
podSelector:
matchLabels:
app.kubernetes.io/name: api
ports:
- protocol: TCP
port: 80
kubectl -n lab-front apply -f np-front-egress.yaml
kubectl -n lab-front run curl --image=curlimages/curl:8.10.1 --rm -i --restart=Never -- \
curl -s -m 5 -o /dev/null -w '%{http_code}\n' http://api.lab # 200
DNSのPodに付いているラベル(k8s-app: kube-dns)は、kindやminikubeでの例です。自分の環境では kubectl -n kube-system get pod --show-labels で確かめてください。
本番の手順書からPVCの定義をコピーしてきた、という状況です。
# pvc-bad.yaml ― このクラスターにない StorageClass を指定
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: uploads
spec:
accessModes: [ReadWriteOnce]
storageClassName: fast-encrypted
resources:
requests:
storage: 1Gi
kubectl -n lab apply -f pvc-bad.yaml # 適用は成功する
kubectl -n lab get pvc uploads # STATUS Pending のまま
kubectl -n lab describe pvc uploads # Events を見る
Eventsに storageclass.storage.k8s.io "fast-encrypted" not found と出ます。API Serverは、参照先のStorageClassが存在するかを確かめずに受け付けます。第3章の演習2(存在しないConfigMap)と同じ形の失敗です。このPVCを使うPodを作ると、Podも Pending のまま置き場所が決まりません。
PVCの storageClassName は、作成後に書き換えられません。消してから作り直します。
kubectl -n lab delete pvc uploads
# pvc-bad.yaml の storageClassName の行を消す(既定を使う)か、get storageclass にある名前に直して
kubectl -n lab apply -f pvc-bad.yaml
kubectl -n lab get pvc uploads # kind では、使うPodがないので Pending(WaitForFirstConsumer の正常な待ち)
直したあとも Pending なのは、kindの WaitForFirstConsumer のためです。Eventsの文言が「not found」から「waiting for first consumer」に変わったことで、原因が消えたと判断できます。表示だけでなく、理由の文言まで読むのがポイントです。
第1章から使っている6項目です。今日は「最初に見たもの」に、図4-2のどの層かを添えてください。
# 演習メモの例
日時/Context : 2026-10-23 21:30 / kind-kind(Namespace: lab, lab-front)
症状 : lab-front から curl http://api.lab が Could not resolve host
最初に見たもの: エラーの種類(6 = 名前解決の失敗)→ ③DNSの層。get networkpolicy -n lab-front
原因 : front-deny-egress で送信を全部止め、DNS(53番)を許可していなかった
修正 : kube-dns への UDP/TCP 53 と、lab の api への TCP 80 を許可するPolicyを追加
再発防止 : Egressを絞るPolicyには、DNSの許可を必ずセットで書く。適用後に名前解決から確かめる
演習で見たとおり、エラーの見え方で、止まっている層の見当がつきます。図4-6の順に、送信元に近いほうから確かめます。
図4-6: 送信元に近い層から順に見る。エラーの種類が、止まっている層の手がかりになる。
大事なのは、実際の送信元と同じ条件で試すことです。デバッグ用のPodを別のNamespaceに作ると、NetworkPolicyの条件が変わり、「自分のPodからは繋がるのに」という食い違いが起きます。演習で lab-front と lab の両方から試したのは、このためです。
# ネットワークの確認セット
kubectl -n lab get svc,endpointslice
kubectl -n lab describe svc api
kubectl get networkpolicy -A
# ストレージの確認セット
kubectl -n lab get pvc
kubectl get pv
kubectl -n lab describe pvc notes
kubectl -n lab describe pod notes # Events にマウントの成否が出る
ストレージも、Bound を「アプリから使える」と読み替えません。Podのイベントでマウントの成功を確かめ、コンテナの中で書き込み、再起動後に読めるかまで試します。書けないときは、ファイルの権限(コンテナを動かすユーザー)、アクセスモード、NodeとディスクのZone、容量、CSIドライバーのイベントを順に見ます。
⚠️ デバッグ用のPodを作れない環境もあります。 本番では、イメージの取得元が制限されていたり、Podの作成権限がなかったりします。その場合は、既存の診断用ワークロード、Nodeの監視、クラウドのロードバランサーのログを使います。デバッグ用Podを許可する場合も、特権モードやホストネットワークを安易に付けず、終わったら消します。
ネットワークは、利用者 → 外部ロードバランサー → Ingress/Gateway → Service → Pod → DB という経路を図にし、矢印ごとに次の項目を書きます。ストレージは、アプリが書くデータ、キャッシュ、バックアップ、監査ログを分けて書きます。
| 対象 | 確かめること |
|---|---|
| 通信経路 | 誰が接続を始めるか、名前はどこで引くか、ポート、TLSをどこで終えるか、送信元IPを残すか |
| タイムアウトと再試行 | ロードバランサー・Gateway・アプリ・DBのタイムアウトが、利用者の待ち時間と矛盾しないか。再試行が相手の負荷を増やさないか |
| NetworkPolicy | CNIが対応しているか。DNS・監視・ログの許可があるか。段階的に入れる計画か |
| ボリューム | 容量だけでなく、IOPS・スループット・Zoneの制約・拡張の可否・RECLAIMPOLICY |
| バックアップ | RPO・RTO、保存先が別の障害範囲にあるか、別のNamespaceへの復元を試したか、復元後の接続先の切り替え手順 |
どの項目も、200 が返った、Bound になった、だけで完了にしないためのものです。
kubectl delete namespace lab lab-front
kubectl get namespaces # lab と lab-front が消えたことを確認
kubectl get pv # notes の PV も消えている(RECLAIMPOLICY が Delete のため)
Namespaceを消すと、中のPVCも消え、kindの standard では対応するPVと中身も消えます。PVは数秒遅れて消えることがあります。Retain のStorageClassを使った場合は、PVが Released で残るので、手で消してください。クラウドで同じことをすると、残ったディスクに料金がかかり続けます。今日は、ロードバランサーは作っていません。
api.lab。名前が引けても繋がらなければ、EndpointSliceとポートを見る🔖 明日10月24日(土)、最終章の第5章「本番運用」を公開します。 可用性、RBAC、イメージの管理、監視、オートスケール、コスト、アップグレード、障害対応、そしてマネージドKubernetesの選び方まで。本番に出す前のチェックリストも付けます。
この特集は10月24日まで、毎日1ページずつ増えていきます。明日が最終日です。新しい章へのリンクは、すべて特集トップに追加されます。章ごとにブックマークしなくても、特集トップを1つ保存しておけば迷いません。
毎日の公開は、X(@BusinessHub413)でもお知らせします。
※本章の記述は2026年10月時点の公式ドキュメントと公式ブログに基づきます。イベントの文言、kubectl の出力形式、StorageClassの名前、NetworkPolicyへの対応は、Kubernetesのバージョンや学習環境(kind・minikube・Docker Desktop)によって異なることがあります。本章のコマンドは手順として整理したもので、すべての環境での動作を確認したものではありません。
「うちはKubernetesが要るのか」「ECSやCloud Runで足りるのか」から、
クラスター設計・運用体制づくりまで、30分の無料相談で承ります。