本記事には広告(アフィリエイトリンク)が含まれます。

fanclub-api全機構連携 総合演習【CKAD第19回完走】

広告
広告

第19回スコープ・第1巻完走の位置づけ・今ここマップ

動作確認バージョン: K8s v1.35 / kubectl v1.35.0 / Helm v4.1.4 / Gateway API v1.4.0 / Traefik chart 39.0.9(Traefik v3.6.15)/ cert-manager v1.20.0 / kind v0.31.0 / kindest/node:v1.35.0 / AlmaLinux 10.1(kernel 6.12.0-124.55.3.el10_1)(2026-05-16 時点・k8s-ops 実機検証)

本回は Kubernetes 実践教科書 第1巻(CKAD 対応・全 19 回)の第19回、つまり第1巻の最終回・完走回です。第6部「パッケージ管理 + HTTPS 公開」の第3回(3 回中の 3 回目)にあたります。

第3回で fanclub-api の Backend イメージを Dockerfile からビルドしてから、第18回で https://fanclub.local として HTTPS 公開するところまで、19 回かけて積み上げてきました。

本回では新しい技術を 1 つも導入しません。代わりに、ep1 から ep18 までで学んだ 14 個の機構を、1 回の通し演習で連携させる総ざらいを行います。これが第1巻の総決算であり、CKAD 受験準備が整ったことを自分で確認するための回です。

第18回までの 18 回は、機構を 1 つずつ順番に学ぶ「縦割り」の構成でした。Pod を学ぶ回、Service を学ぶ回、NetworkPolicy を学ぶ回、というように、1 回で 1〜数個の機構を扱ってきました。

本回は逆に、その 14 機構を「基盤レイヤ → アプリレイヤ → 公開レイヤ」という 3 つのフェーズに横断的に並べ直し、依存関係に沿った順序で 1 つのアプリケーションを組み立てます。

CKAD 試験の本番も、複数のドメインの知識を 1 つの設問の中で組み合わせて解く形式です。本回の総合演習は、その本番の感覚をつかむためのシミュレーションでもあります。

第18回からの継承状態確認

項目状態出典
kind クラスタkind-control-plane Ready(v1.35.0)。port mapping は 6443/tcp(API Server)のみep18 完了状態
Namespace 一覧cert-manager / default / kube-node-lease / kube-public / kube-system / local-path-storage / traefik / fanclub(合計 8 個)ep18 完了状態
Gateway API CRDv1.4.0 導入済(GatewayClass / Gateway / HTTPRoute / ReferenceGrant)ep18 完了状態
cert-managerv1.20.0 稼働(cert-manager ns・3 Pod Running)ep18 完了状態
Traefikchart v39.0.9 稼働(traefik ns・GatewayClass traefik ACCEPTED)ep18 完了状態
fanclub nsfanclub-api(Helm Release)+ Gateway + HTTPRoute + Certificate fanclub-tls・HTTPS 公開継続中ep18 完了状態
Helmv4.1.4 導入済(/usr/local/bin/helmep17 完了状態
手元の素材~/fanclub-charts/fanclub-api/(Helm Chart)・~/fanclub-tls/(ClusterIssuer / Certificate / Gateway / HTTPRoute の YAML)ep17・ep18 完了状態

今ここマップ(第1巻 19 回中の現在位置):

第1部 コンテナとDocker
    第1回〜第4回  [完了]

第2部 Kubernetes基礎
    第5回〜第6回  [完了]

第3部 アプリリソース
    第7回〜第11回  [完了]

第4部 ワークロード戦略
    第12回〜第14回  [完了]

第5部 セキュリティ基礎
    第15回〜第16回  [完了]

第6部 パッケージ管理 + HTTPS公開(第17〜19回)
    第17回 Helm v4 基礎 + fanclub-api Helm Chart 作成  [完了]
    第18回 Gateway API + Traefik + cert-manager で HTTPS 公開  [完了]
  ★ 第19回 fanclub-api 全機構連携 総合演習  ← 今ここ(第1巻完走)

完走後: CKAD受験準備完了 → 第2巻CKA → 第3巻CKS → KCNA + KCSA → Kubestronaut 5冠

第19回の学習目標は次の 5 点です。

  • 第1巻の 19 回で習得した 14 機構(Namespace / ResourceQuota / LimitRange、StatefulSet / PVC、Job、Deployment / 3 Probe、Service、ConfigMap / Secret、ServiceAccount / RBAC、SecurityContext、DaemonSet、NetworkPolicy、Helm、Gateway API / cert-manager)の役割と相互関係を体系的に説明できる
  • 14 機構を「基盤レイヤ → アプリレイヤ → 公開レイヤ」の 3 フェーズで順序立てて構築し、依存関係を踏まえた構築順を判断できる
  • 全機構を 1 回の通し演習で連携させ、https://fanclub.local で HTTPS 公開された fanclub-api にアクセスできる状態を達成できる
  • CKAD 試験本番の形式(120 分・Performance-based・15〜20 問・合格ライン 66%)を理解し、本番に向けた最終チェックリストで自己点検できる
  • 第1巻完走により CKAD 受験準備が整ったことを確認し、第2巻(CKA)・第3巻(CKS)へ続く Kubestronaut 5 冠までの道筋を描ける

本回の進め方:本回の演習は、ep18 までで稼働している fanclub ns の構成とは別に、新しい Namespace fanclub-prod を作って、そこに 14 機構を一から組み立てます。ep18 までの fanclub ns・cert-manager ns・traefik ns には触れません。

理由は H2「総合演習の進め方」で説明しますが、ep18 までの HTTPS 公開構成を壊さずに、もう 1 つ別の通し演習を独立して実施するためです。

Gateway API CRD・cert-manager・Traefik はクラスタ全体に関わるコンポーネントで、ep18 で導入済のものを再利用します。読者がクリーンな kind クラスタで 120 分チャレンジをやりたい場合の手順も、同じ H2 で案内します。

19 回で習得した全機構の俯瞰 — fanclub-api をどう作ってきたか

総合演習に入る前に、第1巻の 19 回で fanclub-api をどう作り上げてきたかを振り返ります。fanclub-api は、ファンクラブの会員管理を行う Web アプリケーションという設定の模擬アプリです。

Java(JDK 25 LTS)+ Payara Micro で書かれた Backend、Nginx + 静的 HTML の Frontend、PostgreSQL 18 のデータベース、という 3 層構成です。

この 3 層を、第3回の「ただのコンテナイメージ」から、第18回の「HTTPS 公開された本番相当のサービス」まで、19 回かけて段階的に作り上げてきました。

各回で fanclub-api に加えてきた機構

第3回:  [Backend(Java)]              Dockerfile を書いた(v0.1.0)
第4回:  [Backend] → k8s-registry    レジストリに push した
第7回:  [Backend(Pod)]              K8s に初めて載せた・JVM × K8s メモリ整合
第8回:  [Frontend] → [Backend]      Service 通信(ClusterIP・DNS 名前解決)
第9回:  [FE] → [BE] → [DB]          3 層完成・StatefulSet + PVC で DB 永続化
第10回: + ConfigMap/Secret/SA       DB 接続情報を外部化
第11回: + DBマイグレーションJob      バッチ処理(Job/CronJob/DaemonSet)
第12回: Backend Deployment 化       自己回復・3 Probe(startup/liveness/readiness)
第13回: Blue/Green + Canary         複数のデプロイ戦略を体験
第14回: + Namespace/Quota/LimitRange リソース制限・マルチテナント分離
第15回: + RBAC + SecurityContext     セキュアになった(最小権限・非root)
第16回: + NetworkPolicy             通信制御された(default-deny + 段階許可)
第17回: Helm Chart 化               helm install 一発で管理できるようになった
第18回: + Gateway API + cert-manager HTTPS 公開(https://fanclub.local)
第19回: 全 14 機構の通し演習         ★ 第1巻完走

この一覧を見ると、fanclub-api の構築は 3 つの段階に分かれていたことが分かります。第3回〜第8回が「コンテナを K8s に載せて通信させる」段階、第9回〜第13回が「データを永続化し、設定を外出しし、安定して動かす」段階、第14回〜第18回が「リソース制限・セキュリティ・パッケージ化・公開」という運用品質を高める段階です。

本回の総合演習は、この 3 段階を「基盤レイヤ → アプリレイヤ → 公開レイヤ」という 3 フェーズに整理し直して、1 回で組み立て直します。

14 機構と CKAD 5 ドメインの対応

CKAD(Certified Kubernetes Application Developer)試験は、5 つのドメイン(出題範囲のグループ)で構成されています。第1巻の 19 回は、この 5 ドメインを完全網羅するように設計されています。14 機構それぞれが、どのドメインに対応し、どの回で学んだかを整理します。

機構役割CKAD ドメイン習得回
Namespace / ResourceQuota / LimitRangeワークロードの分離単位とリソース上限・デフォルト値D4第14回
StatefulSet / PVC安定した識別子と永続ストレージを持つワークロードD1 / D3第9回
Job一度きりのバッチ処理(DB マイグレーション等)D1第11回
Deployment + 3 Probe宣言的な Pod 管理・自己回復・ヘルスチェックD2 / D3第12回
Service(ClusterIP)Pod 群への安定したアクセス先(仮想 IP + DNS 名)D5第8回
ConfigMap / Secret設定値と機密情報の外部化D4第10回
ServiceAccount / RBACPod に紐づく認証情報と最小権限の認可D4第10回 / 第15回
SecurityContextコンテナの実行権限の制限(非 root・capability drop 等)D4第15回
DaemonSet全 Node に 1 Pod ずつ配置(ログ・監視エージェント)D1第11回
NetworkPolicyPod 間の通信を Ingress / Egress ルールで制御D5第16回
Helm複数マニフェストをパッケージ化して一括管理D2第17回
Gateway API / cert-manager外部 HTTPS 公開と TLS 証明書の自動発行D5第18回

この表を見ると、14 機構が CKAD の 5 ドメインすべてに分散していることが分かります。

D1(Application Design and Build)は StatefulSet・Job・DaemonSet、D2(Application Deployment)は Deployment・Helm、D3(Observability and Maintenance)は Probe・StatefulSet、D4(Environment, Configuration and Security)は Namespace 系・ConfigMap / Secret・RBAC・SecurityContext、D5(Services and Networking)は Service・NetworkPolicy・Gateway API です。

本回の総合演習で 14 機構を 1 回で組み立てるということは、CKAD の 5 ドメインを 1 回の演習で横断的に復習することと同じ意味になります。

3 レイヤアーキテクチャの全体像

本回の総合演習では、14 機構を 3 つのレイヤに分けて構築します。基盤レイヤ(Namespace の器とリソース制限・権限)、アプリレイヤ(DB とアプリ本体と設定)、公開レイヤ(通信制御とパッケージ化と外部公開)です。レイヤ同士の依存関係を図にすると、次のようになります。

fanclub-api 全 14 機構の連携アーキテクチャ図。クライアントから HTTPS で公開レイヤ(Gateway / HTTPRoute / cert-manager / Traefik)へ入り、ルーティングでアプリレイヤ(Deployment / Service / StatefulSet / Job / ConfigMap / Secret / DaemonSet)へ、さらに依存で基盤レイヤ(Namespace / ResourceQuota / LimitRange / ServiceAccount / RBAC / SecurityContext / NetworkPolicy)へとつながる 3 レイヤ構造。構築順は基盤から公開へ、削除は逆順であることを示している。

下のレイヤから順に作る、というのが本回の演習の流れです。基盤レイヤがないとアプリレイヤの Pod を置く場所(Namespace)がありません。アプリレイヤが動いていないと、公開レイヤで外部公開しても中身が空のサービスになります。この「下から積む」順序が、なぜ重要なのかは次の H2 で説明します。

総合演習の進め方 — 実技チャレンジ形式と 3 フェーズ

本回の演習は、これまでの回とは少し性格が違います。新しい知識を学ぶ演習ではなく、すでに学んだ知識を「制限時間内に組み立てられるか」を試す実技チャレンジです。CKAD 試験本番の感覚に近づけるため、目標時間を 120 分に設定します。これは CKAD 試験の制限時間(2 時間)と同じです。

実技チャレンジ形式の狙い

CKAD 試験は、知識を問う多肢選択式ではなく、実機を操作して課題を解く Performance-based(実技)形式です。120 分で 15〜20 問を解く必要があり、1 問あたりの平均は 6〜8 分です。

マニフェストを書くスピード、kubectl コマンドの正確さ、リソース同士の依存関係を素早く判断する力が問われます。本回の総合演習で 14 機構を 120 分で組み立てる体験は、この本番のペース感覚をつかむための練習です。

1 回目で 120 分を超えても問題ありません。どこに時間がかかったかを記録し、2 回目・3 回目で短縮していくのが学習の進め方です。

3 フェーズの構成

14 機構を、依存関係に沿って 3 つのフェーズに分けます。各フェーズの所要時間の目安と、扱う機構は次のとおりです。

フェーズレイヤ扱う機構目安時間
フェーズ 1基盤レイヤNamespace / ResourceQuota / LimitRange / ServiceAccount / Role / RoleBinding約 25 分
フェーズ 2アプリレイヤConfigMap / Secret / StatefulSet / PVC / Headless Service / Job / Deployment / 3 Probe / SecurityContext / ClusterIP Service / DaemonSet約 55 分
フェーズ 3公開レイヤNetworkPolicy / Helm Chart 統合 / Gateway / HTTPRoute / Certificate / HTTPS 動作確認約 40 分

各フェーズの終わりには「フェーズ完了確認」のチェックポイントを置きます。フェーズ 1 が終わったら基盤が整ったか、フェーズ 2 が終わったら全 Pod が Running か、フェーズ 3 が終わったら HTTPS でアクセスできるか、を確認してから次のフェーズに進みます。フェーズの途中で詰まったときは、そのフェーズの確認コマンドで状態を見て原因を特定する、という進め方になります。

構築順序の原則 — 依存される側から先に作る

3 フェーズを「基盤 → アプリ → 公開」の順で進めるのには、明確な理由があります。依存される側から先に作る、という原則です。あるリソース A が別のリソース B を参照する(B に依存する)とき、B が先に存在していないと A は正しく動きません。fanclub-api の構成では、次のような依存の連鎖があります。

  • Pod は Namespace の中にしか作れない。だから Namespace が先
  • Deployment の Pod は ServiceAccount を参照する。だから ServiceAccount が先
  • Backend Pod は ConfigMap / Secret を環境変数として読む。だから ConfigMap / Secret が先
  • Backend は DB(StatefulSet の Service)に接続する。だから DB が先
  • HTTPRoute は Backend の Service を backendRefs で参照する。だから Service が先
  • Gateway は HTTPS リスナーで TLS 証明書(Secret)を使う。だから Certificate が先

この連鎖をたどると、Namespace → ResourceQuota / LimitRange → ServiceAccount / Role → ConfigMap / Secret → DB(StatefulSet)→ Backend(Deployment)→ Service → NetworkPolicy → Gateway / HTTPRoute、という順序が自然に決まります。これがフェーズ 1 → 2 → 3 の構築順です。

順序を間違えると、たとえば Backend を先に作って ConfigMap を後から作った場合、Backend Pod は環境変数を読めずに CreateContainerConfigError で起動失敗します。後から ConfigMap を作っても、Pod を再作成しないと反映されません。

「依存される側から作る」を守れば、こうした手戻りを避けられます。

運用知識への接続 — 同じ順序原則が障害復旧でも効く:「依存される側から作る」という順序原則は、本回の総合演習だけのテクニックではありません。本番運用でクラスタやアプリを別環境にリストア(復旧)するときも、まったく同じ順序が使えます。

バックアップから復元するときは、Namespace → 設定(ConfigMap / Secret)→ データ(PVC / StatefulSet)→ アプリ(Deployment)→ 公開(Gateway)の順に戻すと、各リソースが参照先を見つけられて素直に復旧します。

逆に、リソースを削除して片付けるときは逆順です。公開レイヤ(Gateway / HTTPRoute)→ アプリレイヤ(Deployment / Service)→ 基盤レイヤ(Namespace)の順で壊すと、まだ誰かに参照されているリソースを先に消してエラーになる事態を避けられ、最後に Namespace を削除すれば中身ごとまとめて片付きます。

「作るときは下から、壊すときは上から」と覚えておくと、総合演習で身につけた順序感覚が、そのまま運用の障害復旧やリソース整理に活きます。

kind の 2 vCPU 制約に合わせた最小構成

本シリーズの kind クラスタは、k8s-ops VM 上の Docker で動く学習用のシングルノードクラスタです。割り当てている CPU は 2 vCPU です。

本回の総合演習では、14 機構を 1 つの Namespace に展開します。DB(StatefulSet)+ Backend(Deployment)+ DaemonSet + Job が同時に稼働するため、各ワークロードを最小構成にしないと、CPU の合計要求がノードの割り当て可能量を超えて Pod が Pending のままになります。

本回は次の方針でリソースを抑えます。

  • Backend Deployment は replicas: 1(ep12 では複数 replica を扱ったが、本回は 1 に絞る)
  • Backend の CPU 要求は requests.cpu: 100m、上限は limits.cpu: 500m に抑える
  • DB(PostgreSQL StatefulSet)も replicas: 1requests.cpu: 100m
  • DaemonSet はシングルノードなので 1 Pod のみ。requests.cpu: 50m と軽くする
  • Job は短時間で完了し、完了後は ttlSecondsAfterFinished で自動削除されるため、CPU 圧迫は一時的

このリソース設計は、後のフェーズ 1 で作る LimitRange のデフォルト値とも整合させます。LimitRange でコンテナのデフォルト requests.cpu100m に設定しておけば、resources を書き忘れたコンテナにもこの値が自動で入り、Quota の枠内に収まります。

本回の演習環境 — fanclub-prod Namespace で独立して組む

H2-1 でも触れたとおり、本回の演習は ep18 までで稼働している fanclub ns には触れず、新しい fanclub-prod Namespace を作ってそこに 14 機構を組み立てます。

Gateway API CRD・cert-manager・Traefik はクラスタ全体のコンポーネントなので、ep18 で導入済のものを再利用します。これにより、ep18 の HTTPS 公開構成を保ったまま、もう 1 つ独立した総合演習を実施できます。

読者がもっと本番に近い形でチャレンジしたい場合は、kind クラスタを一度削除して作り直すこともできます。

kind delete cluster でクラスタを消し、kind create cluster --image docker.io/kindest/node:v1.35.0 で作り直してから、Gateway API CRD の導入(ep18 演習①)→ cert-manager の helm install(ep18 演習①)→ Traefik の helm install(ep18 演習②)を済ませると、本回の 3 フェーズをまっさらな状態から始められます。

120 分チャレンジを本番に近づけたい場合はこの方法が向いていますが、クラスタ前提コンポーネント(CRD・cert-manager・Traefik)の導入時間も加わるため、合計時間は 120 分より長くなります。本回の手順本文は、これらのクラスタ前提コンポーネントが導入済であることを前提に、フェーズ 1 から書きます。

やってみよう①: フェーズ 1 — 基盤レイヤ構築

フェーズ 1 では、fanclub-api を載せる「器」を作ります。Namespace を作り、その Namespace に対してリソースの上限(ResourceQuota)とデフォルト値(LimitRange)を設定し、アプリが使う ServiceAccount と最小権限の Role / RoleBinding を用意します。

この段階ではまだアプリの Pod は 1 つも作りません。アプリを安全に載せるための土台を整える段階です。目安時間は約 25 分です。

Step 1: 作業ディレクトリと Namespace を作る(ep14 で学んだ)

本回の演習で作る YAML をまとめて置く作業ディレクトリを ~/fanclub-final/ として用意し、フェーズごとにサブディレクトリを切ります。実行コマンド:

$ mkdir -p ~/fanclub-final/phase1 ~/fanclub-final/phase2 ~/fanclub-final/phase3
$ cd ~/fanclub-final

Namespace は、第14回で学んだ Kubernetes のマルチテナント分離単位です。fanclub-prod という名前で作ります。

Namespace の作成だけなら kubectl create namespace でもできますが、本回は ResourceQuota や LimitRange と一緒に YAML で管理するため、マニフェストで作ります。~/fanclub-final/phase1/01-namespace.yaml を次の内容で作成します。

apiVersion: v1
kind: Namespace
metadata:
  name: fanclub-prod
  labels:
    app.kubernetes.io/part-of: fanclub-api
    environment: production

適用します。実行コマンド:

$ kubectl apply -f phase1/01-namespace.yaml

期待される実行結果:

namespace/fanclub-prod created

これ以降のコマンドで毎回 -n fanclub-prod を付けるのは手間なので、第6回・第14回で学んだ kubectl config set-context でカレント Namespace を fanclub-prod に切り替えておきます。実行コマンド:

$ kubectl config set-context --current --namespace=fanclub-prod

期待される実行結果:

Context "kind-kind" modified.

これでカレント Namespace が fanclub-prod になり、以降の kubectl コマンドは -n なしで fanclub-prod を対象にします。第6回で alias k=kubectl も設定済のはずなので、以降は k でも kubectl でも構いません。本文では kubectl で統一します。

Step 2: ResourceQuota を適用する(ep14 で学んだ)

ResourceQuota は、第14回で学んだ「Namespace 全体のリソース上限」を定めるリソースです。fanclub-prod Namespace の中で使える CPU・Memory の合計、Pod の数、PVC の数に上限を設定します。

kind の 2 vCPU 制約に合わせ、控えめな上限にします。~/fanclub-final/phase1/02-resourcequota.yaml を次の内容で作成します。

apiVersion: v1
kind: ResourceQuota
metadata:
  name: fanclub-prod-quota
  namespace: fanclub-prod
spec:
  hard:
    requests.cpu: "1"
    requests.memory: 2Gi
    limits.cpu: "2"
    limits.memory: 4Gi
    pods: "15"
    persistentvolumeclaims: "5"

各項目の意味です。requests.cpu: "1" は、Namespace 内の全コンテナの CPU 要求の合計が 1 コア(1000m)を超えないという制限です。

limits.cpu: "2" は CPU 上限の合計が 2 コアまで。pods: "15" は同時に存在できる Pod が 15 個まで。persistentvolumeclaims: "5" は PVC が 5 個まで。

本回の演習で使う Pod は Backend 1 + DB 1 + DaemonSet 1 + Job 1 程度なので、15 個の枠で十分です。

適用します。実行コマンド:

$ kubectl apply -f phase1/02-resourcequota.yaml

期待される実行結果:

resourcequota/fanclub-prod-quota created

Step 3: LimitRange を適用する(ep14 で学んだ)

ResourceQuota を適用した Namespace には、第14回で学んだ重要な落とし穴があります。ResourceQuota が requests.cpu を制限している Namespace では、resources.requests を書いていないコンテナを含む Pod は作成を拒否されるのです。「合計を制限したいのに、個々の Pod が要求量を申告しないと合計が計算できない」ためです。

これを避けるには 2 つの方法があります。1 つはすべてのコンテナに resources を明示すること。もう 1 つは LimitRange でデフォルト値を設定し、書き忘れたコンテナにも自動で値が入るようにすることです。

本回は両方を併用します。~/fanclub-final/phase1/03-limitrange.yaml を次の内容で作成します。

apiVersion: v1
kind: LimitRange
metadata:
  name: fanclub-prod-limits
  namespace: fanclub-prod
spec:
  limits:
  - type: Container
    default:
      cpu: 300m
      memory: 512Mi
    defaultRequest:
      cpu: 100m
      memory: 256Mi
    max:
      cpu: "1"
      memory: 1Gi
    min:
      cpu: 50m
      memory: 64Mi

各フィールドの意味です。default はコンテナに limits を書かなかったときに自動で入る上限値。defaultRequestrequests を書かなかったときに自動で入る要求値。max はコンテナに設定できる上限の天井(これを超える limits を書くと拒否される)。min は下限の床です。

type: Container はコンテナ単位の制限であることを示します。これで、resources を書き忘れたコンテナにも requests.cpu: 100m / limits.cpu: 300m が自動で入り、ResourceQuota の枠内に収まります。

適用します。実行コマンド:

$ kubectl apply -f phase1/03-limitrange.yaml

期待される実行結果:

limitrange/fanclub-prod-limits created

Step 4: ServiceAccount を作る(ep10 で学んだ)

ServiceAccount は、第10回で学んだ「Pod に紐づく Kubernetes 内の認証情報」です。Backend Pod に専用の ServiceAccount を割り当てると、その Pod が API Server に対してどんな権限を持つかを、後続の Role / RoleBinding で細かく制御できます。

専用 ServiceAccount を使わず default のままにすると、権限管理の単位が Namespace 全体の default と一緒くたになってしまいます。~/fanclub-final/phase1/04-serviceaccount.yaml を次の内容で作成します。

apiVersion: v1
kind: ServiceAccount
metadata:
  name: fanclub-backend-sa
  namespace: fanclub-prod
automountServiceAccountToken: false

automountServiceAccountToken: false は、第10回で学んだ設定です。ServiceAccount のトークンを Pod に自動マウントしない、という指定で、Pod が API Server を操作する必要がない場合はトークンを配らないのが安全側の設計です。

本回の Backend は API Server を直接叩く処理を持たないため、ここでは false にしておきます。

なお、後続の Step で Role / RoleBinding を作りますが、これは「もし権限が必要になったときに最小権限で設計する」という設計の型を総ざらいで通すためのもので、トークンを自動マウントしない設定とは独立した話です。

適用します。実行コマンド:

$ kubectl apply -f phase1/04-serviceaccount.yaml

期待される実行結果:

serviceaccount/fanclub-backend-sa created

Step 5: Role と RoleBinding を作る(ep15 で学んだ)

RBAC(Role-Based Access Control)は、第15回で学んだ「誰が・何に・何をできるか」を制御する仕組みです。Role は Namespace スコープの権限の集合、RoleBinding は Role を ServiceAccount などの主体に紐付けるリソースです。

本回は、最小権限の原則(PoLP・Principle of Least Privilege)の型を通すために、pod-reader という「同じ Namespace の Pod を読み取れるだけ」の Role を作り、それを fanclub-backend-sa に紐付けます。~/fanclub-final/phase1/05-rbac.yaml を次の内容で作成します。

apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: pod-reader
  namespace: fanclub-prod
rules:
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: fanclub-backend-pod-reader
  namespace: fanclub-prod
subjects:
- kind: ServiceAccount
  name: fanclub-backend-sa
  namespace: fanclub-prod
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io

Role の rules は、apiGroups: [""](コアグループ)の pods リソースに対して get / list / watch の 3 動詞だけを許可しています。Pod の作成・削除はできません。

RoleBinding の subjectsfanclub-backend-sa を指定し、roleRefpod-reader Role を指しています。これで fanclub-backend-sa は「fanclub-prod Namespace の Pod を読めるだけ」の権限を持ちます。

適用します。実行コマンド:

$ kubectl apply -f phase1/05-rbac.yaml

期待される実行結果:

role.rbac.authorization.k8s.io/pod-reader created
rolebinding.rbac.authorization.k8s.io/fanclub-backend-pod-reader created

権限が意図どおり付いたかは、第15回で学んだ kubectl auth can-i で確認できます。実行コマンド:

$ kubectl auth can-i list pods \
    --as=system:serviceaccount:fanclub-prod:fanclub-backend-sa \
    -n fanclub-prod
$ kubectl auth can-i delete pods \
    --as=system:serviceaccount:fanclub-prod:fanclub-backend-sa \
    -n fanclub-prod

期待される実行結果:

yes
no

1 行目の list podsyes、2 行目の delete podsno です。pod-reader Role が get / list / watch だけを許可し delete を許可していないことが、これで確認できました。最小権限が効いています。

Step 6: フェーズ 1 完了確認

フェーズ 1 で作ったリソースを、kubectl describe namespace でまとめて確認します。describe namespace は、Namespace に紐づく ResourceQuota と LimitRange の内容も一緒に表示してくれる便利なコマンドです。実行コマンド:

$ kubectl describe namespace fanclub-prod

期待される実行結果:

Name:         fanclub-prod
Labels:       app.kubernetes.io/part-of=fanclub-api
              environment=production
Status:       Active

Resource Quotas
  Name:                   fanclub-prod-quota
  Resource                Used  Hard
  --------                ---   ---
  limits.cpu              0     2
  limits.memory           0     4Gi
  persistentvolumeclaims  0     5
  pods                    0     15
  requests.cpu            0     1
  requests.memory         0     2Gi

Resource Limits
  Type       Resource  Min   Max  Default Request  Default Limit
  ----       --------  ---   ---  ---------------  -------------
  Container  cpu       50m   1    100m             300m
  Container  memory    64Mi  1Gi  256Mi            512Mi

ResourceQuota の Used 列がすべて 0 なのは、まだ Pod を 1 つも作っていないからです。フェーズ 2 でアプリを作り始めると、この Used 列が増えていきます。ServiceAccount と RBAC も確認します。実行コマンド:

$ kubectl get serviceaccount,role,rolebinding -n fanclub-prod

期待される実行結果:

NAME                                SECRETS   AGE
serviceaccount/default               0         3m
serviceaccount/fanclub-backend-sa    0         2m

NAME                                        CREATED AT
role.rbac.authorization.k8s.io/pod-reader   2026-05-16T11:02:14Z

NAME                                                              ROLE             AGE
rolebinding.rbac.authorization.k8s.io/fanclub-backend-pod-reader  Role/pod-reader  2m

Namespace に標準で存在する default ServiceAccount に加えて、本回作った fanclub-backend-sa が表示されています。Role pod-reader と RoleBinding fanclub-backend-pod-reader も揃いました。これでフェーズ 1(基盤レイヤ)は完了です。アプリを安全に載せるための器が整いました。

フェーズ 1 チェックポイント:次のフェーズに進む前に、3 点を確認してください。

(1) kubectl get namespace fanclub-prodStatus: Active になっている。(2) kubectl describe namespace fanclub-prod の出力に Resource Quotas と Resource Limits の両方が表示される。(3) kubectl auth can-i list pods --as=system:serviceaccount:fanclub-prod:fanclub-backend-sayes を返す。

この 3 点が揃っていれば、フェーズ 2 へ進めます。フェーズ 1 の目安時間は約 25 分でした。

やってみよう②: フェーズ 2 — アプリレイヤ構築

フェーズ 2 では、フェーズ 1 で作った器(fanclub-prod Namespace)の中に、fanclub-api の本体を組み立てます。

設定値(ConfigMap / Secret)を用意し、データベース(PostgreSQL StatefulSet)を永続ストレージ付きで起動し、DB マイグレーション Job でテーブルを作り、Backend を Deployment として 3 Probe + SecurityContext 付きでデプロイし、Backend に ClusterIP Service を付け、ログコレクター模擬の DaemonSet を配置します。

フェーズ 2 で扱う機構が一番多く、目安時間は約 55 分です。

Step 1: ConfigMap と Secret を作る(ep10 で学んだ)

第10回で学んだとおり、ConfigMap は非機密の設定値、Secret は機密情報を Pod の外に出して管理するリソースです。fanclub-api の Backend は、DB 接続情報(ホスト名・DB 名・ユーザー・パスワード)と JVM オプションを環境変数で受け取ります。

このうちパスワードだけが Secret、それ以外が ConfigMap です。~/fanclub-final/phase2/01-config.yaml を次の内容で作成します。

apiVersion: v1
kind: ConfigMap
metadata:
  name: fanclub-config
  namespace: fanclub-prod
data:
  DB_HOST: "fanclub-db.fanclub-prod.svc.cluster.local"
  DB_PORT: "5432"
  DB_NAME: "fanclubdb"
  DB_USER: "fanclub"
  JAVA_OPTS: "-XX:MaxRAMPercentage=75.0"
---
apiVersion: v1
kind: Secret
metadata:
  name: fanclub-secret
  namespace: fanclub-prod
type: Opaque
stringData:
  DB_PASSWORD: "fanclub-prod-pass-2026"
  POSTGRES_PASSWORD: "fanclub-prod-pass-2026"

DB_HOST には、後で作る DB の Headless Service の完全修飾ドメイン名(FQDN)fanclub-db.fanclub-prod.svc.cluster.local を指定しています。第8回で学んだ <service>.<namespace>.svc.cluster.local の形式です。

Secret は stringData で書くと、Kubernetes が自動で base64 エンコードしてくれます(data フィールドだと自分でエンコードする必要があります)。

第10回で学んだとおり、Secret は base64 エンコードされるだけで暗号化ではない点に注意します。本番では SealedSecrets や External Secrets Operator を使いますが、それは第3巻のテーマです。

適用します。実行コマンド:

$ kubectl apply -f phase2/01-config.yaml

期待される実行結果:

configmap/fanclub-config created
secret/fanclub-secret created

Step 2: PostgreSQL StatefulSet + PVC + Headless Service を作る(ep9 で学んだ)

第9回で学んだ StatefulSet は、Pod に安定した識別子と安定した永続ストレージを与えるワークロードです。データベースのように「同じ Pod が同じデータを持ち続ける」必要があるものは、Deployment ではなく StatefulSet を使います。

StatefulSet には volumeClaimTemplates を書くことで、Pod ごとに PVC を自動生成できます。また StatefulSet には、Pod に安定した DNS 名を与える Headless Service(clusterIP: None の Service)が必要です。

~/fanclub-final/phase2/02-database.yaml を次の内容で作成します。

apiVersion: v1
kind: Service
metadata:
  name: fanclub-db
  namespace: fanclub-prod
  labels:
    app: fanclub-db
spec:
  clusterIP: None
  selector:
    app: fanclub-db
  ports:
  - name: postgres
    port: 5432
    targetPort: 5432
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: fanclub-db
  namespace: fanclub-prod
spec:
  serviceName: fanclub-db
  replicas: 1
  selector:
    matchLabels:
      app: fanclub-db
  template:
    metadata:
      labels:
        app: fanclub-db
    spec:
      containers:
      - name: postgres
        image: postgres:18
        ports:
        - containerPort: 5432
          name: postgres
        env:
        - name: POSTGRES_DB
          valueFrom:
            configMapKeyRef:
              name: fanclub-config
              key: DB_NAME
        - name: POSTGRES_USER
          valueFrom:
            configMapKeyRef:
              name: fanclub-config
              key: DB_USER
        - name: POSTGRES_PASSWORD
          valueFrom:
            secretKeyRef:
              name: fanclub-secret
              key: POSTGRES_PASSWORD
        - name: PGDATA
          value: /var/lib/postgresql/data/pgdata
        resources:
          requests:
            cpu: 100m
            memory: 256Mi
          limits:
            cpu: 500m
            memory: 512Mi
        volumeMounts:
        - name: db-data
          mountPath: /var/lib/postgresql/data
        readinessProbe:
          exec:
            command: ["pg_isready", "-U", "fanclub", "-d", "fanclubdb"]
          initialDelaySeconds: 10
          periodSeconds: 5
  volumeClaimTemplates:
  - metadata:
      name: db-data
    spec:
      accessModes: ["ReadWriteOnce"]
      storageClassName: standard
      resources:
        requests:
          storage: 1Gi

ポイントを整理します。Service の clusterIP: None が Headless Service の指定です。StatefulSet の serviceName: fanclub-db はこの Headless Service を指します。

volumeClaimTemplates は、StatefulSet が Pod を作るたびに db-data-fanclub-db-0 という名前で PVC を自動生成します。storageClassName: standard は、kind が標準で持つ動的プロビジョニング対応の StorageClass です。

環境変数の POSTGRES_DB / POSTGRES_USER は Step 1 の ConfigMap から、POSTGRES_PASSWORD は Secret から valueFrom で参照しています。readinessProbepg_isready コマンドを使い、PostgreSQL が接続を受け付けられる状態になってから Service のエンドポイントに登録されるようにしています。

適用します。実行コマンド:

$ kubectl apply -f phase2/02-database.yaml

期待される実行結果:

service/fanclub-db created
statefulset.apps/fanclub-db created

DB Pod が Running になり、PVC が Bound になるまで待ちます。実行コマンド:

$ kubectl get pod,pvc -l app=fanclub-db -n fanclub-prod

期待される実行結果:

NAME               READY   STATUS    RESTARTS   AGE
pod/fanclub-db-0    1/1     Running   0          45s

NAME                                       STATUS   VOLUME       CAPACITY   ACCESS MODES   AGE
persistentvolumeclaim/db-data-fanclub-db-0  Bound    pvc-a1b2...  1Gi        RWO            45s

Pod 名が fanclub-db-0 という連番付きの安定した名前であること、PVC が db-data-fanclub-db-0 という volumeClaimTemplates 由来の名前であること、PVC の STATUS が Bound であることを確認します。

第9回で学んだとおり、kind の standard StorageClass は WaitForFirstConsumer モードで、PVC は Pod がスケジュールされて初めて Bound になります。Pod が起動していれば Bound になっているはずです。

Step 3: DB マイグレーション Job を実行する(ep11 で学んだ)

第11回で学んだ Job は、一度きりのバッチ処理を実行するワークロードです。DB が起動したら、アプリが使うテーブルを作る DDL(データ定義言語)を流す必要があります。これを Job で実行します。~/fanclub-final/phase2/03-migration-job.yaml を次の内容で作成します。

apiVersion: batch/v1
kind: Job
metadata:
  name: fanclub-db-migration
  namespace: fanclub-prod
spec:
  backoffLimit: 4
  ttlSecondsAfterFinished: 300
  template:
    spec:
      restartPolicy: OnFailure
      containers:
      - name: migration
        image: postgres:18
        env:
        - name: PGPASSWORD
          valueFrom:
            secretKeyRef:
              name: fanclub-secret
              key: DB_PASSWORD
        command:
        - sh
        - -c
        - |
          until pg_isready -h fanclub-db -U fanclub -d fanclubdb; do
            sleep 2
          done
          psql -h fanclub-db -U fanclub -d fanclubdb -c \
            "CREATE TABLE IF NOT EXISTS members (id SERIAL PRIMARY KEY, name TEXT NOT NULL, email TEXT UNIQUE NOT NULL, created_at TIMESTAMP DEFAULT now());"
        resources:
          requests:
            cpu: 100m
            memory: 128Mi
          limits:
            cpu: 300m
            memory: 256Mi

各フィールドの意味です。backoffLimit: 4 は失敗時に最大 4 回まで再試行する設定。ttlSecondsAfterFinished: 300 は、第11回で学んだ「完了した Job を 300 秒後に自動削除する」指定で、これを付けないと完了済 Job が Namespace に残り続けます。

restartPolicy: OnFailure は Job で使う再起動ポリシーです。command の中身は、まず pg_isready で DB の準備完了を待ち、その後 members テーブルを CREATE TABLE IF NOT EXISTS で作成します。

IF NOT EXISTS を付けているので、Job が再実行されてもエラーになりません(冪等になっています)。

適用して完了を待ちます。実行コマンド:

$ kubectl apply -f phase2/03-migration-job.yaml
$ kubectl wait --for=condition=complete job/fanclub-db-migration \
    -n fanclub-prod --timeout=120s

期待される実行結果:

job.batch/fanclub-db-migration created
job.batch/fanclub-db-migration condition met

kubectl wait は、指定した条件(ここでは Job の complete)が満たされるまで待つコマンドです。condition met と出れば、マイグレーションが成功して members テーブルが作られています。Job のログを確認すると、テーブル作成の結果が見られます。実行コマンド:

$ kubectl logs job/fanclub-db-migration -n fanclub-prod

期待される実行結果:

fanclub-db:5432 - accepting connections
CREATE TABLE

Step 4: fanclub-api Backend Deployment を作る(ep12 / ep15 で学んだ)

ここが本回のアプリレイヤの中心です。第12回で学んだ Deployment に、第12回の 3 Probe(startupProbe / livenessProbe / readinessProbe)と、第15回の SecurityContext を組み合わせて、fanclub-api Backend をデプロイします。

Backend イメージは、第3回〜第4回でビルドして k8s-registry に push した fanclub-backend:0.1.0 を使います。~/fanclub-final/phase2/04-backend.yaml を次の内容で作成します。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: fanclub-backend
  namespace: fanclub-prod
  labels:
    app: fanclub-backend
spec:
  replicas: 1
  selector:
    matchLabels:
      app: fanclub-backend
  template:
    metadata:
      labels:
        app: fanclub-backend
    spec:
      serviceAccountName: fanclub-backend-sa
      automountServiceAccountToken: false
      securityContext:
        seccompProfile:
          type: RuntimeDefault
      containers:
      - name: backend
        image: k8s-registry:5000/fanclub-backend:0.1.0
        ports:
        - containerPort: 8080
          name: http
        envFrom:
        - configMapRef:
            name: fanclub-config
        env:
        - name: DB_PASSWORD
          valueFrom:
            secretKeyRef:
              name: fanclub-secret
              key: DB_PASSWORD
        resources:
          requests:
            cpu: 100m
            memory: 384Mi
          limits:
            cpu: 500m
            memory: 768Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]
        startupProbe:
          httpGet:
            path: /health/started
            port: 8080
          failureThreshold: 30
          periodSeconds: 10
        livenessProbe:
          httpGet:
            path: /health/live
            port: 8080
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /health/ready
            port: 8080
          periodSeconds: 5

このマニフェストには、第1巻で学んだ多くの機構が組み込まれています。順に整理します。serviceAccountName: fanclub-backend-sa はフェーズ 1 で作った ServiceAccount を割り当てる指定。envFrom で ConfigMap の全キーを環境変数として一括注入し、パスワードだけ env + valueFrom で Secret から個別注入しています。

3 Probe は第12回で学んだとおりです。startupProbe は Java / Payara の起動が遅いことを考慮し、failureThreshold: 30 × periodSeconds: 10 = 最大 300 秒の起動猶予を与えています。startupProbe が成功するまで liveness / readiness は動き出しません。

SecurityContext は image 非依存の項目だけを適用する — runAsNonRoot は概念のみ:第15回で SecurityContext を学んだとき、runAsNonRoot: true / runAsUser / readOnlyRootFilesystem: true など、コンテナを非 root で動かす項目を扱いました。ただし第15回の実機検証で判明したとおり、本シリーズの fanclub-backend:0.1.0 イメージは root 前提で作られた image です。

この image に runAsNonRoot: true を付けると、kubelet が「image が UID 0(root)で動こうとしている」ことを検知して起動を拒否し、Pod は Init:CreateContainerConfigError(または CreateContainerConfigError)で起動失敗します。

runAsNonRoot を有効にして非 root で動かすには、image 側の Dockerfile を改修して USER 命令で非 root ユーザーを指定し、必要なディレクトリの所有権を整える前提作業が必要です。これは Kubernetes のマニフェストの問題ではなく、コンテナイメージ(Dockerfile)の作り方の問題です。

そのため本演習の Backend には、image の作りに依存しない項目だけを適用しています。

具体的には allowPrivilegeEscalation: false(プロセスが自分より強い権限を獲得するのを禁止)、capabilities.drop: ["ALL"](Linux capability をすべて剥奪)、Pod レベルの seccompProfile.type: RuntimeDefault(コンテナランタイムの標準 seccomp プロファイルを適用)の 3 つです。これらは root 前提 image でも問題なく適用でき、CKAD でも頻出の SecurityContext 項目です。

runAsNonRoot / readOnlyRootFilesystem は「Dockerfile 改修が前提」であることを概念として理解しておけば十分で、本演習では適用しません。CKAD 試験で runAsNonRoot を問われた場合は、出題の image が非 root 対応であることを前提に設定すれば通ります。

適用します。実行コマンド:

$ kubectl apply -f phase2/04-backend.yaml

期待される実行結果:

deployment.apps/fanclub-backend created

Backend Pod が Running になるまで待ちます。Java / Payara の起動には 1〜2 分かかります。実行コマンド:

$ kubectl rollout status deployment/fanclub-backend -n fanclub-prod --timeout=300s

期待される実行結果:

Waiting for deployment "fanclub-backend" rollout to finish: 0 of 1 updated replicas are available...
deployment "fanclub-backend" successfully rolled out

Step 5: Backend の ClusterIP Service を作る(ep8 で学んだ)

第8回で学んだ Service は、変動する Pod の IP を隠して、安定したアクセス先(仮想 IP + DNS 名)を提供するリソースです。

Backend Pod を、後で公開レイヤの HTTPRoute から参照できるように、ClusterIP Service を付けます。~/fanclub-final/phase2/05-backend-service.yaml を次の内容で作成します。

apiVersion: v1
kind: Service
metadata:
  name: fanclub-backend
  namespace: fanclub-prod
  labels:
    app: fanclub-backend
spec:
  type: ClusterIP
  selector:
    app: fanclub-backend
  ports:
  - name: http
    port: 80
    targetPort: 8080

第8回で学んだ落とし穴を 1 つ振り返ります。Service の port は Service が受ける窓口のポート、targetPort は Pod のコンテナが待ち受けるポートです。

ここでは Service の窓口を 80、転送先の Pod ポートを 8080(Backend の containerPort)にしています。

selectorapp: fanclub-backend が Backend Pod のラベルと一致していることが、Service が Pod を見つける条件です。ラベルがずれていると Service のエンドポイントが空になり、通信が届きません。

適用してエンドポイントを確認します。実行コマンド:

$ kubectl apply -f phase2/05-backend-service.yaml
$ kubectl get endpointslice -l kubernetes.io/service-name=fanclub-backend -n fanclub-prod

期待される実行結果:

service/fanclub-backend created
NAME                     ADDRESSTYPE   PORTS   ENDPOINTS    AGE
fanclub-backend-x7k2p    IPv4          8080    10.244.0.18  10s

EndpointSlice に Backend Pod の IP(10.244.0.18 など)が登録されていれば、Service が Pod を正しく見つけています。第8回で学んだとおり、v1 の Endpoints リソースは K8s v1.33 以降で非推奨となり、現在は EndpointSlice で確認します。

Step 6: ログコレクター模擬の DaemonSet を作る(ep11 で学んだ)

第11回で学んだ DaemonSet は、すべての Workload Node に 1 つずつ Pod を配置するワークロードです。ログエージェントや監視エージェントのように「全ノードで動かしたいもの」に使います。

本回はログコレクターを模擬した軽量な DaemonSet を 1 つ配置し、DaemonSet の機構を総ざらいに組み込みます。~/fanclub-final/phase2/06-daemonset.yaml を次の内容で作成します。

apiVersion: apps/v1
kind: DaemonSet
metadata:
  name: fanclub-log-collector
  namespace: fanclub-prod
  labels:
    app: fanclub-log-collector
spec:
  selector:
    matchLabels:
      app: fanclub-log-collector
  template:
    metadata:
      labels:
        app: fanclub-log-collector
    spec:
      containers:
      - name: log-collector
        image: busybox:1.37
        command:
        - sh
        - -c
        - "while true; do echo \"[log-collector] $(date) collecting logs from $(hostname)\"; sleep 60; done"
        resources:
          requests:
            cpu: 50m
            memory: 64Mi
          limits:
            cpu: 100m
            memory: 64Mi
        securityContext:
          allowPrivilegeEscalation: false
          capabilities:
            drop: ["ALL"]

DaemonSet には Deployment と違って replicas フィールドがありません。「全ノードに 1 つずつ」が DaemonSet の数え方だからです。本シリーズの kind クラスタはシングルノードなので、DaemonSet の Pod は 1 つだけ作られます。

busybox イメージで 60 秒ごとにログ収集を模擬したメッセージを出力します。CPU 要求は 50m と軽くし、kind の 2 vCPU 制約を圧迫しないようにしています。SecurityContext は Backend と同じく image 非依存の項目だけを適用しています。

適用します。実行コマンド:

$ kubectl apply -f phase2/06-daemonset.yaml

期待される実行結果:

daemonset.apps/fanclub-log-collector created

Step 7: フェーズ 2 完了確認

フェーズ 2 で作ったリソースが、すべて意図どおりの状態になっているかを確認します。まず全 Pod の状態を見ます。実行コマンド:

$ kubectl get pods -n fanclub-prod

期待される実行結果:

NAME                               READY   STATUS      RESTARTS   AGE
fanclub-backend-6d9f8c5b7-xq4n2     1/1     Running     0          3m
fanclub-db-0                        1/1     Running     0          7m
fanclub-log-collector-h8k2v         1/1     Running     0          1m

Backend・DB・DaemonSet の 3 Pod が RunningREADY 1/1 になっています。DB マイグレーション Job の Pod は、完了して ttlSecondsAfterFinished: 300 で自動削除されていれば、ここには表示されません(実行直後ならまだ Completed で残っている場合もあります)。

次に ResourceQuota の使用量を見ます。実行コマンド:

$ kubectl get resourcequota fanclub-prod-quota -n fanclub-prod

期待される実行結果:

NAME                 AGE   REQUEST                                                  LIMIT
fanclub-prod-quota   30m   pods: 3/15, requests.cpu: 250m/1, requests.memory: 704Mi/2Gi   limits.cpu: 1100m/2, limits.memory: 1344Mi/4Gi

フェーズ 1 ではすべて 0 だった Used 側が、Pod 3 個ぶんの消費に増えています。requests.cpu250m/1pods3/15 で、いずれも上限に余裕があります。Backend が DB に接続できているかは、Backend のログで確認します。実行コマンド:

$ kubectl logs deployment/fanclub-backend -n fanclub-prod --tail=5

期待される実行結果:

[INFO] Payara Micro 7.2026.4 ready in 48123 ms
[INFO] Datasource fanclubdb connected: fanclub-db.fanclub-prod.svc.cluster.local:5432
[INFO] MicroProfile Health endpoints registered: /health/started /health/live /health/ready

フェーズ 2 チェックポイント:次のフェーズに進む前に、3 点を確認してください。

(1) kubectl get pods -n fanclub-prod で Backend・DB・DaemonSet の 3 Pod が Running かつ READY 1/1。(2) kubectl get endpointslice -l kubernetes.io/service-name=fanclub-backend -n fanclub-prod で Service のエンドポイントに Pod IP が登録されている。(3) Backend のログに DB 接続成功と MicroProfile Health エンドポイントの登録が出ている。

この 3 点が揃っていれば、フェーズ 3 へ進めます。フェーズ 2 の目安時間は約 55 分でした。

やってみよう③: フェーズ 3 — 公開レイヤ構築と HTTPS 動作確認

フェーズ 3 では、フェーズ 2 で動き出したアプリを「守って」「まとめて」「外に出す」処理を行います。

NetworkPolicy で Pod 間通信を制御し(守る)、ここまで個別の YAML で作ってきた構成を Helm Chart 1 つに統合する考え方を整理し(まとめる)、Gateway + HTTPRoute + cert-manager で HTTPS 公開して動作確認します(外に出す)。目安時間は約 40 分です。

Step 1: NetworkPolicy を段階的に適用する(ep16 で学んだ)

第16回で学んだ NetworkPolicy は、Pod 間の通信を Ingress(受信)/ Egress(送信)のルールで制御するリソースです。第16回で確認した重要な落とし穴は、適用順序でした。

default-deny-all(全拒否)を先に適用してしまうと、許可ルールを入れるまでの間アプリの通信が全部止まります。許可ルールを先に入れてから最後に default-deny-all を適用するのが安全な順序です。

本回は、許可ルールと拒否ルールを 1 つの YAML にまとめ、kubectl apply でまとめて適用します(同時適用なら順序の問題は起きません)。~/fanclub-final/phase3/01-networkpolicy.yaml を次の内容で作成します。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: fanclub-prod
spec:
  podSelector: {}
  policyTypes:
  - Ingress
  - Egress
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: fanclub-prod
spec:
  podSelector: {}
  policyTypes:
  - Egress
  egress:
  - to:
    - namespaceSelector: {}
    ports:
    - protocol: UDP
      port: 53
    - protocol: TCP
      port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-from-gateway
  namespace: fanclub-prod
spec:
  podSelector:
    matchLabels:
      app: fanclub-backend
  policyTypes:
  - Ingress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          kubernetes.io/metadata.name: traefik
    ports:
    - protocol: TCP
      port: 8080
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-db-from-backend
  namespace: fanclub-prod
spec:
  podSelector:
    matchLabels:
      app: fanclub-db
  policyTypes:
  - Ingress
  ingress:
  - from:
    - podSelector:
        matchLabels:
          app: fanclub-backend
    ports:
    - protocol: TCP
      port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-backend-egress-to-db
  namespace: fanclub-prod
spec:
  podSelector:
    matchLabels:
      app: fanclub-backend
  policyTypes:
  - Egress
  egress:
  - to:
    - podSelector:
        matchLabels:
          app: fanclub-db
    ports:
    - protocol: TCP
      port: 5432

5 つの NetworkPolicy の役割を整理します。default-deny-allpodSelector: {}(Namespace 内の全 Pod が対象)で Ingress / Egress の両方を拒否するベースライン。allow-dns は第16回で学んだ「Egress を絞ると DNS の 53 番が止まって名前解決が壊れる」落とし穴への対策で、全 Pod に DNS(UDP / TCP 53)を許可します。

allow-backend-from-gateway は Traefik(公開レイヤ)から Backend への受信を許可。allow-db-from-backend は Backend から DB への受信を許可。allow-backend-egress-to-db は Backend が DB へ送信できるよう Egress を許可します。

Ingress 側だけ・Egress 側だけを許可しても通信は成立しないため、両方向に許可ルールが要る点が NetworkPolicy の難所です。

適用します。実行コマンド:

$ kubectl apply -f phase3/01-networkpolicy.yaml

期待される実行結果:

networkpolicy.networking.k8s.io/default-deny-all created
networkpolicy.networking.k8s.io/allow-dns created
networkpolicy.networking.k8s.io/allow-backend-from-gateway created
networkpolicy.networking.k8s.io/allow-db-from-backend created
networkpolicy.networking.k8s.io/allow-backend-egress-to-db created

NetworkPolicy 適用後も Backend が DB に接続できているか、Backend Pod を再起動して確認します。実行コマンド:

$ kubectl rollout restart deployment/fanclub-backend -n fanclub-prod
$ kubectl rollout status deployment/fanclub-backend -n fanclub-prod --timeout=300s

期待される実行結果:

deployment.apps/fanclub-backend restarted
deployment "fanclub-backend" successfully rolled out

再起動後も successfully rolled out になれば、NetworkPolicy が DB 接続と DNS を許可した状態で正しく機能しています。もしここで Backend が起動失敗するなら、allow-dnsallow-backend-egress-to-db の許可漏れが疑われます。

Step 2: Helm Chart 統合の考え方を整理する(ep17 で学んだ)

ここまで、フェーズ 1〜3 で 10 個以上の YAML ファイルを kubectl apply してきました。第17回で学んだ Helm は、これらの YAML をまとめて 1 つのパッケージ(Chart)にし、helm install 一発でデプロイできるようにする仕組みです。

本回の総合演習では、まず生 YAML で 1 つずつ作って各機構を確認し、その後「これらは Helm Chart にまとめられる」という統合の視点を整理します。

第17回で作った ~/fanclub-charts/fanclub-api/ の Helm Chart は、ConfigMap / Secret / StatefulSet / Service / Deployment / Job などのマニフェストを templates/ に格納し、変動する値(イメージタグ・レプリカ数・リソース量など)を values.yaml に切り出した構造になっています。

本回の fanclub-prod Namespace 向けに values.yaml を統合的に書くと、次のようになります(~/fanclub-final/phase3/values-prod.yaml として作成)。

namespace: fanclub-prod

backend:
  image:
    repository: k8s-registry:5000/fanclub-backend
    tag: "0.1.0"          # ep4 で push したイメージタグ
  replicaCount: 1          # kind 2 vCPU 制約のため 1 に固定
  resources:
    requests:
      cpu: 100m            # LimitRange の defaultRequest と整合
      memory: 384Mi
    limits:
      cpu: 500m
      memory: 768Mi
  serviceAccountName: fanclub-backend-sa
  securityContext:
    allowPrivilegeEscalation: false   # image 非依存・適用可
    capabilities:
      drop: ["ALL"]                   # image 非依存・適用可
    # runAsNonRoot は Dockerfile 改修が前提のため設定しない

database:
  image:
    repository: postgres
    tag: "18"
  storage: 1Gi             # volumeClaimTemplates の要求容量

config:
  dbHost: fanclub-db.fanclub-prod.svc.cluster.local
  dbName: fanclubdb
  dbUser: fanclub

gateway:
  hostname: fanclub.local
  className: traefik       # ep18 で導入した GatewayClass

この values.yaml を見ると、本回の総合演習で個別 YAML に散らばっていた設定(イメージタグ・レプリカ数・リソース量・SecurityContext の項目・DB 接続情報・Gateway のホスト名)が、1 つのファイルに集約されています。

第17回で学んだ --version 指定によるバージョンピン留めと合わせると、helm install fanclub-api ~/fanclub-charts/fanclub-api/ -f phase3/values-prod.yaml -n fanclub-prod のような 1 コマンドで、フェーズ 1〜3 の構成をまとめてデプロイできる、というのが Helm のパッケージ管理の到達点です。

本回で生 YAML を使う理由:第17回で Helm Chart 化を学んだのに、本回の演習でなぜ生 YAML を 1 つずつ kubectl apply したのか、と疑問に思うかもしれません。理由は 2 つあります。

1 つは、総合演習の目的が「14 機構それぞれを自分の手で書いて理解を確認すること」だからです。Helm でまとめて install すると、各機構の YAML を 1 行も書かずにデプロイが終わってしまい、復習になりません。CKAD 試験も、Helm でのデプロイは出題範囲ですが、個々のリソース(Deployment・Service・NetworkPolicy 等)を YAML で書く力が問われる設問の方が多くを占めます。

もう 1 つは、本回が「Helm Chart にまとめられる構成を、まず分解して理解する」という流れを取っているためです。生 YAML で各機構を確認した上で「これらは values.yaml 1 つに集約できる」と理解する方が、Helm の価値が分かります。

実務でも、新しいアプリを Chart 化する前には、まず生 YAML で動かして各リソースを確認してから Chart にまとめる、という進め方が定石です。

Step 3: Gateway と Certificate を作る(ep18 で学んだ)

第18回で学んだ Gateway API は、外部からの HTTP / HTTPS トラフィックの入口を定義する仕組みです。Gateway がリスナー(ポート・プロトコル・TLS)を、HTTPRoute がホスト名・パスの振り分けを担当します。

本回は ep18 で導入済の Gateway API CRD・cert-manager・Traefik(GatewayClass traefik)を再利用し、fanclub-prod Namespace 向けに Gateway / Certificate / HTTPRoute を作ります。

第18回で作った自己署名 ClusterIssuer(ca-issuer)はクラスタスコープのリソースで、どの Namespace からも参照できます。これを使って fanclub-prod 向けの TLS 証明書を発行し、Gateway の HTTPS リスナーで TLS 終端します。~/fanclub-final/phase3/02-gateway.yaml を次の内容で作成します。

apiVersion: cert-manager.io/v1
kind: Certificate
metadata:
  name: fanclub-prod-tls
  namespace: fanclub-prod
spec:
  secretName: fanclub-prod-tls
  dnsNames:
  - fanclub.local
  issuerRef:
    name: ca-issuer
    kind: ClusterIssuer
---
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
  name: fanclub-prod-gateway
  namespace: fanclub-prod
spec:
  gatewayClassName: traefik
  listeners:
  - name: websecure
    protocol: HTTPS
    port: 443
    hostname: fanclub.local
    tls:
      mode: Terminate
      certificateRefs:
      - kind: Secret
        name: fanclub-prod-tls
    allowedRoutes:
      namespaces:
        from: Same

Certificate の issuerRef は ep18 で作った ca-issuer(ClusterIssuer)を指し、secretName: fanclub-prod-tls で発行された証明書を Secret に格納します。

Gateway の listeners は HTTPS のリスナーを 1 つ定義し、tls.mode: Terminate で Gateway 側で TLS を終端、certificateRefs で Certificate が作る Secret を参照します。第18回で学んだとおり、tls.mode: Terminate はクライアントと Gateway の間が HTTPS、Gateway とバックエンドの間は平文 HTTP になるモードです。

allowedRoutes.namespaces.from: Same は、同じ Namespace の HTTPRoute だけがこの Gateway に紐付けられる設定です。

適用して証明書が発行されるのを待ちます。実行コマンド:

$ kubectl apply -f phase3/02-gateway.yaml
$ kubectl wait --for=condition=Ready certificate/fanclub-prod-tls \
    -n fanclub-prod --timeout=120s

期待される実行結果:

certificate.cert-manager.io/fanclub-prod-tls created
gateway.gateway.networking.k8s.io/fanclub-prod-gateway created
certificate.cert-manager.io/fanclub-prod-tls condition met

Gateway の状態を確認します。実行コマンド:

$ kubectl get gateway fanclub-prod-gateway -n fanclub-prod

期待される実行結果:

NAME                   CLASS     ADDRESS        PROGRAMMED   AGE
fanclub-prod-gateway   traefik   10.96.x.x      True         40s

PROGRAMMEDTrue になっていれば、Traefik が Gateway を受け付けてリスナーを構成できています。

第18回で学んだとおり、ここが False のままなら、Traefik の entryPoint と Gateway のリスナーのポート不一致が疑われます。本回は ep18 で Traefik の websecure entryPoint を 443 に合わせ済なので、True になります。

Step 4: HTTPRoute を作る(ep18 で学んだ)

HTTPRoute は、Gateway に届いたリクエストを、ホスト名・パスでどの Service に振り分けるかを定義するリソースです。fanclub.local へのリクエストを Backend Service(fanclub-backend)に転送します。~/fanclub-final/phase3/03-httproute.yaml を次の内容で作成します。

apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
  name: fanclub-prod-route
  namespace: fanclub-prod
spec:
  parentRefs:
  - name: fanclub-prod-gateway
  hostnames:
  - fanclub.local
  rules:
  - matches:
    - path:
        type: PathPrefix
        value: /
    backendRefs:
    - name: fanclub-backend
      port: 80

parentRefs で Step 3 の Gateway(fanclub-prod-gateway)を指し、hostnamesfanclub.local 宛のリクエストを対象にします。rules.matches はパス / 以下すべて(PathPrefix)、backendRefs は Backend Service(Step 5 で作った fanclub-backendport: 80)を指します。

Backend Service と HTTPRoute は同じ fanclub-prod Namespace にあるため、第18回で学んだ ReferenceGrant は不要です。

適用して状態を確認します。実行コマンド:

$ kubectl apply -f phase3/03-httproute.yaml
$ kubectl get httproute fanclub-prod-route -n fanclub-prod -o wide

期待される実行結果:

httproute.gateway.networking.k8s.io/fanclub-prod-route created
NAME                 HOSTNAMES          PARENTS                          AGE
fanclub-prod-route   ["fanclub.local"]  fanclub-prod-gateway (Accepted)   15s

HTTPRoute の詳細を describe で見て、Accepted=TrueResolvedRefs=True を確認します。実行コマンド:

$ kubectl describe httproute fanclub-prod-route -n fanclub-prod

期待される実行結果(末尾の Conditions 部分):

  Parents:
    Conditions:
      Type:      Accepted
      Status:    True
      Reason:    Accepted
      Type:      ResolvedRefs
      Status:    True
      Reason:    ResolvedRefs

Accepted=True は Gateway が HTTPRoute を受け付けたこと、ResolvedRefs=TruebackendRefs が指す Backend Service が見つかったことを意味します。両方が True なら、ルーティングが構成できています。

Step 5: HTTPS 動作確認

ここまでで構成が揃ったので、HTTPS でのアクセス確認に進みます。第18回で確立した方式どおり、本シリーズの kind クラスタは 80 / 443 をホストに公開する extraPortMappings を持たないため、kubectl port-forward で Traefik の Service をローカルに転送してからアクセスします。

まず port-forward をバックグラウンドで起動します。実行コマンド:

$ kubectl port-forward -n traefik service/traefik 8443:443 &

期待される実行結果:

Forwarding from 127.0.0.1:8443 -> 8443
Forwarding from [::1]:8443 -> 8443

localhost の 8443 番が Traefik の 443 番に転送されました。この状態で curl を使い、fanclub.local 宛の HTTPS リクエストを送ります。

--resolvefanclub.local:8443127.0.0.1 に名前解決させ、--noproxy "*" で alma-proxy を経由させず、-k で自己署名証明書の検証をスキップします。実行コマンド:

$ curl --noproxy "*" --resolve fanclub.local:8443:127.0.0.1 \
    https://fanclub.local:8443/health/live -k

期待される実行結果:

{"status":"UP","checks":[{"name":"fanclub-backend-liveness","status":"UP"}]}

HTTPS 経由で /health/live{"status":"UP"} を返しました。これが、14 機構が連携して fanclub-api が HTTPS 公開された状態の証明です。証明書の発行元も確認しておきます。-v(verbose)を付けると TLS ハンドシェイクの詳細が見られます。実行コマンド:

$ curl --noproxy "*" --resolve fanclub.local:8443:127.0.0.1 \
    https://fanclub.local:8443/health/live -k -v 2>&1 | grep -E "issuer|subject"

期待される実行結果:

*  subject: CN=fanclub.local
*  issuer: CN=fanclub-selfsigned-ca

証明書の subject が fanclub.local、issuer が ep18 で作った自己署名 CA(fanclub-selfsigned-ca)であることが確認できました。cert-manager が証明書を発行し、Gateway が TLS 終端に使っています。port-forward はもう不要なので止めます。実行コマンド:

$ kill %1

Step 6: フェーズ 3 完了確認 — 14 機構の連携を一望する

フェーズ 3 まで終わったところで、fanclub-prod Namespace の全リソースを一望し、14 機構がすべて揃っているかを確認します。実行コマンド:

$ kubectl get all,configmap,secret,serviceaccount,networkpolicy,pvc \
    -n fanclub-prod

期待される実行結果(抜粋):

NAME                                  READY   STATUS    RESTARTS   AGE
pod/fanclub-backend-7c8d6f4b9-mn3kp    1/1     Running   0          12m
pod/fanclub-db-0                       1/1     Running   0          25m
pod/fanclub-log-collector-h8k2v        1/1     Running   0          20m

NAME                      TYPE        CLUSTER-IP     PORT(S)
service/fanclub-backend   ClusterIP   10.96.x.x      80/TCP
service/fanclub-db        ClusterIP   None           5432/TCP

NAME                              READY   UP-TO-DATE   AVAILABLE
deployment.apps/fanclub-backend   1/1     1            1

NAME                                     DESIRED   CURRENT   READY
daemonset.apps/fanclub-log-collector     1         1         1

NAME                          READY   AGE
statefulset.apps/fanclub-db   1/1     25m

14 機構が fanclub-prod Namespace に揃いました。どの機構がどのフェーズで作られたかを、最後に表で整理します。

機構リソース名フェーズ習得回
Namespacefanclub-prod1第14回
ResourceQuotafanclub-prod-quota1第14回
LimitRangefanclub-prod-limits1第14回
ServiceAccount / RBACfanclub-backend-sa / pod-reader1第10回 / 第15回
ConfigMap / Secretfanclub-config / fanclub-secret2第10回
StatefulSet / PVCfanclub-db / db-data-fanclub-db-02第9回
Jobfanclub-db-migration2第11回
Deployment / 3 Probefanclub-backend2第12回
SecurityContextBackend / DaemonSet に適用2第15回
Service(ClusterIP)fanclub-backend / fanclub-db2第8回
DaemonSetfanclub-log-collector2第11回
NetworkPolicydefault-deny-all ほか 5 個3第16回
Helmvalues-prod.yaml 統合の整理3第17回
Gateway API / cert-managerfanclub-prod-gateway / HTTPRoute / Certificate3第18回

フェーズ 3 チェックポイント:総合演習の最終確認です。

(1) kubectl get gateway fanclub-prod-gateway -n fanclub-prodPROGRAMMED=True。(2) kubectl get httproute fanclub-prod-route -n fanclub-prodAccepted。(3) curl --noproxy "*" --resolve fanclub.local:8443:127.0.0.1 https://fanclub.local:8443/health/live -k{"status":"UP"} を返す。

この 3 点が揃えば、14 機構の通し演習は完了です。3 フェーズの合計目安時間は約 120 分でした。

完走マイルストーン — https://fanclub.local の到達点と CRUD のスコープ

3 フェーズの総合演習を終えたところで、第1巻の「完走マイルストーン」が何を意味するのかを、はっきりさせておきます。これは、本シリーズが何を教える教材なのか、という根本にも関わる話です。

完走マイルストーンの到達点

本回の完走マイルストーンの到達点は、次のように定義します。

第1巻 完走マイルストーンhttps://fanclub.local に HTTPS でアクセスし、/health/live{"status":"UP"} を返す。

これは、第1巻で学んだ全 14 機構(Namespace・ResourceQuota・LimitRange・StatefulSet・PVC・Job・Deployment・3 Probe・Service・ConfigMap・Secret・ServiceAccount・RBAC・SecurityContext・DaemonSet・NetworkPolicy・Helm・Gateway API・cert-manager)が連携して、fanclub-api というアプリケーションが安全に・リソース制限付きで・HTTPS で公開された状態である、ということを意味する。

/health/live が UP を返す」という地味な確認に見えるかもしれませんが、その 1 つの HTTPS レスポンスが返ってくるためには、14 機構すべてが正しく連携している必要があります。

Namespace の器があり、Quota の枠内にリソースが収まり、ServiceAccount が割り当てられ、ConfigMap / Secret から設定が注入され、StatefulSet の DB が永続ストレージで動き、Job がテーブルを作り、Deployment の Backend が 3 Probe を通過して Running になり、Service がエンドポイントを公開し、NetworkPolicy が必要な通信だけを許可し、Gateway / HTTPRoute / cert-manager が HTTPS の入口を構成している。

この連鎖のどこか 1 つでも欠けると、/health/live の UP レスポンスは返ってきません。1 つの HTTPS レスポンスが、14 機構の連携が成立している証明書になっているのです。

CRUD 操作のスコープ — アプリ実装は本シリーズの範囲外

第1巻のカリキュラムには、完走マイルストーンとして「https://fanclub.local から会員登録・一覧・編集・削除(CRUD)が動作する」という記述があります。本回では、これを「https://fanclub.local に HTTPS でアクセスし /health/live が UP を返す」に現実的に整理しました。なぜそうするのか、誠実に説明します。

第9回の実機検証で判明したとおり、本シリーズが使う fanclub-backend:0.1.0 イメージは、MicroProfile Health のエンドポイント(/health/started / /health/live / /health/ready)だけを実装しています。会員の登録・一覧・編集・削除を行う /api/members のような CRUD の REST API は、このイメージには実装されていません。

CRUD のフル実装は、fanclub-backend の Java アプリケーション側のビジネスロジックを書くことに相当します。SQL を発行し、JSON を組み立て、バリデーションをかける、というアプリケーション開発の作業です。

本シリーズは Kubernetes の教材です。本シリーズが扱うのは「アプリケーションをどう Kubernetes で動かし・公開し・運用するか」であり、「アプリケーション本体のビジネスロジックをどう実装するか」は扱いません。

Java で CRUD の REST API を書くことは、Java / Jakarta EE の教材の範囲であって、Kubernetes の教材の範囲ではありません。本シリーズが /health/live までで完走マイルストーンを区切るのは、教材としてのスコープを誠実に守るためです。

読者が自分のアプリに置き換える場合:本回の総合演習で組み立てた 14 機構の Kubernetes 構成は、fanclub-api 専用のものではありません。読者が自分の業務で開発している、CRUD を実装済の Web アプリケーション(言語は Java でも Go でも Python でも構いません)があるなら、その image を fanclub-backend:0.1.0 の代わりに指定し、ConfigMap / Secret のキーを自分のアプリの環境変数に合わせるだけで、まったく同じ 14 機構の構成でそのアプリを HTTPS 公開できます。

Backend Deployment の image を差し替え、Probe のパスを自分のアプリのヘルスチェックエンドポイントに合わせ、HTTPRoute のパスを調整すれば、CRUD が動くアプリがそのまま https://<your-host> で公開されます。

本シリーズで学んだのは「アプリを K8s で本番相当に動かす型」であり、その型は中身のアプリが何であっても再利用できる、という点が第1巻の到達点です。

CKAD 試験本番の形式と最終チェックリスト

第1巻の最終回として、CKAD 試験本番の形式を整理し、受験に向けた最終チェックリストを示します。本回の総合演習で 14 機構を 120 分で組み立てる練習をしてきたのは、この試験本番のシミュレーションでもありました。

CKAD 試験の形式

項目内容
試験形式Performance-based(実機操作)。ブラウザ上の仮想ターミナルで実際に kubectl を操作して課題を解く。多肢選択式ではない
試験時間120 分(2 時間)
問題数15〜20 問。1 問あたり平均 6〜8 分
合格ライン66% 以上
監督オンライン監督(プロクター)。Web カメラと画面共有で監視される
参照可能なドキュメントkubernetes.io / helm.sh の公式ドキュメント。試験中にブラウザの別タブで参照できる
有効期間3 年
対象 K8s バージョンv1.35(2026 年時点)
付属特典Killer.sh 模擬試験 2 回分(受験申込時に付属)

CKAD は、知識を覚えているかではなく、実際にクラスタを操作して課題を解けるかを問う試験です。kubectl を素早く正確に打てること、YAML をすぐに書けること、リソースの依存関係を把握していることが問われます。本回の総合演習で身につけた「依存される側から作る順序感覚」は、試験で複数リソースを組み合わせる設問にそのまま効きます。

CKAD v1.35 のドメイン別出題比率

CKAD の 5 ドメインには、それぞれ出題比率が定められています。学習の最終配分を確認するために、比率と第1巻での主な対応回を整理します。

ドメイン名称出題比率第1巻の主な対応回
D1Application Design and Build20%第1〜4回・第7回・第9回・第11回
D2Application Deployment20%第12回・第13回・第17回
D3Application Observability and Maintenance15%第6回・第12回
D4Application Environment, Configuration and Security25%第10回・第14回・第15回
D5Services and Networking20%第8回・第16回・第18回

出題比率で見ると、D4(Environment, Configuration and Security)が 25% で最大です。ConfigMap / Secret・ResourceQuota / LimitRange・RBAC・SecurityContext がここに含まれます。次いで D1・D2・D5 が各 20%、D3 が 15% です。

試験対策の総仕上げでは、配点が一番高い D4 の見直しに最も時間を割き、次に D1・D2・D5、最後に D3、という配分が合理的です。

本回の総合演習でも、フェーズ 1(基盤レイヤ)とフェーズ 2 の設定・セキュリティ部分が D4 に対応しており、自然と配点の高い領域を厚く復習する構成になっています。

CKAD 速攻テクニック総まとめ

120 分で 15〜20 問を解くには、kubectl の操作スピードが重要です。第1巻の各回で扱った速攻テクニックを総まとめします。

テクニック用途習得回
alias k=kubectlkubectlk に短縮。試験開始直後に設定する第6回
kubectl config set-context --current --namespace=<ns>カレント Namespace を切り替え、-n の打ち忘れと打ち過ぎを防ぐ第6回 / 第14回
--dry-run=client -o yamlクラスタに適用せず YAML の雛形を生成。> pod.yaml でファイルに出して編集する第6回 / 第7回
kubectl explain <resource>.<field>リソースのフィールド定義を調べる。YAML の書き方を忘れたとき即座に確認第6回
kubectl create deployment <name> --image=<img>Deployment を 1 コマンドで生成。--dry-run と組み合わせて雛形にする第12回
kubectl set image / kubectl scaleイメージ更新・レプリカ数変更を YAML 編集せず即実行第12回
kubectl rollout status / history / undoローリングアップデートの状態確認・履歴・ロールバック第12回
kubectl create configmap / secret --from-literalConfigMap / Secret をコマンドラインから直接生成第10回
kubectl auth can-i <verb> <resource> --as=<subject>RBAC の権限を即座に検証第15回
kubectl wait --for=condition=<cond>リソースが特定の状態になるまで待つ。Job 完了待ち等に使う第11回 / 第17回
helm install / upgrade / rollback / templateHelm のフルサイクル操作。helm template はマニフェスト確認第17回

特に --dry-run=client -o yaml は CKAD で最も多用するテクニックです。Pod や Deployment を 1 から YAML で書くより、kubectl run / kubectl create で雛形を生成してから必要な箇所だけ編集する方が速く、ミスも減ります。

試験開始直後に alias k=kubectl を設定し、各設問では最初にカレント Namespace を設問指定のものに切り替える、という流れを習慣にしておきます。

受験前の最終チェックリスト

CKAD 受験前に、ドメイン別に「自分の手で書けるか」を自己点検するチェックリストです。すべてに「はい」と答えられれば、第1巻の学習内容は身についています。

  • D1:Pod / Deployment / StatefulSet / DaemonSet / Job / CronJob を、用途に応じて選び、YAML で書けるか。Init Container と Sidecar の使い分けを説明できるか。PVC と volumeClaimTemplates を書けるか
  • D2:Deployment のローリングアップデートを実施し、rollout undo でロールバックできるか。Blue/Green と Canary の違いを説明できるか。helm install / upgrade / rollback を実行できるか
  • D3:3 種の Probe(startup / liveness / readiness)を設計・実装できるか。kubectl logs --previous で再起動前ログを見られるか。kubectl describe の Events で CrashLoopBackOff の原因を特定できるか
  • D4:ConfigMap / Secret を作り、envFrom / valueFrom で Pod に注入できるか。ResourceQuota / LimitRange を書けるか。Role / RoleBinding を書き、auth can-i で検証できるか。SecurityContext の allowPrivilegeEscalation / capabilities を設定できるか
  • D5:ClusterIP / NodePort Service を書けるか。Service の porttargetPort の違いを説明できるか。NetworkPolicy で default-deny + 段階的許可を書けるか。Gateway / HTTPRoute で HTTP 公開を構成できるか
  • 共通--dry-run=client -o yaml で雛形を生成できるか。kubectl explain でフィールドを調べられるか。120 分で 15〜20 問のペース感覚を、本回の総合演習で確認したか

このチェックリストで「うろ覚え」の項目があれば、その機構の習得回(前掲の対応表)に戻って復習します。CKAD 受験申込時には Killer.sh の模擬試験 2 回分が付属します。第1巻完走後に Killer.sh にチャレンジし、本番のペースを最終確認するのが推奨の流れです。Killer.sh は本番よりやや難しめに作られているため、Killer.sh で合格ラインに届けば本番は対応できます。

19 回のつまずきポイント総整理

本回は総合演習のため、新しいヒヤリハットは扱いません。代わりに、第1回から第18回までの各回で扱った重大な発見・つまずきポイントを、体系的に振り返ります。CKAD 試験でも実務でも、これらは繰り返し遭遇する落とし穴です。総整理しておくことで、つまずいたときにどの回に戻ればよいかが分かります。

環境系のつまずき

つまずき内容と対策該当回
kind の 2 vCPU 制約kind はシングルノードで CPU が限られる。全機構を 1 Namespace に展開すると CPU 要求の合計が上限を超え Pod が Pending になる。replicas を 1 に絞り、requests.cpu100m 程度に抑える第5回 / 第19回
alma-proxy の whitelist企業プロキシ再現のため alma-proxy は whitelist 方式。registry.k8s.io のリダイレクト先や kind.sigs.k8s.io/dl/ が未許可だとイメージ取得・バイナリ取得に失敗する。GitHub Releases 経由や Docker Hub mirror の明示指定で回避する第1回 / 第5回
kind の port mappingkind はデフォルトで 80 / 443 をホストに公開しない。extraPortMappings はクラスタ作成時にしか指定できず後付け不可。本シリーズは kubectl port-forward で HTTPS 検証する第18回 / 第19回

リソース系のつまずき

つまずき内容と対策該当回
OOMKilledコンテナのメモリ使用量が limits.memory を超えると OOMKilled で強制終了。Java では -XX:MaxRAMPercentage でヒープを limits.memory の内側に収める第7回
PVC が WaitForFirstConsumerkind の standard StorageClass は WaitForFirstConsumer モード。PVC は Pod がスケジュールされて初めて Bound になる。Pod を作らずに PVC だけ見て「Pending だ」と慌てない第9回
ResourceQuota と LimitRange の相互作用ResourceQuota が requests.cpu を制限する Namespace では、resources を書かないコンテナを含む Pod が作成拒否される。LimitRange でデフォルト値を入れるか、全コンテナに resources を明示する第14回 / 第19回

ネットワーク系のつまずき

つまずき内容と対策該当回
NetworkPolicy の適用順序default-deny-all を先に適用すると、許可ルールを入れるまで通信が全断する。許可ルールを先に適用してから最後に deny を入れる。または 1 つの YAML にまとめて同時適用する第16回 / 第19回
Egress 制限で DNS が切れるNetworkPolicy で Egress を絞ると、DNS(UDP / TCP 53)も拒否されて名前解決が壊れる。allow-dns ポリシーで 53 番を明示的に許可する第16回
Service の port と Pod の containerPort のずれService の targetPort と Pod の containerPort、Service の selector と Pod のラベルがずれると通信が届かない。kubectl get endpointslice でエンドポイントが空でないか確認する第8回

デプロイ系のつまずき

つまずき内容と対策該当回
startupProbe の不足で起動失敗Java / Payara は起動に時間がかかる。startupProbe の failureThreshold × periodSeconds が短いと、起動途中で liveness に殺され CrashLoopBackOff になる。最大 300 秒程度の猶予を与える第12回
Recreate 戦略の戻し忘れ検証で strategy.type: Recreate にしたまま戻し忘れると、本番のデプロイで毎回ダウンタイムが発生する。検証後は RollingUpdate に戻す第13回
helm rollback のリビジョン取り違えhelm rollback のリビジョン番号を間違えると、意図しない過去の状態に戻る。helm history でリビジョンを確認してからロールバックする。rollback 自体も新しいリビジョン番号を増やす第17回

セキュリティ・公開系のつまずき

つまずき内容と対策該当回
runAsNonRoot と image の前提のずれroot 前提で作られた image に runAsNonRoot: true を付けると CreateContainerConfigError で起動失敗する。非 root 化には image の Dockerfile 改修(USER 指定)が前提。マニフェスト側だけでは解決しない第15回 / 第19回
automountServiceAccountTokenPod は標準で ServiceAccount トークンが自動マウントされる。API Server を操作しない Pod には automountServiceAccountToken: false でトークンを配らない方が安全第10回 / 第15回
cert-manager の enableGatewayAPIcert-manager を Gateway API と連携させるには、インストール時に Gateway API サポートを有効化する必要がある。これを忘れると Gateway 経由の証明書発行が動かない第18回
Traefik entryPoint と Gateway リスナーの整合Traefik の entryPoint のポートと Gateway リスナーのポートが一致しないと、Gateway が PROGRAMMED=False のままになる。本シリーズは Traefik の websecure を 443 に合わせている第18回

これらのつまずきに共通するのは、「YAML を作っただけで安心せず、kubectl get / describe / logs で実際の状態を確認する」という習慣の重要性です。STATUSREADYConditionsEvents を見れば、ほとんどのつまずきは原因を特定できます。CKAD 試験でも、設問のリソースを作った後に状態を確認する数十秒が、減点を防ぎます。

第1巻完走宣言 — CKAD 受験準備完了

本回をもって、Kubernetes 実践教科書 第1巻「CKAD アプリケーション開発編」全 19 回が完走です。第1回でコンテナとは何かを学んでから、本回で 14 機構を連携させた総合演習を完遂するまで、19 回かけて段階的に積み上げてきました。

fanclub-api の 19 回の変化

第1巻を通して、fanclub-api は次のように変わってきました。第3回では「Dockerfile から作られたただのコンテナイメージ」でした。

第7回で初めて Kubernetes の Pod として動き、第9回で 3 層構成(Frontend + Backend + DB)が完成し、第12回で Deployment 化されて自己回復するようになり、第15回でセキュアになり、第16回で通信制御され、第17回で Helm Chart にパッケージ化され、第18回で HTTPS 公開されました。

そして本回の総合演習で、その全機構を 1 回の通し演習で組み立て直しました。「コンテナイメージ」から「Kubernetes 上で安全に・リソース制限付きで・HTTPS 公開された本番相当のサービス」へ。これが第1巻での fanclub-api の変化です。

読者が得た実技能力

第1巻を完走した読者は、次の実技能力を身につけた状態です。

  • Dockerfile を書き、マルチステージビルドでアプリケーションのコンテナイメージをビルドし、レジストリに push できる
  • kind で学習用クラスタを起動し、kubectl でクラスタを操作し、ログ確認・Pod デバッグ・Events 確認でトラブルシュートできる
  • Pod・Deployment・StatefulSet・DaemonSet・Job・CronJob を、用途に応じて選んで YAML で書ける
  • Service でアプリへのアクセスを提供し、ConfigMap / Secret で設定を外部化できる
  • 3 種の Probe を設計・実装・デバッグし、ローリングアップデートとロールバックを実施できる
  • ResourceQuota / LimitRange でリソースを管理し、RBAC と SecurityContext でセキュアにできる
  • NetworkPolicy で通信を制御し、Helm でアプリをパッケージ化し、Gateway API + cert-manager で HTTPS 公開できる
  • 14 機構を依存関係に沿った順序で組み立て、1 つのアプリケーションを本番相当の構成でデプロイできる

第1巻完走 = CKAD 受験準備完了:第1巻の 19 回は、CKAD(Certified Kubernetes Application Developer)の 5 ドメインを完全網羅するように設計されています。D1(Application Design and Build)から D5(Services and Networking)まで、出題範囲のすべてを実機演習を通じて扱いました。

本回の総合演習で 14 機構を 120 分で組み立てる練習をしたことで、試験本番のペース感覚もつかめています。

第1巻を完走した今、CKAD の受験準備は整いました。Killer.sh の模擬試験 2 回分で最終確認をすれば、CKAD 受験に臨めます。

第1巻の到達目標は「自社プロダクトを Kubernetes 上にデプロイし、HTTPS で公開できるレベル」でした。本回の総合演習で、その目標に到達したことを自分の手で確認できたはずです。読者は今、Kubernetes アプリケーション開発の業務に着手できる実技能力を持っています。

Kubestronaut への道 — 第2巻 CKA / 第3巻 CKS への展望

第1巻の完走は、Kubernetes エンジニアとしての出発点です。本シリーズは三部作で、第1巻(CKAD)の先に第2巻(CKA)と第3巻(CKS)があります。最終的なゴールは、5 つの資格をすべて取得して Kubestronaut として認定されることです。

第2巻 CKA — クラスタ構築・運用編

第1巻では、学習用の kind クラスタの「上に」アプリをデプロイすることを学びました。第2巻「CKA クラスタ構築・運用編」では、視点が変わります。クラスタ「そのもの」を、kubeadm を使って自力で構築・運用します。

本番想定の HA(High Availability・高可用性)構成として、Control Plane Node を 3 台・Workload Node を 2 台といった複数 VM 構成を組み、AlmaLinux 上に Kubernetes v1.35 を kubeadm でインストールします。

CNI(Calico など)の導入、Longhorn による分散ストレージ、kube-prometheus-stack による監視、Loki によるログ収集、ArgoCD による GitOps 化まで扱い、CKA(Certified Kubernetes Administrator)の受験準備が整います。第1巻が「アプリ開発者の視点」だったのに対し、第2巻は「基盤運用者の視点」を学ぶ巻です。

第3巻 CKS — 本番運用・SRE 編

第3巻「CKS 本番運用・SRE 編」では、セキュリティと信頼性を扱います。第1巻でも SecurityContext や NetworkPolicy でセキュリティの基礎に触れましたが、第3巻ではさらに踏み込み、CIS ベンチマークによる監査(kube-bench)、Falco によるランタイムセキュリティ、OPA Gatekeeper によるポリシー制御、Trivy によるイメージスキャン、Cilium による Pod 間 mTLS 暗号化、SBOM とイメージ署名(Cosign)といった、本番クラスタを守る技術を学びます。

さらに SRE(Site Reliability Engineering)の観点として、SLO / Error Budget / Runbook / インシデント対応も扱います。第3巻を完走すると、CKS(Certified Kubernetes Security Specialist)の受験準備が整い、「立派な Kubernetes エンジニア」と呼べる実力に到達します。

Kubestronaut 5 冠と三部作の対応

Kubestronaut は、Linux Foundation が提供する称号で、Kubernetes 関連の 5 つの資格をすべて有効に保持している人に与えられます。三部作との対応は次のとおりです。

[第1巻完走 (19回)]  → CKAD 受験準備完了  ← 今ここ(本回で達成)
       ↓
[第2巻完走 (16回)]  → CKA 受験準備完了
       ↓
[第3巻完走 (13回)]  → CKS 受験準備完了
       ↓
[KCNA + KCSA 自習]  → 多肢選択式・各1ヶ月程度の自習で取得
       ↓
★ Kubestronaut 5冠(CKAD + CKA + CKS + KCNA + KCSA)
Kubestronaut 5 冠ロードマップ図。第1巻完走(CKAD アプリケーション開発編)から CKAD 取得、第2巻 CKA クラスタ構築・運用編、第3巻 CKS 本番運用・SRE 編、KCNA + KCSA の自習、最後に Linux Foundation 認定の Kubestronaut 5 冠へと到達する 6 ステップを 2 段折り返しで示した到達経路図。

三部作(第1〜3巻)を完走すると、実技 3 資格(CKAD / CKA / CKS)の受験準備が整います。残る KCNA(Kubernetes and Cloud Native Associate)と KCSA(Kubernetes and Cloud Native Security Associate)は、実技ではなく多肢選択式の試験です。

三部作で実技力を身につけていれば、KCNA・KCSA は知識の整理が中心になるため、各 1 ヶ月程度の自習で取得できます。

CKAD・CKA・CKS・KCNA・KCSA の 5 資格をすべて有効に保持すると、Linux Foundation から Kubestronaut として認定されます。

第1巻を完走した今、5 冠の最初の 1 つ(CKAD)の受験準備が整いました。次は第2巻で CKA、第3巻で CKS へと進みます。1 巻ずつ、実機で手を動かしながら積み上げていけば、Kubestronaut 5 冠は十分に達成できる目標です。

理解度チェック(総合)・第1巻まとめ

理解度チェック(○×形式・9 問・第1巻総合)

第1巻全体から横断的に出題します。各ドメインから 1〜2 問ずつ取り上げています。

問 1:ResourceQuota が requests.cpu を制限している Namespace では、Pod に resources を明示するか、LimitRange でデフォルト値を設定するかのどちらかが必要である。

問 2:StatefulSet の Pod を削除すると、その Pod に紐づく PVC も同時に削除される。

問 3:Deployment の Recreate 戦略は、新旧の Pod が一時的に同時稼働する。

問 4:NetworkPolicy が 1 つも適用されていない Pod は、すべての通信が許可される。

問 5helm rollback を実行すると、リビジョン番号が増える。

問 6:Gateway API の GatewayClass は、クラスタスコープのリソースである。

問 7:SecurityContext の runAsNonRoot: true は、root 前提で作られた image を自動的に非 root 化する。

問 8:CKAD 試験は、実機を操作して課題を解く Performance-based 形式である。

問 9kubectl--dry-run=client -o yaml は、クラスタに適用せず YAML を生成する。

解答

解答解説
問 1ResourceQuota が requests.cpu を制限する Namespace では、各 Pod が要求量を申告しないと合計が計算できないため、resources 未指定の Pod は作成拒否される。全コンテナに resources を明示するか、LimitRange の defaultRequest でデフォルト値を入れる。第14回・本回フェーズ 1 で扱った
問 2×StatefulSet の Pod を削除しても、volumeClaimTemplates 由来の PVC は削除されない。PVC が残るからこそ、Pod が再作成されたときに同じデータを引き継げる。これが StatefulSet がデータベースに向く理由。第9回で扱った
問 3×Recreate 戦略は、旧 Pod を全削除してから新 Pod を起動するため、新旧の Pod が同時稼働する瞬間はない。その代わりダウンタイムが発生する。新旧が一時的に同時稼働するのは RollingUpdate 戦略。第13回で扱った
問 4NetworkPolicy が 1 つも適用されていない Pod は、Ingress / Egress ともにすべて許可される(デフォルト許可)。NetworkPolicy を 1 つでも適用すると、その Pod は「許可されたもの以外は拒否」に切り替わる。default-deny-all はこれを利用したベースライン。第16回・本回フェーズ 3 で扱った
問 5helm rollback は過去のリビジョンの状態に戻すが、その操作自体が新しいリビジョンとして記録され、リビジョン番号が増える。たとえばリビジョン 3 でリビジョン 1 にロールバックすると、その結果はリビジョン 4 になる。helm history で履歴を確認できる。第17回で扱った
問 6GatewayClass はクラスタスコープのリソースで、-n を付けずに kubectl get gatewayclass で確認する。Namespace スコープなのは Gateway / HTTPRoute / ReferenceGrant の 3 つ。第18回で扱った
問 7×runAsNonRoot: true は「このコンテナは非 root で動くべき」という宣言にすぎず、image を非 root 化する機能ではない。root 前提の image にこれを付けると、kubelet が UID 0 を検知して起動を拒否し CreateContainerConfigError になる。非 root 化には image の Dockerfile 改修が前提。第15回・本回フェーズ 2 で扱った
問 8CKAD は多肢選択式ではなく、ブラウザ上の仮想ターミナルで実際に kubectl を操作して課題を解く Performance-based(実技)形式。120 分で 15〜20 問、合格ライン 66%。本回の総合演習はこの試験形式のシミュレーション
問 9--dry-run=client -o yaml は、リソースをクラスタに適用せず、生成される YAML をクライアント側で出力するだけ。> pod.yaml でファイルに出して編集すれば、YAML を 1 から書くより速い。CKAD で最も多用する速攻テクニック。第6回・第7回で扱った

第1巻まとめ

Kubernetes 実践教科書 第1巻「CKAD アプリケーション開発編」全 19 回を通して、以下を達成しました。

  • 第1部(第1〜4回)でコンテナと Docker の基礎を学び、Dockerfile からアプリケーションイメージをビルドしてレジストリに push できるようになった
  • 第2部(第5〜6回)で Kubernetes の全体像と kind による学習環境、kubectl の基本操作・Observability・デバッグ手法を習得した
  • 第3部(第7〜11回)で Pod・Service・StatefulSet・ConfigMap / Secret・Job / CronJob / DaemonSet という、アプリを構成する基本リソースを習得した
  • 第4部(第12〜14回)で Deployment と 3 Probe・複数のデプロイ戦略・ResourceQuota / LimitRange という、ワークロードを安定運用する機構を習得した
  • 第5部(第15〜16回)で RBAC・SecurityContext・NetworkPolicy という、アプリをセキュアにする機構を習得した
  • 第6部(第17〜19回)で Helm によるパッケージ化・Gateway API + cert-manager による HTTPS 公開を習得し、本回の総合演習で 14 機構を 1 回の通し演習で連携させた
  • 本回の総合演習で、14 機構を「基盤レイヤ → アプリレイヤ → 公開レイヤ」の 3 フェーズで依存順に組み立て、https://fanclub.local で HTTPS 公開された fanclub-api にアクセスできる状態を達成した
  • CKAD 試験の形式(120 分・Performance-based・15〜20 問・合格ライン 66%)とドメイン別出題比率(D4 が 25% で最大)を理解し、最終チェックリストで自己点検する準備が整った
  • 第1巻完走により CKAD 受験準備が完了し、第2巻(CKA)・第3巻(CKS)へ続く Kubestronaut 5 冠までの道筋を描けるようになった

第1巻はこれで完走です。次は第2巻「CKA クラスタ構築・運用編」で、kubeadm による本番 HA クラスタの自力構築へ進みます。第2巻は本シリーズの別の巻として独立しており、第1巻完走後に取り組む内容です。第1巻で身につけた「アプリを Kubernetes で動かす力」の上に、第2巻で「クラスタそのものを作って運用する力」を積み上げていきます。

シリーズ一覧

Kubernetes 実践教科書 第1巻「CKAD アプリケーション開発編」全 19 回の一覧です。

第1部:コンテナと Docker

第2部:Kubernetes 基礎

第3部:アプリリソース

第4部:ワークロード戦略

第5部:セキュリティ基礎

第6部:パッケージ管理 + HTTPS 公開

第1巻はこの第19回で完走です。第2巻「CKA クラスタ構築・運用編」(全 16 回)と第3巻「CKS 本番運用・SRE 編」(全 13 回)は、本シリーズの別の巻として独立して進行します。第1巻を完走した読者は、第2巻で kubeadm による本番 HA クラスタの構築・運用へ進めます。

広告
kubernetes
スポンサーリンク