インフラエンジニアの羅針盤

インフラエンジニア1〜3年目のための技術ガイド

CKS試験直前対策と暗記総まとめ【第19回】

公開

新卒インフラエンジニア向け「Kubernetes 実践教科書 ③ CKS セキュリティ・ハードニング編」(全19回)の第19回、最終回です。最終回にあたります。第2回から第18回までで、CIS 準拠化からインシデント対応まで一周しました。本回は新しい技術を 1 つも入れません。 積み上げたものを棚卸しし、試験で書ける形へ翻訳し直します。

本回の中心にある事実を、最初に置きます——クラスタに残っているものと、試験で書けなければならないものは、一致しません。 残っているのに試験に出ないものがあり、試験に出るのに残っていないものがあり、そもそも一度も動かせなかったものがあります。積み上げを数えても、それは答案になりません。

第17回の中心は「防御は検知を生まない」でした。第18回は「防御は調査も妨げる」でした。本回はその 3 つ目です——到達状態は、そのままでは答案になりません。 3 回とも「積み上げたものが、そのままでは次の目的に使えない」という同じ形の反転です。

  • ラボ Kubernetes v1.36.3
  • kubectl v1.35.6
  • CKS 試験環境 v1.35(2026-08-03 公式確認)
  • Helm v4.1.4
  • Cilium v1.19.6
  • Falco 0.44.1(chart 9.1.0)
  • OPA Gatekeeper chart 3.23.0(app v3.23.0)
  • Trivy v0.70.0
  • Cosign v3.1.2
  • kubeseal v0.38.4
  • bom v0.7.1
  • Kubesec v2.14.2
  • KubeLinter v0.8.3
  • kube-bench v0.15.6(Control Plane 上で実行・k8s-ops には入っていません)
  • containerd v2.2.6
  • AlmaLinux 10.2
  • 確認日 2026-08-03
目次
  1. 第19回のスコープ・今ここマップ
  2. この回のゴール
    1. 手元の kubectl は、試験と同じ世代になっている
  3. ラボに残っているものと、試験で問われるもの —— 4 つの箱
    1. B と C を分ける理由
    2. 箱を分ける根拠は、すべて実機の事実にある
  4. やってみよう①:クラスタの棚卸し —— 箱 A と箱 B を実機で確定させる
    1. ステップ1:箱 A —— 残っている防御を数える
    2. ステップ2:箱 B —— 残っていない機構を数える
    3. ステップ3:それでも webhook は残っている
    4. ステップ4:箱 D —— 試験に出ないものを確認しておく
  5. CKS 試験の最終確認と 6 ドメインの配点
    1. 6 ドメインの配点
    2. ラボと試験のバージョン差
  6. 試験環境の作法 —— base とタスクごとの SSH ホスト
    1. 開始直後に打つのは 1 行だけ
    2. ラボと試験の道具の差
  7. 参照可能 8 件の使いこなし
    1. CKS で参照できないもの(CKA / CKAD との差)
  8. 時間配分 —— 120 分の 3 パス戦略
    1. 1 パス目で飛ばす判断基準
    2. 全問共通の鉄則(本巻で確立したもの)
    3. 3 パス目の検証コマンド集
  9. 暗記必須コマンド総まとめ① —— 参照できない 10 ツール
    1. 当日カード(20 行)
    2. 厳選版 —— 参照できない 10 ツール(各 4 行 × 10 = 40 行)
    3. 1. Trivy(trivy.dev は 8 件に入っていない)
    4. 2. Cosign(sigstore.dev も github.com/sigstore/cosign も不可)
    5. 3. OPA Gatekeeper(open-policy-agent.github.io は許可 8 サイトに含まれない)
    6. 4. gVisor / containerd 2.x(gvisor.dev も containerd.io も含まれない)
    7. 5. AppArmor(不可。man は可)
    8. 6. SELinux(許可リスト外。man のみ)
    9. 7. kube-bench(許可 8 件のどこにも載っていない)
    10. 8. Kubesec / KubeLinter(kubesec.io も docs.kubelinter.io も不可)
    11. 9. kubeseal / SealedSecrets(許可 8 サイトに含まれない)
    12. 10. ホスト OS 系(第7回の 25 行のうち、参照できるのは最後の 1 行だけ)
    13. 全量版 267 行の索引
  10. 暗記必須コマンド総まとめ② —— 参照できるのに、開かずに打てると差がつくもの
  11. 暗記しないものを決める —— ラボ固有の事情の切り分け(箱 D)
  12. やってみよう②:CKS 形式の模擬問題 6 問
    1. 各問の実行方針
    2. Q5 のスキャンで、本回の主題がもう一度出る
    3. 出題(6 問・合計 52 分)
    4. Q1 の解答例(D1・6 分)
    5. Q2 の解答例(D2・8 分)
    6. Q3 の解答例(D3・8 分)
    7. Q4 の解答例(D4・10 分)
    8. Q5 の解答例(D5・10 分)
    9. Q6 の解答例(D6・10 分)
  13. やってみよう③:書けるかを測る —— 箱 B の弱点リスト
    1. 自己テストの手順
    2. 自己テスト項目(箱 B・4 機構)
    3. 弱点リストのテンプレート
  14. ラボで手を動かせなかった 4 領域と Killer.sh での補完計画(箱 C)
    1. Killer.sh の使い方
  15. 第3巻完走宣言 —— fanclub-api のセキュリティ進化
    1. 到達点
    2. 同時に、外したものも数えた
    3. 三部作の完走
  16. Kubestronaut 5 冠ロードマップ —— CARE を含む
    1. KCSA は第3巻とどれだけ重なるか
    2. CARE —— 維持コストの実際
    3. 残り 2 資格の進め方
  17. まとめ
  18. 理解度チェック(○×形式・全 9 問)
  19. おわりに

第19回のスコープ・今ここマップ

第18回と本回では、読者の頭の使い方が違います。 第18回は「起きている事案に対して、いま何をするか」を考える回でした。本回は「本を閉じたあと、試験当日までに何をするか」を決める回です。手を動かすのは確認と自己テストだけになります。

第3巻 19 回のうち、現在位置は次のとおりです。

第6部 監視・ログ・ランタイムセキュリティ(D6)
    第15回: Falco ランタイム検知(D6)
    第16回: 監査ログ設計・分析 + 攻撃フェーズ特定(D6)
    第17回: 多面的脅威検知の体系 + コンテナ不変性 + アラート連携(D6)

第7部 インシデント対応・総括
    第18回: セキュリティインシデント対応 DAIR 実戦
  ★ 第19回: CKS 試験直前対策 + 第3巻完走宣言 + Kubestronaut への道  ← 今ここ(最終回)

この回のゴール

本回を終えると、次のことができるようになります。到達できたかは記事末の「やってみよう」と「理解度チェック」で確認します。

  • 自分のクラスタを 4 つの箱に棚卸しし、試験対策の時間をどこに使うかを決められる
  • 参照できない 10 ツールのコマンドを、ドキュメントを開かずに打てる
  • base とタスクごとの SSH ホストという CKS 固有の作法に沿って動ける
  • 120 分の中で、配点(D4・D5・D6 で 60%)に沿った 3 パスの進め方を実行できる
  • Kubestronaut 5 冠までの残作業と、CARE による維持コストの下がり方を数字で説明できる

手元の kubectl は、試験と同じ世代になっている

版行に 1 つ、教材になる組み合わせがあります。本ラボの API Server は v1.36.3、k8s-ops の kubectl は v1.35.6 で、1 マイナーずれています。第5回のパッチアップグレードで API Server 側だけが進み、作業端末の kubectl は第2巻で入れたままだからです。

スキューポリシーは暗記対象です。

kubectl は API Server の ±1 マイナーまでサポートされます。本ラボの v1.35.6 / v1.36.3 はその範囲内で、しかも手元の kubectl のほうが CKS 試験環境(v1.35)と同じ世代です。ただしバージョンスキューの公式文書は kubernetes.io/releases/ にあり、許可 8 件のサブパス外なので試験中に開けません第5回で確定済み)。だからこの規則は覚えるしかありません。

ラボに残っているものと、試験で問われるもの —— 4 つの箱

第3巻はここまで 18 回、防御を積み上げてきました。しかし、いまクラスタに残っているものを数えても、それは試験の答案にはなりません。残っているのに試験に出ないものがあり、試験に出るのに残っていないものがあり、そもそも一度も動かせなかったものがあるからです。

本回では、18 回分の到達を次の 4 つの箱に仕分けます。以降の節は、この 4 つを順に処理していく作業になります。

定義該当するもの本回での扱い
A残っていて、試験にも出るNetworkPolicy 7 本 / RBAC 最小化 / apiserver 堅牢化 / SELinux + seccomp / PSS restricted / etcd 暗号化 / SealedSecrets / Cilium L7 / Falco / 監査ログ(exec 限定)やってみよう①で実機から読み返す
B残っていないが、試験に出る(やった上で撤去した)Gatekeeper Constraint / ValidatingAdmissionPolicy / RuntimeClass(gVisor)/ ImagePolicyWebhookやってみよう②③で「書けるか」を測る
C一度も動かせていないが、試験に出る(環境が許さなかった)AppArmor / Kata Containers / Istio mTLS / Cilium mutual authentication(+ IPsec・toFQDNs は記法復習)Killer.sh での補完計画を立てる
D残っているが、試験に出ないLonghorn / MetalLB / ArgoCD / Traefik / Gateway API / Helm / Squid プロキシ / k8s-registry / jq / falcosidekick・Alertmanager・Loki の設定キー除外リストとして明示する
18 回分の到達を 4 つの箱に仕分けた図。残っていて試験にも出る箱 A、撤去したが試験に出る箱 B、一度も動かせず試験に出る箱 C、残っているが試験に出ない箱 D の 4 つに分け、B と C は同じ外枠の中で上下に並べて「残っていない・試験に出る」でも中身が違うことを示している
図1:18 回分の到達を仕分けた 4 つの箱(A / B / C / D)

図にすると、B と C が同じ外枠の中で分かれていることが見えます。どちらも「残っていないのに試験に出る」ですが、B は手順が本文に残っており、C は残っていません。この違いが、対策の立て方を分けます。

B と C を分ける理由

この 2 つは「試験に出るのにクラスタに無い」という点では同じですが、補完の方法がまったく違います。 C は「やれなかった」ので、外部環境で手を動かすしかありません。B は「やった上で外した」ので、手順は本文に残っており、読者は各回に戻れば読み返せます。同じ表に混ぜると、対策の立て方を間違えます。

箱を分ける根拠は、すべて実機の事実にある

  1. 試験に出るのに、ラボに残っていない機構が 4 つあります。 次節のやってみよう①で、4 つとも空であることを実測します
  2. Gatekeeper の webhook だけは残り続けています。 ValidatingWebhookConfiguration は 5 件あり、そのうち 1 件が gatekeeper-validating-webhook-configuration(webhook 2 件)です。しかし Constraint は 0 本で、何も止めていません
  3. ラボにあって試験に無いものが多くあります。 Helm リリースは 13 本残っていますが、helm.sh/docs は CKS では参照すらできませんhelm コマンドそのものが出題範囲外です
  4. 試験にあってラボに無いものもあります。 試験ホストには alias k=kubectl と Bash 補完、yq がプリインストールされていますが、本ラボの k8s-ops には k エイリアスも yq も入っていません。逆に、ラボにあって試験に無い jq を本文で多用してきました(第4回で明記)

本回の中心メッセージ: 積み上げを数えるのではなく、棚卸しして翻訳します。

「第3巻で何を入れたか」を並べても、試験の設問には答えられません。4 つの箱のどれに入るかで、やるべきことが変わります。

やってみよう①:クラスタの棚卸し —— 箱 A と箱 B を実機で確定させる

自分のクラスタに何が残り、何が残っていないかを実測します。本演習で使うのは読み取りコマンドだけです。 applydeletelabel も打ちません——第3巻はここで完結するため、状態を変えずに終えます。

ステップ1:箱 A —— 残っている防御を数える

実行コマンド(k8s-ops 上):

$ kubectl get networkpolicy -n fanclub --no-headers | wc -l
$ kubectl get ciliumnetworkpolicy -n fanclub --no-headers
$ kubectl get ns fanclub -o jsonpath='{.metadata.labels}'
$ kubectl get sa,role,rolebinding -n fanclub --no-headers
$ kubectl get sealedsecret -n fanclub

実行結果:

7
fanclub-backend-l7   5d14h   True
{"app.kubernetes.io/managed-by":"Helm","kubernetes.io/metadata.name":"fanclub","pod-security.kubernetes.io/audit":"restricted","pod-security.kubernetes.io/audit-version":"latest","pod-security.kubernetes.io/enforce":"restricted","pod-security.kubernetes.io/enforce-version":"latest","pod-security.kubernetes.io/warn":"restricted","pod-security.kubernetes.io/warn-version":"latest"}
serviceaccount/default           8d
serviceaccount/fanclub-backend   8d
role.rbac.authorization.k8s.io/fanclub-backend-reader   2026-07-25T18:03:05Z
rolebinding.rbac.authorization.k8s.io/fanclub-backend-reader-binding   Role/fanclub-backend-reader   8d
NAME                STATUS   SYNCED   AGE
fanclub-db-secret            True     6d17h

回ごとの成果を、確認コマンドと期待値の形で並べます。この表は「自分のクラスタが第3巻の到達状態にあるか」の点検表としてそのまま使えます。

機構確認コマンド期待値
第3回NetworkPolicykubectl get netpol -n fanclub7 本
第4回RBAC 最小化kubectl get sa fanclub-backend -n fanclubSA + Role fanclub-backend-reader + RoleBinding
第5回apiserver 堅牢化kubectl get pod -n kube-system kube-apiserver-k8s-cp-01 -o jsonpath='{.spec.containers[0].command}'--profiling=false / --authentication-config / --kubelet-certificate-authority / --service-account-max-token-expiration=24h
第6回SELinux / seccompgetenforce(5 台)/ Pod の seccompProfileEnforcing / RuntimeDefault
第8回PSSkubectl get ns fanclub --show-labelsenforce / audit / warn すべて restricted
第9回etcd 暗号化apiserver の --encryption-provider-configsecretbox / 鍵名 key3
第9回SealedSecretskubectl get sealedsecret -n fanclubfanclub-db-secret(SYNCED=True)
第11回Cilium L7kubectl get cnp -n fanclubfanclub-backend-l7(VALID=True)
第15回第17回Falcokubectl get ds -n falco5/5 Running
第18回監査ログapiserver の --audit-policy-fileaudit-policy-exec.yamlexec 限定 Metadata・常設)

第17回で数えた「本ラボの穴 3 つ」は、いま 2 つに減っています。第17回は「Users 面が空(第16回で監査ログを撤去した)/ Gatekeeper は Constraint 0 本 / 署名検証は第14回で撤去」と書きましたが、第18回の Postmortem で監査ログを常設したので Users 面は塞がりました。第17回の記述が誤っていたのではなく、その後の作業で 1 つ埋まったという経過です。

ステップ2:箱 B —— 残っていない機構を数える

実行コマンド(k8s-ops 上):

$ kubectl get constrainttemplates
$ kubectl get validatingadmissionpolicy
$ kubectl get runtimeclass
$ kubectl get pod -n kube-system -l component=kube-apiserver -o jsonpath='{.items[0].spec.containers[0].command}' | tr ',' '\n' | grep -E 'admission-control-config-file|enable-admission-plugins'

実行結果:

No resources found
No resources found
No resources found
"--enable-admission-plugins=NodeRestriction"

4 つとも空です。 Gatekeeper の Constraint と ValidatingAdmissionPolicy(第8回)、RuntimeClass(第10回)、ImagePolicyWebhook(第14回)は、いずれも演習で入れたあと、同じ回のうちにクラスタから消えています。

ただし、消えた理由は 4 つとも同じではありません。 ここを一括りにすると、第3巻が積み上げてきた設計判断が失われます。

機構消えた理由性質
Gatekeeper Constraint第8回演習の後片付け。 設計判断として棄却したのではありません。第8回本文は「本番ではこの演習をそのまま使えます。違うのは『後片付けをしない』ことだけです」と書いています片付けただけ
ValidatingAdmissionPolicy第8回同上片付けただけ
RuntimeClass / gVisor第10回代償を数えた上での不採用。 ①同じノードの通常 Pod も MAC 拘束を失う ②可用性が下がる。Workload Node が 2 台しかなく「サンドボックス専用ノード」を作れないのが根本理由です判断して外した
ImagePolicyWebhook第14回「適用 → 証拠 → 代償 → 撤去」で完結。 理由を 3 つ挙げて外しました。第14回本文は「本巻で 3 例目です」(第8回の 2 つ / 第10回 gVisor / 第14回)と書いています判断して外した

同じ「箱 B」でも、持ち帰り方が違います。

第8回の 2 つは「本番なら片付けない」もの——試験でも本番でも書けるべきです。第10回第14回の 2 つは「本番でも入れない」と判断したもの——試験では書けるべきですが、入れない判断ごと覚えます。本回の結論「足した数ではなく、判断した数が成果である」は、この区別があって初めて成立します。

ステップ3:それでも webhook は残っている

実行コマンド(k8s-ops 上):

$ kubectl get validatingwebhookconfiguration --no-headers
$ kubectl get validatingwebhookconfiguration gatekeeper-validating-webhook-configuration -o jsonpath='{range .webhooks[*]}{.name}{"\n"}{end}'

実行結果:

cert-manager-webhook                          1     8d
gatekeeper-validating-webhook-configuration   2     5d14h
kube-prometheus-stack-admission               2     8d
longhorn-webhook-validator                    1     8d
metallb-webhook-configuration                 6     8d
validation.gatekeeper.sh
check-ignore-label.gatekeeper.sh

Gatekeeper 本体と webhook 2 件は残っています。 しかし判断させるルール(Constraint)が 0 本なので、何も止めません

validatingwebhookconfiguration に名前が並んでいることは、ポリシーが効いていることを意味しません。

効いているかは constrainttemplates / constraints -A / validatingadmissionpolicy の 3 点で確かめます。第17回で書いたこの点検が、そのまま試験の設問形式(「このクラスタでポリシーが効いているか調べよ」)になります。

ただし、この 3 点は同じ形の答えを返しません。 本ラボで打つと、返り方が 2 種類に分かれます。

コマンド本ラボでの返り
kubectl get constrainttemplatesNo resources found型はあるが 0 本
kubectl get validatingadmissionpolicyNo resources found型はあるが 0 本
kubectl get constraints -Aerror: the server doesn't have a resource type "constraints"

constraintsConstraintTemplate が生やす CRD です。テンプレートが 0 本なら、そもそも型が存在しません。エラーが返ることは失敗ではなく、「1 本も定義されていない」という答えそのものです。試験で「ポリシーが効いているか調べよ」と言われたとき、このエラーを見て手が止まるのが、最もありがちな時間の使い方になります。

ステップ4:箱 D —— 試験に出ないものを確認しておく

実行コマンド(k8s-ops 上):

$ helm list -A --no-headers | wc -l

実行結果:

13

13 リリースのうち CKS の出題対象は Cilium / Falco / Gatekeeper / SealedSecrets の 4 つだけです。残る 9 つ(fanclub 自身 / Traefik / cert-manager / ArgoCD / kube-prometheus-stack / Loki / Fluent Bit / Longhorn / MetalLB)は試験に出ません。helm コマンドそのものも CKS の出題範囲外です(第15回第17回で明記)。

この 4 対 9 という比が、箱 D の実感になります。18 回かけて育てたものの大半は、試験には出ません。 三部作の題材として最後まで動かし続けた fanclub ですら、CKS の設問に現れることはありません。それでも積み上げてきたのは、試験に受かることと、クラスタを守れることが同じではないからです。

chart の appVersion で判断してはいけません。

fanclub リリースの chart appVersion0.3.2 のままですが、実際に動いているイメージは fanclub-backend:0.4.1 です。chart と実物の管理者が別だからです——appVersion 0.3.2 は第1巻で helm install したときの値のまま更新されておらず、いま Deployment を管理しているのは ArgoCD(Kustomize の overlays/prodです。第12回base/backend-deployment.yamlimage:0.4.0 へ、第13回0.4.1 へ書き換えて push した結果が、いま動いているものです。chart の値は、その経路を 1 度も通っていません。 宣言値と実物がずれる典型例で、これも本回の主題と同じ形です。試験でも実務でも kubectl get pod -o jsonpath='{..image}'実物を見るのが正しい手順になります。

CKS 試験の最終確認と 6 ドメインの配点

ここからは箱の話をいったん離れ、試験そのものの事実を確定させます。以下はすべて 2026-08-03 に公式ページから取得した値です。

項目内容
試験形式オンライン・プロクター付き・パフォーマンスベース(コマンドライン)
試験環境の Kubernetesv1.35(”The CKS environment is currently running Kubernetes v1.35.”)
試験時間120 分
合格点67%(CKA・CKAD は 66%)
前提条件CKA 合格歴が必須(受験前に合格していること。有効期限内でなくてよい)
再受験1 回(登録に 2 attempts が含まれる)
模擬試験Killer.sh 2 回分・各 36 時間
有効期限2 年
タスク数15〜20 タスク(”The exams consist of 15-20 performance-based tasks.”)

「15〜20 タスク」と「17 問」は別の値です。

試験本体は 15〜20 タスク(”The exams consist of 15-20 performance-based tasks.”)。一方 CKS 製品ページの “Each simulator session contains 17 questions.” は Killer.sh の設問数であって試験本体の値ではありません。数字が近いので混同しやすいところです。

この 15〜20 という数字は、時間配分を決めるうえで役に立ちます。120 分 ÷ 15〜20 タスク = 1 タスクあたり 6〜8 分。本回で扱う模擬問題の想定時間(6〜10 分)も、後述する 3 パス戦略も、この幅を土台にしています。1 タスクに 15 分かけている時点で、他のどこかを捨てていることになります。

6 ドメインの配点

ドメイン配点本巻の対応回
D1 Cluster Setup15%第2回第3回
D2 Cluster Hardening15%第4回第5回
D3 System Hardening10%第6回第7回
D4 Minimize Microservice Vulnerabilities20%第8回第9回第10回第11回
D5 Supply Chain Security20%第12回第13回第14回
D6 Monitoring, Logging and Runtime Security20%第15回第16回第17回

D4 + D5 + D6 で 60% です。この比率が、後述する時間配分と暗記の重みづけの根拠になります。

ラボと試験のバージョン差

ラボは v1.36.3、試験は v1.35 で、1 マイナー + パッチの差があります。Linux Foundation は「Kubernetes のリリースから概ね 4〜8 週間で試験環境を最新マイナーへ整合させる」としているので、受験前に公式ページで試験バージョンを確認してください第8回で確認したとおり、pod-security.kubernetes.io/enforce-version: latest の解決先はクラスタのバージョンに引きずられます——ラボで latest と書くと v1.36 の定義、試験環境なら v1.35 の定義になります。

なお cncf/curriculum の CKS カリキュラム文書は CKS_Curriculum v1.34.pdf が最新です(CKA / CKAD は v1.35)。これはドメイン定義の版であって、試験環境のバージョンとは別物です。混同しないでください。

試験環境の作法 —— base とタスクごとの SSH ホスト

CKA / CKAD 経験者が最も戸惑うのがここです。CKS は「タスクごとに指定される SSH ホスト上で作業する」方式で、context を切り替えて回る CKA / CKAD の作法とは違います。以下は Important Instructions: CKS に明記されている作法です。

#作法補足
1すべてのタスクは、タスクごとに指定される SSH ホスト上で実施するCKA / CKAD との最大の違いです
2base という hostname のベースシステムにはツールが入っていない“does not have any of the above tools pre-installed”
3各タスクを終えたら base ノードへ戻る戻り忘れると、次のタスクを別ホストで解き始めます
4多段 SSH は使えない(nested SSH connections)本ラボは k8s-ops から各ノードへ多段しているので逆です
5base ノードを再起動してはいけない“Rebooting the base system will NOT restart your exam environment.” 再起動しても試験環境は戻りません
6権限昇格は sudo -i または sudo本ラボと同じです
7プリインストール済み: kubectlk エイリアス + Bash 補完)/ yq / curl / wget / man + man pagesjq は一覧にありません第4回で確認済み)
8コピー / ペーストは Linux ターミナル内では Ctrl+Shift+C / Ctrl+Shift+Vリモートデスクトップアプリ側では Ctrl+C / Ctrl+V です
9単語削除は Ctrl+Alt+W を使う“Use Ctrl+Alt+W instead of Ctrl+W.” Ctrl+W は Chrome のタブを閉じます
CKS 試験環境の構造と本ラボの対比図。上段は base からタスクごとの SSH ホストへ入り、タスクを終えるたびに base へ戻る流れと、多段 SSH が不可であることを示す。下段は本ラボが k8s-ops から各ノードへ多段 SSH している構成で、試験の作法とは正反対であることを対比している
図2:CKS 試験環境の作法と本ラボの構成の対比

本ラボの作法は、試験とほぼ正反対です。 本書ではここまで k8s-ops から各ノードへ多段 SSH して作業してきましたが、試験ではそれができません。逆に、試験ホストには k エイリアスと yq がありますが、k8s-ops にはどちらも入っていません手が覚えた動きを、そのまま持ち込めないということです。

画面分割については公式に記載がありません。

2026-08-03 時点の Important Instructions: CKS に tmux の語はありません。ターミナルの多重化ツールが使えるなら便利ですが、使える前提で計画を立てないでください

開始直後に打つのは 1 行だけ

暗記カード(試験ホスト上):

export do="--dry-run=client -o yaml"
  • alias k=kubectl は打つ必要がありません。 設定済みです。本書ラボの k8s-ops には入っていないので、そこは差分になります
  • export do=第9回で導入し、第6回第8回第14回第15回で繰り返し推奨した時短です。全ドメインで効きます
  • yq が使えるので、既存マニフェストへのフィールド追加はエディタを開かずに済ませられます(第4回で提示済み)
  • kubectl config use-context は打ちません。 CKS はタスクごとに SSH ホストが指定される方式で、公式の指示にも context 切り替えの記載はありません

最後の 1 点は、本回の主題そのものの実例です。CKA で身についた手が、CKS の答案に無意識に混ざります。 「試験の最初にやること」として体が覚えている動作ほど、疑われないまま持ち込まれます。

ラボと試験の道具の差

道具本書ラボ(k8s-ops)CKS 試験ホスト
kubectlあり(v1.35.6あり
alias k=kubectl + Bash 補完無い.bashrc は AlmaLinux 既定のまま)あり
yq無いあり
jqあり公式のプリインストール一覧に無い
manありあり第7回の 25 行はここに頼ります)
helmあり(v4.1.4)出題範囲外
kube-benchk8s-ops には無い(Control Plane 上で実行)タスクで指定されたホスト上
プロキシ(--noproxy '*' が要る)あり無い
プライベートレジストリ(HTTP)あり無い(TLS)

参照可能 8 件の使いこなし

第5回で立てた判断基準を、そのまま持ち込みます。

参照可否は、製品名や重要度では決まりません。そのページが許可された URL のサブパスにあるかだけで決まります。

crictl は「Kubernetes 本体ではない道具」ですが kubernetes.io/docs/tasks/debug/debug-cluster/crictl/ にページがあるので参照できます。逆にバージョンスキューは公式の重要文書ですが kubernetes.io/releases/ にあるので開けません。

この 8 件には第1回で一度触れ、第12回以降は各回の文脈で必要な行だけを再掲してきました。全量の正本は下の表です。 各回の再掲と食い違って見えたときは、こちらを取ってください。

#許可 URL主な用途本巻での出典回
1https://kubernetes.io/docs/NetworkPolicy / RBAC / PSS / VAP / Audit Policy / seccomp / AppArmor 記法 / RuntimeClass / EncryptionConfiguration / エフェメラルコンテナほぼ全回
2https://kubernetes.io/blog/機能解説——
3https://falco.org/docs/Falco の rule / macro / list・フィールド名・priority第15回第17回
4https://kubernetes-sigs.github.io/bom/cli-reference/bom generate / bom document outlinecli-reference/ 配下のみ第14回
5https://etcd.io/docs/etcdctl のオプション第9回
6https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/Ingress(この配下のみ第3回
7https://docs.cilium.io/en/stableCiliumNetworkPolicy / 暗号化(/en/stable 配下のみ第11回
8https://istio.io/latest/docs/PeerAuthentication の mTLS第11回

加えて、ターミナル上の試験指示ディストリビューション同梱ドキュメント/usr/share 配下 = man)・ディストリビューションのパッケージ(既定に無ければ導入も可)が許可されています。検索については、kubernetes.io/docs/ の検索機能は使用可ですが、外部検索結果を開くのは不可です。

CKS で参照できないもの(CKA / CKAD との差)

URLCKA / CKADCKS
https://helm.sh/docs/不可
https://gateway-api.sigs.k8s.io/可(CKA のみ)不可
https://github.com/…不可不可
https://kubernetes.io/releases/不可(/docs/ のサブパス外)不可

本ラボは Gateway API と cert-manager で HTTPS を出していますが、gateway-api.sigs.k8s.io は CKS では参照できません。試験で TLS を問われたら、標準 Ingressspec.tls + kubernetes.io/tls Secret で解答します。kubectl create secret tls は最頻出です(第3回)。

istio.io/latest/docs/ は参照できます。

許可 8 件の 8 番目に入っており、CKA / CKAD では許可されていない CKS 固有の追加です。ただし試験時間内に検索している余裕はありません。 PeerAuthenticationmtls.mode の 3 値(STRICT / PERMISSIVE / DISABLE)は、開かずに書けるようにしておきます。

サブパス限定の 3 件は URL ごと覚えます第13回で確定)。docs.cilium.io/en/stable は許可されていても、docs.cilium.io のトップから辿ることはできません。同様に bom は cli-reference/ 配下、ingress-nginx は user-guide/nginx-configuration/ 配下だけです。

時間配分 —— 120 分の 3 パス戦略

時間配分は公式に示されていません。

Linux Foundation が公表しているのは配点比率だけです。以下の時間の数字はすべて本書の目安であり、公式の推奨ではありません。

パス目安やること
1 パス目〜40 分程度全問に目を通し、手が動くものだけ即答する。配点表示を見て D4 / D5 / D6 を優先する。詰まったら 3 分で飛ばす
2 パス目〜100 分程度残りを腰を据えて解く。static Pod の編集・Audit Policy・Gatekeeper / VAP など、手数の多いもの
3 パス目残り検証だけを行う。作ったものが本当に効いているかを get -o yaml / describe / auth can-i で確かめる

1 パス目で飛ばす判断基準

  • クラスタを再起動させる操作を含む問題(apiserver / kubelet の再起動待ちが入る)は 2 パス目へ回す
  • ノードを跨ぐ問題base からの SSH 先が複数)は移動コストが高いので後回しにする
  • 3 分打っても手が止まる問題は、その時点の知識では解けない。次へ行く

全問共通の鉄則(本巻で確立したもの)

  1. static Pod は編集前に必ずバックアップします第2回で確立。/etc/kubernetes/manifests/へ置きます)
  2. ファイル参照フラグは 3 点セットです(フラグ + volumeMounts + volumes)。第2回で原則を立て、第5回第9回第14回第16回第18回5 回組んだ型です(ログ出力先だけは readOnly を付けません——第16回・第18回がその例です)
  3. API が落ちている間の真実は crictl ps だけです第2回第5回
  4. HA では全 Control Plane に同じ設定を入れます第9回第14回第16回第18回)。1 台だけだと LB の振り分け次第で結果が変わります
  5. --dry-run=server は API に届き、--dry-run=client は届きません第8回)。効くかどうかを試すなら server です
  6. 成功表示は成功を意味しません第8回第9回第10回第15回第17回第18回6 回出た型)。apply が通っても status を見ます。helm upgrade が失敗しても rollout status は成功と言い(第15回)、送信が 200 でも受け皿が null なら誰にも届きません(第17回)

3 パス目の検証コマンド集

対象検証コマンド
RBACkubectl auth can-i <verb> <res> --as=system:serviceaccount:<ns>:<sa> -n <ns>
NetworkPolicykubectl get netpol <name> -n <ns> -o yamlAND / OR は YAML でしか判別できません第3回
PSAkubectl label ns <ns> pod-security.kubernetes.io/enforce=<level> --dry-run=server
VAPkubectl get validatingadmissionpolicy <name> -o jsonpath='{.status.typeChecking}'
Gatekeeperkubectl get <ConstraintKind> <name> -o jsonpath='{.status.byPod[0].enforced}'
etcd 暗号化etcdctl … get /registry/secrets/<ns>/<name> | hexdump -C | tail -8
Audit Policyapiserver Pod の起動確認 + audit*.log の grep
Falcoログの Loading rules from:schema validation: ok

暗記必須コマンド総まとめ① —— 参照できない 10 ツール

第2回から第18回までの「暗記必須コマンド」は、合わせて 267 行あります。全部を並べても読み切れません。ここでは 3 層に分けて渡します。

分量中身本記事での位置
当日カード20 行試験当日に実際に打つもの本節の冒頭
厳選版60 行参照できない 10 ツール(40 行)+ 引けるが開かずに打ちたいもの(20 行本節 + 次節
全量版267 行記事には載せません。 各回の「暗記必須コマンド」節へ戻って回収します本節末尾の索引表

整理の単位は回ではなくツールです。回単位で覚えると、試験中に「これは何回目でやったか」を思い出す手順が挟まります。配点は掲載順の根拠としてのみ使います。

当日カード(20 行)

これは「試験当日に打つもの」のリストであって、「本番クラスタに打ってよいもの」のリストではありません。

とくに kubectl label ns … pod-security.kubernetes.io/enforce=restricted は、試験では即打ちでよいが、本番では既存 Pod を時限爆弾化します——ラベルを付けた時点で動いている Pod は落ちず、次に作り直されたときに落ちます第8回で実測)。本番では次節の --dry-run=server で数えてから貼ります。

暗記カード(試験開始から終了まで、実際に手が打つもの):

export do="--dry-run=client -o yaml"
kubectl auth can-i <verb> <res> --as=system:serviceaccount:<ns>:<sa> -n <ns>
kubectl create secret tls <name> --cert=tls.crt --key=tls.key -n <ns>
kubectl label ns <ns> pod-security.kubernetes.io/enforce=restricted pod-security.kubernetes.io/enforce-version=latest
kubectl explain pod.spec.securityContext --recursive
kubectl get secrets --all-namespaces -o json | kubectl replace -f -
cp /etc/kubernetes/manifests/kube-apiserver.yaml /root/kube-apiserver.yaml.bak
crictl ps | grep kube-apiserver
kube-bench run --targets master
trivy image --severity HIGH,CRITICAL --exit-code 1 <image>
cosign generate-key-pair
cosign sign --key cosign.key <img>@sha256:<digest>
cosign verify --key cosign.pub <img>
bom generate --image <img> --output sbom.json --format json
kubesec scan <file>
kube-linter lint <path>
grep Seccomp /proc/1/status
ausearch --input-logs -m AVC -ts recent
kubectl logs -n <ns> -l <label> -c falco
kubectl debug <pod> -it --image=<img> --target=<container> --profile=restricted

この 20 行は、各回の本文が「試験で打つのはこの形だけ」と明示した行を集約したものです。第14回「試験で打つのは、次の 3 行だけです」、第15回「試験で打つのは、次の形だけです」、第12回kubectl explain を引く手を覚える」——といった記述が出どころです。

cosign signcosign verify で形が違うのには理由があります。

署名はダイジェスト指定<img>@sha256:…)、検証はタグ形<img>)です。署名は「どのイメージに署名したか」が一意でなければ意味を失いますが(タグは後から差し替えられます)、検証はタグから解決してよい——差し替えられていれば署名が見つからず失敗するので、それ自体が検出になるからです。第14回の「試験で打つ 3 行」は 3 行とも簡略形(<image>)で示しました。あれは打鍵の形を覚えるための簡略形で、署名対象の固定はダイジェストで行います。

厳選版 —— 参照できない 10 ツール(各 4 行 × 10 = 40 行)

掲載順は配点順ではなく、「引けないから覚えるしかない」度合いの順です。各ツールをちょうど 4 行に絞ります。網羅ではなく、試験で手が動く最小セットにするためです。4 行に収まらないツールについては、その 4 行を選ぶ判断そのものが中身になります。

1. Trivy(trivy.dev は 8 件に入っていない)

trivy image --severity HIGH,CRITICAL --exit-code 1 <image>
trivy fs --severity HIGH,CRITICAL <dir>
trivy config <dir>
trivy k8s --report summary

よくある取り違え: --severity HIGH, CRITICALスペースを入れる(入れません)。--exit-code 1付けずに「ゲートした」と思う(付けないと落ちません)。CI で使うのは trivy fs であって rootfs ではありません。--ignore-unfixed は「無視」ではなく「後回し」です。

2. Cosign(sigstore.devgithub.com/sigstore/cosign も不可)

cosign generate-key-pair
cosign sign --key cosign.key <img>@sha256:<digest>
cosign verify --key cosign.pub <img>
cosign tree <img>

よくある取り違え: sign をタグ指定でやる(ダイジェスト指定が本来)。cosign attach sbom は DEPRECATED で、SBOM の添付は cosign attest --predicate <sbom> --type spdxjson --key cosign.key <img> です。

3. OPA Gatekeeper(open-policy-agent.github.io は許可 8 サイトに含まれない)

apiVersion: templates.gatekeeper.sh/v1
spec.crd.spec.names.kind: <ConstraintKind>
apiVersion: constraints.gatekeeper.sh/v1beta1
kubectl get <ConstraintKind> <name> -o jsonpath='{.status.byPod[0].enforced}'

よくある取り違え: Rego v1 記法で書くimport rego.v1bad import で ingest エラーになり、apply は通るのに効きません)。Constraint の kind:テンプレート名にしてしまう(テンプレートが生やした CRD 名を書きます)。違反の確認は {.status.violations} です。

4. gVisor / containerd 2.x(gvisor.devcontainerd.io も含まれない)

head -1 /etc/containerd/config.toml
[plugins.'io.containerd.cri.v1.runtime'.containerd.runtimes.runsc]
runtime_type = 'io.containerd.runsc.v1'
containerd config dump | grep -A 6 runtimes.runsc

よくある取り違え: RuntimeClassnode.k8s.io/v1 / handler: runsc)自体は kubernetes.io/docs で引けますが、containerd 2.x(version = 3)のテーブル名はどこにも載っていません。版を head -1 で確認してから書きます。サンドボックスで動いているかの判定は uname -r4.19.0-gvisor を返すかどうかです。

5. AppArmor(不可。man は可)

cat /sys/kernel/security/lsm
apparmor_parser -q /etc/apparmor.d/<profile>
aa-status
cat /sys/kernel/security/apparmor/profiles

よくある取り違え: 記法(securityContext.appArmorProfile)は kubernetes.io/docs で引けるので、暗記対象はロード側の 4 行です。localhostProfile に書くのはファイルパスではなくプロファイル名——seccomp の Localhost/var/lib/kubelet/seccomp/ からの相対パス)とは非対称です。

6. SELinux(許可リスト外。man のみ)

getenforce
semanage fcontext -a -t container_file_t '<path>(/.*)?'
restorecon -Rv <path>
ausearch --input-logs -m AVC -ts recent

よくある取り違え: chcon -t container_file_tその場しのぎで、restorecon やラベル再設定で戻ります。恒久化は semanage fcontext + restorecon の 2 段です。拒否が複数隠れているときは semodule -DBdontaudit を外し、調査が終わったら semodule -B で必ず戻します。真偽値の恒久化は setsebool -P、ラベル確認は ls -Z / ps -eZ です。

7. kube-bench(許可 8 件のどこにも載っていない)

export KUBECONFIG=/etc/kubernetes/admin.conf
kube-bench run --targets master
kube-bench run --targets node --check 1.2.15 --include-test-output
kube-bench run --benchmark cis-1.12 --group 1.2

よくある取り違え: root で打つと KUBECONFIG が無く、判定がごっそり WARN になります。1 行目を忘れないでください。--targets には master / node / etcd / controlplane / policies が指定できます。

8. Kubesec / KubeLinter(kubesec.iodocs.kubelinter.io も不可)

kubesec scan <file>
kubesec scan <file> --exit-code 2
kube-linter lint <path>
kube-linter checks list

よくある取り違え: 減点幅を覚えていないと、どこから直すかを決められません。-30privileged / SYS_ADMIN)、-9(host 系・docker.sock/proc)、-7allowPrivilegeEscalation: true)の順です。KubeLinter の設定は .kube-linter.yamlchecks.include / exclude / addAllBuiltIn: true です。

9. kubeseal / SealedSecrets(許可 8 サイトに含まれない)

kubeseal --fetch-cert
kubectl create secret generic <name> -n <ns> --from-literal=<key>=<value> --dry-run=client -o yaml | kubeseal --format yaml
kubeseal --cert <cert> --format yaml --scope namespace-wide
kubeseal --re-encrypt --format yaml

よくある取り違え: --format yaml を忘れる(既定は json です)。--scopestrict(既定)/ namespace-wide / cluster-wide の 3 値で、strict は名前と Namespace の両方に縛られます。復旧は --recovery-unseal --recovery-private-key、コントローラに管理させるアノテーションは sealedsecrets.bitnami.com/managed=true です。

10. ホスト OS 系(第7回の 25 行のうち、参照できるのは最後の 1 行だけ)

systemctl mask <unit>
systemctl list-unit-files --type=service --state=enabled
ss -ltnup
firewall-cmd --permanent --remove-service=<svc> && firewall-cmd --reload

よくある取り違え: disable では依存経由の起動が止まりません。止めるなら mask です。--permanent を付けたら --reload までがワンセットです。SSH は sshd -T(実効値)と sshd -t(構文チェック)の使い分け、sudo は visudo -c -f <file>install -m 0440 -o root -g rootvisudo -c の順、権限の確認は sudo -l -U <user> です。

bom は逆側です。

kubernetes-sigs.github.io/bom/cli-reference/許可 8 件の 4 番なので、bom のフラグは試験中に引けます。第14回が「同じ回で扱う 2 つの道具の、試験中の扱いが正反対」と書いたとおりです。Cosign と混ぜて「両方暗記」と覚えないでください。

全量版 267 行の索引

残る全量は、各回へ戻って回収します。「どの回に何があるか」を思い出せる索引として使ってください。

見出し行数主なツール
第1回 第3巻スコープ + CKA 境界 + 4C/脅威モデル + Kubestronaut暗記必須コマンド(第1回)6kube-bench(取得と実行の形)
第2回 kube-bench + CIS ベンチマーク修復 + バイナリ検証暗記必須コマンド(第2回)14kube-bench / sha256sum --check
第3回 NetworkPolicy 完全設計 + ノードメタデータ保護 + TLS Ingress暗記必須コマンド(第3回)8kubectl create secret tls / NetworkPolicy
第4回 RBAC 監査 + ServiceAccount トークン管理暗記必須コマンド(第4回)12auth can-i --as / SA トークン
第5回 API Server 堅牢化 + kubeadm アップグレード暗記必須コマンド(第5回)18apiserver / CSR / kubeadm upgrade
第6回 カーネル層の強制アクセス制御(SELinux / seccomp / capabilities / AppArmor)暗記必須コマンド(seccomp / SELinux / AppArmor)18seccomp / SELinux / AppArmor
第7回 ホスト OS の攻撃面最小化と権限管理暗記必須コマンド(ホスト OS 堅牢化)25systemd / firewalld / sshd / sudo
第8回 Pod Security Standards + Admission(Gatekeeper / ValidatingAdmissionPolicy)暗記必須コマンド(PSA / AdmissionConfiguration / VAP / Gatekeeper)20PSA / VAP / Gatekeeper
第9回 Secret 管理(etcd 暗号化 / SealedSecrets / ExternalSecrets)暗記必須コマンド(etcd 暗号化 / etcdctl / kubeseal)24EncryptionConfiguration / etcdctl / kubeseal
第10回 マルチテナンシー分離 + サンドボックス(gVisor / RuntimeClass)暗記必須コマンド(RuntimeClass / gVisor / テナント境界)19RuntimeClass / containerd / gVisor
第11回 Cilium 透過暗号化 + L7 NetworkPolicy(Calico → Cilium 移行)暗記必須コマンド(Cilium / L7 / Hubble)14CiliumNetworkPolicy / WireGuard / Hubble
第12回 最小イメージ + 静的解析(Kubesec + KubeLinter)securityContext ゴールデンセットと暗記必須コマンド12Kubesec / KubeLinter / Dockerfile
第13回 Trivy イメージスキャン + CI/CD パイプラインセキュリティ統合暗記必須コマンドと 2 つの設定ファイルの型15Trivy
第14回 SBOM(bom)+ Cosign イメージ署名 + 許可レジストリ制限暗記必須コマンドと、ラボ固有の事情の切り分け16bom / Cosign / ImagePolicyWebhook
第15回 Falco ランタイム検知暗記必須コマンドと、ラボ固有の事情の切り分け17Falco のルール記法
第16回 監査ログ設計・分析 + 攻撃フェーズ特定暗記必須コマンド7Audit Policy / jq
第17回 多面的脅威検知の体系 + コンテナ不変性 + アラート連携暗記必須コマンド11不変性 / evt.res = EROFS / rule_matching
第18回 セキュリティインシデント対応 DAIR 実戦試験ではどう出るか11kubectl debug / objectRef.subresource
合計267

暗記必須コマンド総まとめ② —— 参照できるのに、開かずに打てると差がつくもの

1 問 5〜12 分の試験で、書式を確かめにドキュメントを開くたびに 1〜2 分が消えます。ここに挙げるのは「載っていないから覚える」ではなく、開かずに打てるようにしておくと差がつく項目です(第4回が立てた区分を全 18 回へ広げます)。12 領域から 20 行を選びました。

#領域(参照先)開かずに打てるべきこと
1RBAC(許可 1)--as=system:serviceaccount:<ns>:<sa>コロン 3 個--serviceaccount=<ns>:<sa>コロン 1 個
2RBAC(許可 1)ClusterRole × RoleBinding で Namespace に閉じる/automountServiceAccountToken は SA 単位と Pod 単位があり Pod 側が勝つ
3NetworkPolicy(許可 1)from / toAND / OR の書き分け- を付けるか、同一要素内に並べるか)
4NetworkPolicy(許可 1)podSelector 単独は同一 Namespace のみipBlockexceptcidr に含まれる範囲だけ
5PSS / PSA(許可 1)enforceenforce-version は必ずセット/壊さずに数えるなら --dry-run=server
6PSS / PSA(許可 1)拒否の真因は kubectl describe rs の Eventsget deploy の READY では気づけません)
7VAP(許可 1)validationActions: ["Deny"] を忘れない/object.spec.containers.all(c, …)initContainers とタグ省略を取りこぼさない
8etcd 暗号化(許可 1・5)head -c 32 /dev/urandom | base64--encryption-provider-config-automatic-reload=true
9etcd 暗号化(許可 1・5)鍵ローテーションの順序 ①新鍵を先頭 ②反映 ③全件再暗号化 ④旧鍵削除etcdctl--cacert --cert --key の 3 点
10Audit Policy(許可 1)level 4 種と「最初にマッチしたルールが勝つ」評価順/stage 4 種と omitStages
11Audit Policy(許可 1)kubectl exec の verb は create ではなく get 絞り込みは objectRef.subresource
12Falco(許可 3)- list: / - macro: / - rule:desc / condition / output / priority / tags)/override: condition: replaceappend の差
13Falco(許可 3)rule_matching: first は定義順が勝ち、priority は無関係/阻止された書き込みは evt.res = EROFS
14Cilium(許可 7)toPorts[].rules.http[]method / pathpath は POSIX 正規表現・拒否は 403)/toFQDNsrules.dns とセット
15Cilium(許可 7)encryption.enabled + encryption.type は Helm 値(CiliumNetworkPolicy 側ではありません)/CLI が無い環境では agent Pod の cilium-dbghubble
16Istio許可 8PeerAuthenticationmtls.modeSTRICT / PERMISSIVE / DISABLE)。参照はできますが、検索する時間はありません
17securityContext(許可 1)ゴールデンセット: Pod 側に runAsNonRoot + runAsUser + seccompProfile.type: RuntimeDefault、コンテナ側に readOnlyRootFilesystem + allowPrivilegeEscalation: false + capabilities.drop: ["ALL"]
18securityContext(許可 1)PSS Restricted は readOnlyRootFilesystem を要求しません/迷ったら kubectl explain … --recursive
19kubectl debug(許可 1)--target でプロセス名前空間を共有し /proc/1/root/ から読む/--profile=restrictedrunAsUser を付けません/エフェメラルコンテナは Pod を残したまま取り除けません
20kubeadm upgrade(許可 1)upgrade plan1 台目だけ upgrade apply <ver> → 2 台目以降と Workload は upgrade nodedrain --ignore-daemonsetsuncordon

ディストロ差を覚えます(第5回で確立)。

本書ラボは AlmaLinux(dnf / dnf versionlock)ですが、CKS の試験環境は Debian 系(apt / apt-mark holdです。コマンド文字列をそのまま暗記すると当日に手が止まります。暗記するのは型——「ロックを外す → 版を指定して入れ替える → ロックし直す」——で、対応するコマンドは kubernetes.io/docskubeadm upgrade のページに両方載っています(試験中に参照できます)。

暗記しないものを決める —— ラボ固有の事情の切り分け(箱 D)

ここからは箱 D の処理です。第13回から第18回までが各回で書いてきた「ラボ固有」の表を、1 つに統合します。何を覚えないかを決めるのは、覚えることを決めるのと同じくらい試験対策になります。

使ったもの本ラボで必要だった理由試験では
--noproxy '*' / no_proxyNO_PROXY の 2 行Squid プロキシ環境。cosign は Go 製で大文字の NO_PROXY を優先して読みます不要(試験環境にプロキシはありません)
--db-repository / --java-db-repository / --checks-bundle-repository / --skip-db-updatewhitelist で Trivy DB の既定配布元に出られません不要(素の trivy image が動きます)
--allow-http-registry / --use-signing-config=false / --tlog-upload=false / --insecure-ignore-tlogk8s-registry が HTTP 平文で、TUF・Rekor に到達できません不要(TLS レジストリ・外部到達可)
bom generate --image-archive + docker savebom が HTTP レジストリを読めません不要--image が使えます)
oci://ghcr.io/falcosecurity/charts/falcofalcosecurity.github.io が whitelist にありません不要(Falco は導入済みの前提で出題されます)
fs.inotify.max_user_instances の引き上げ + /etc/sysctl.d/ への永続化wl-01 の Pod 密度が高く、上限 128 に対して 144 使っていました不要
helm upgrade によるルール反映 / ArgoCD・Git 経由の反映本ラボの chart / GitOps 構成不要Helm の運用そのものが出題範囲外helm.sh/docs も参照不可)
自作 ImagePolicyWebhook バックエンド(Python 89 行)Kubernetes は実装を提供しません。仕組みを見せるために自作しました書きません(バックエンドは用意されている前提で、apiserver 側の 3 ファイル整合が問われます)
--profile=restricted --custom=<file>--customfanclub が PSA enforce: restricted で、busybox が root 起動だからです条件次第--profile の存在は覚える価値があります。--custom までは要りません)
jq のパイプk8s-ops に入っているからです不要公式のプリインストール一覧に jq はありませんyq はあります)
falcosidekick / Alertmanager(/api/v2/alerts)/ Loki({source="syscall"})/ Grafana / Hubble の設定キー本ラボの観測基盤出題範囲外第17回が「実務・KCSA」区分と明記。参照できるドキュメントもありません)
Longhorn / MetalLB / ArgoCD / Traefik / Gateway API / cert-manager第2巻から継承した基盤出題範囲外gateway-api.sigs.k8s.io は CKS では参照不可)

ここに並べたものを覚えても、試験の点数は 1 点も増えません。ただし実務では逆になります。

閉じたネットワーク、TLS の無いレジストリ、Pod 密度の高いノードは、現場のほうが普通にあります。試験のために覚えるものと、仕事のために覚えるものは、重なりますが同じではありません。

やってみよう②:CKS 形式の模擬問題 6 問

ここまでで、どこに時間を使うかは決まりました。次は書けるかどうかです。CKS と同じパフォーマンスベース形式で、第2回から第18回までの範囲から 6 問出します。6 問・合計 52 分です。

  • 各問に想定時間があります(CKS は 1 問 5〜12 分が目安)。時計を見ながら解いてください
  • クラスタの状態を変えません。 各問の「実行方針」に従ってください
  • 解答は試験環境で通る形で書きます。ラボ固有のフラグを解答例に混ぜません——前節で「覚えなくてよい」と切り分けた側のものです

各問の実行方針

実行方針理由
Q1--dry-run=server まで(受理されることの確認)fanclub には第3回の NetworkPolicy が 7 本あり、同名で apply すると上書きになります。別 Namespace を作るのも状態変更です
Q2読み取りのみ。 kubectl auth can-i --as=… はそのまま打てます。Deployment の変更は --dry-run=server で止めますfanclubArgoCD 管理下で、手で変えると selfHeal が戻します(第8回「クラスタの正本は Git である」)。戻る前に差分が残ります
Q3既存 Pod で読み取りkubectl execgrep Seccomp /proc/1/status)。検証用 Pod を新規に作りませんfanclubPSA enforce: restricted なので素の busybox は弾かれます。AppArmor の記法は書くだけです(箱 C・このクラスタでは適用できません)
Q4--dry-run=server まで箱 B。VAP は現在 0 本なので、apply すると箱 B が箱 A に変わってしまいます
Q5スキャンは実行できます(読み取りのみ)。bom generatecosign sign は「書くだけ」cosign sign は k8s-registry へ署名タグ(sha256-…)を push する=レジストリの状態が変わります。代わりに第14回で作成済みの署名を cosign verify / cosign tree で読み返してください
Q6既存設定の読み取り第18回audit-policy-exec.yaml を 3 台の Control Plane に常設済みです。すでに動いているものを読んで、解答が成立するかを確かめます

Q5 のスキャンで、本回の主題がもう一度出る

本ラボで素の trivy image を打つと、次のようになります。

実行コマンド(k8s-ops 上):

$ time trivy image --severity HIGH,CRITICAL --exit-code 1 k8s-registry:5000/fanclub-backend:0.4.1

実行結果:

INFO	[vulndb] Need to update DB
INFO	[vulndb] Downloading vulnerability DB...
INFO	[vulndb] Downloading artifact...	repo="mirror.gcr.io/aquasec/trivy-db:2"
FATAL	Fatal error	run error: init error: DB error: failed to download vulnerability DB: OCI artifact error: failed to download vulnerability DB: failed to download artifact from mirror.gcr.io/aquasec/trivy-db:2: OCI repository error: 1 error occurred:
	* Get "https://mirror.gcr.io/v2/": Forbidden

real	0m0.052s

real 0m0.052s——スキャンが始まる前に落ちています。 mirror.gcr.io が alma-proxy の whitelist に無いため、脆弱性 DB を更新できず、初期化の時点で FATAL になります。「Forbidden なので結果が空になる」のではありません。--skip-db-update を足すと、キャッシュ済みの DB で完走します。

この FATAL はいつ打つかで出たり出なかったりします。

Trivy が DB を取りに行くのは、キャッシュの有効期限が切れているときだけです。期限内なら更新を試みないので、mirror.gcr.io にも出ようとせず、素の trivy image がそのまま完走します(実測でも、第13回で DB を取った翌日は完走し、exit code 1 で HIGH / CRITICAL を返しました)。

期限は ~/.cache/trivy/db/metadata.json で確認できます。

$ cat ~/.cache/trivy/db/metadata.json

実行結果(抜粋):

"DownloadedAt":"2026-08-07T04:55:39Z","NextUpdate":"2026-08-08T01:59:50Z"

NextUpdate を過ぎた瞬間から、同じコマンドが FATAL に変わります(脆弱性 DB は 24 時間、Java DB は 3 日周期です)。「昨日は動いたのに今日は落ちる」という壊れ方をするので、--skip-db-update は最初から付けておくのが安全です。 ——これも箱 D の性格そのものです。ラボの事情は、時間が経つと形を変えて出てきます。

これは箱 D の実例です。

試験環境は外へ出られるので --skip-db-update は要りません——つまり覚えなくてよいフラグです。ところが本書のラボで手を動かすには要ります。だから解答例には書かず、ラボで打つ人向けの注記としてだけ添えます。 「解答は試験の形・ラボの事情は別枠」という書き分けそのものが、本回が渡す型です。

出題(6 問・合計 52 分)

#ドメイン想定時間設問(要旨)元の回
Q1D1(15%)6 分Namespace に default-deny-all を敷いたうえで DNS だけを許可し、169.254.169.254/32 への egress が遮断されたままであることを YAML で示すA第3回
Q2D2(15%)8 分ServiceAccount が Secret を読めないことを検証し、その SA を使う Deployment の Pod にトークンをマウントさせない(Pod 単位で)A第4回
Q3D3(10%)8 分seccompProfile: RuntimeDefaultcapabilities.drop: ["ALL"] を適用し、効いていることを確認する。あわせて AppArmor の Localhost プロファイルの記法を書くA + C第6回
Q4D4(20%)10 分latest タグのイメージを持つ Pod を拒否する ValidatingAdmissionPolicy と Binding を作る(validationActions: ["Deny"]B第8回
Q5D5(20%)10 分イメージを HIGH と CRITICAL のみでスキャンし、検出時に終了コード 1 を返す。続いて SBOM を生成し、鍵ペア方式で署名して検証するA第13回第14回
Q6D6(20%)10 分pods/exec / pods/attach / pods/portforward だけMetadata で記録し、それ以外は一切記録しない Audit Policy を書く。適用に必要な 3 点セットも示すA第16回第18回

箱 B からの出題は Q4(VAP)の 1 問だけです。残りは箱 A(Q3 のみ A + C)で、実機に残っているものを題材にしています。箱 B の残り 3 つ(Gatekeeper / RuntimeClass / ImagePolicyWebhook)は、時間を測らない自己テストとして次節で扱います。

Q1 の解答例(D1・6 分)

土台になる default-deny です(全量)。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: <ns>
spec:
  podSelector: {}
  policyTypes:
    - Ingress
    - Egress

DNS だけを開けます(全量)。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-dns
  namespace: <ns>
spec:
  podSelector: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

メタデータ API への egress は、この 2 本だけで遮断されています。 default-deny の下では、明示的に許可したもの以外は通らないからです。「遮断したままであること」を YAML で示すとは、0.0.0.0/0 を許可するポリシーが存在しないことを示すのと同じです。もし外部通信を開ける必要があるなら、次の形で穴を空けます(全量)。

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: egress-external-except-metadata
  namespace: <ns>
spec:
  podSelector:
    matchLabels:
      app: <app>
  policyTypes:
    - Egress
  egress:
    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except:
              - 169.254.169.254/32
採点基準配点の考え方
podSelector: {}policyTypesIngress / Egress の両方があるこれが無いと 0 点(片方だけでは default-deny になりません)
allow-dnsUDP 53 と TCP 53 の両方を開けている必須。TCP を忘れると大きな応答で名前解決が失敗します
namespaceSelectorpodSelector同一リスト要素内にある(AND)必須。- を付けると OR になり kube-system 全体が開きます
メタデータ遮断を「default-deny が残っていること」で説明できている加点。except外部を開ける場合にのみ必要です

よくある減点: egress default-deny を敷いて DNS を開け忘れる(名前解決が壊れ、以降の設問すべてに影響します)。from / to の AND / OR を取り違える。効いているかは kubectl get netpol -o yaml でしか判別できません——describe の整形出力では AND / OR が読み取れません(第3回)。

Q2 の解答例(D2・8 分)

まず読めないことを検証します。

実行コマンド(k8s-ops 上):

$ kubectl auth can-i get secrets --as=system:serviceaccount:fanclub:fanclub-backend -n fanclub
$ kubectl auth can-i list secrets --as=system:serviceaccount:fanclub:fanclub-backend -n fanclub

実行結果:

no
no

次に Pod 単位でトークンのマウントを止めます(全量)。

apiVersion: apps/v1
kind: Deployment
metadata:
  name: <name>
  namespace: <ns>
spec:
  replicas: 3
  selector:
    matchLabels:
      app: <name>
  template:
    metadata:
      labels:
        app: <name>
    spec:
      serviceAccountName: <sa>
      automountServiceAccountToken: false
      containers:
      - name: app
        image: <image>
        ports:
        - containerPort: 8080

受理されることの確認(状態は変えません):

実行コマンド(k8s-ops 上):

$ kubectl apply -f deployment.yaml --dry-run=server

実行結果:

Warning: would violate PodSecurity "restricted:latest": allowPrivilegeEscalation != false (container "app" must set securityContext.allowPrivilegeEscalation=false), unrestricted capabilities (container "app" must set securityContext.capabilities.drop=["ALL"]), runAsNonRoot != true (pod or container "app" must set securityContext.runAsNonRoot=true), seccompProfile (pod or container "app" must set securityContext.seccompProfile.type to "RuntimeDefault" or "Localhost")
deployment.apps/<name> created (server dry run)

設問には関係のない Warning が 1 つ出ます。 この Deployment はトークンのマウントを止めることだけを書いていて securityContext を持たないので、第8回fanclub に敷いた PSA restricted が 4 項目を指摘してきます。それでも created (server dry run) は返ります——enforce は Pod の作成時に効くもので、Deployment の受理そのものは止めないためです。

この Warning は「通った」の一部です。

試験で --dry-run=server を使うとき、Warning と Error を読み分けられないと、通っているものを直しに戻って時間を失います。止まったのかどうかは、最後の行に created があるかどうかで判断します。なお本番でこの Deployment をそのまま適用すると、Deployment は作られるのに Pod が 1 つも起動しない——ReplicaSet の Events にだけ理由が出る状態になります(第8回で実測した形です)。

採点基準配点の考え方
--as=system:serviceaccount:<ns>:<sa> の書式が正しいこれが無いと 0 点(コロン 3 個)
automountServiceAccountToken: falsespec.template.spec 直下にある必須。コンテナ側やメタデータ側では効きません
SA 単位ではなく Pod 単位で書いている設問の指定どおり。Pod 側の指定が SA 側に勝ちます
マウントが消えたことを spec.volumes で確認できる加点(第4回で確認した手順)

よくある減点: --as--serviceaccount の書式を取り違える--as はコロン 3 個、kubectl create 系の --serviceaccount=<ns>:<sa> はコロン 1 個)。SA 単位で patch して、既存 Pod に効いていないのに効いたと誤認する——マウントは Pod 作成時に決まるので、作り直すまで変わりません。

本番では違います。

本ラボの fanclubArgoCD 管理下なので、手で変えても selfHeal が戻します。正本は Git にあります第8回)。試験では直接編集が正解ですが、GitOps 下の本番で同じ手を打つと、差分が残ったまま元へ戻されます。

Q3 の解答例(D3・8 分)

seccomp と capabilities を適用した Pod です(全量)。

apiVersion: v1
kind: Pod
metadata:
  name: hardened
  namespace: <ns>
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
    runAsGroup: 10001
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: <image>
    securityContext:
      readOnlyRootFilesystem: true
      allowPrivilegeEscalation: false
      capabilities:
        drop: ["ALL"]

効いていることの確認です。

実行コマンド(k8s-ops 上):

$ kubectl exec -n fanclub <pod> -c backend -- grep Seccomp /proc/1/status
$ kubectl exec -n fanclub <pod> -c backend -- grep CapEff /proc/1/status

実行結果:

Seccomp:	2
Seccomp_filters:	1
CapEff:	0000000000000000

Seccomp: 2 が filter modeCapEff がすべて 0 なら capability は 1 つも残っていません。あわせて、AppArmor の記法は次のとおりです。

securityContext:
  appArmorProfile:
    type: Localhost
    localhostProfile: k8s-apparmor-example-deny-write

このクラスタでは適用できません。 AlmaLinux のカーネルは LSM に AppArmor を持たず、cat /sys/kernel/security/lsmapparmor が現れないためです。指定した Pod は phase: Failed・STATUS 列 AppArmor で拒否されます(第6回で実測)。

採点基準配点の考え方
seccompProfile.type: RuntimeDefaultPod 側にあるこれが無いと 0 点
capabilities.drop: ["ALL"]コンテナ側にある必須(capabilities はコンテナ単位のフィールドです)
効果確認を /proc/1/status で行っている必須。マニフェストを見せるだけでは「効いている」証拠になりません
AppArmor が type + localhostProfile の 2 行で書けている必須。localhostProfile はパスではなくプロファイル名です

よくある減点: privileged: true があると seccomp が無視されることを見落とす。効果確認を grep Seccomp /proc/1/status でやらず、マニフェストの目視で済ませる。旧アノテーション記法(container.apparmor.security.beta.kubernetes.io/<container>)を新規に書く——新規は新フィールド記法です。

Q4 の解答例(D4・10 分)

ポリシー本体です(全量)。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
  name: disallow-latest-tag
spec:
  failurePolicy: Fail
  matchConstraints:
    resourceRules:
      - apiGroups: [""]
        apiVersions: ["v1"]
        operations: ["CREATE", "UPDATE"]
        resources: ["pods"]
  variables:
    - name: allContainers
      expression: "object.spec.containers + (has(object.spec.initContainers) ? object.spec.initContainers : [])"
  validations:
    - expression: "variables.allContainers.all(c, c.image.split('/')[c.image.split('/').size() - 1].contains(':') && !c.image.endsWith(':latest'))"
      message: "イメージはタグを明示し latest を使わないこと"

適用先を決める Binding です(全量)。

apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
  name: disallow-latest-tag-binding
spec:
  policyName: disallow-latest-tag
  validationActions: ["Deny"]
  matchResources:
    namespaceSelector:
      matchLabels:
        kubernetes.io/metadata.name: <ns>

受理されることの確認(状態は変えません):

実行コマンド(k8s-ops 上):

$ kubectl apply -f vap.yaml -f vap-binding.yaml --dry-run=server
採点基準配点の考え方
validationActions: ["Deny"] が Binding にあるこれが無いと 0 点(ポリシーを書いただけでは何も止まりません)
initContainersvariables で合流させている必須。has(…) ? … : [] を使わないと未定義で式が落ちます
タグ省略nginx)を latest と扱えている必須。split('/') の最終要素に : があるかで判定します
matchConstraintsoperationsUPDATE が入っている加点(作成だけ塞いでも更新で入ります)

よくある減点: validationActions: ["Deny"] を書き忘れる(作っても止まりません)。initContainers を見ていない。タグ省略を latest として扱えていない。型チェックの結果は kubectl get validatingadmissionpolicy <name> -o jsonpath='{.status.typeChecking}' で読めます。

機構の選び方も 1 行で思い出しておきます。第8回の結論は「まず PSS、足りない制約だけ Gatekeeper / CEL」でした。latest タグの禁止は PSS の守備範囲外なので、VAP か Gatekeeper のどちらかになります。同じ要件を Gatekeeper で書く練習は、次節の自己テストで行います。

本番では違います。

試験ではいきなり Deny で正解ですが、本番は Warn / Audit で影響範囲を測ってから Deny にします。いきなり Deny にすると、次に Pod が作り直されたときに落ちる——時限式の障害を仕込むことになります。第8回の PSA で同じ判断を実測しました。

Q5 の解答例(D5・10 分)

暗記カード(試験環境で通る形):

trivy image --severity HIGH,CRITICAL --exit-code 1 <image>
bom generate --image <image> --output sbom.json --format json
bom document outline sbom.json
cosign generate-key-pair
cosign sign --key cosign.key <image>@sha256:<digest>
cosign verify --key cosign.pub <image>

ダイジェストは、署名する前に解決しておきます。

実行コマンド(k8s-ops 上):

$ kubectl get pod -n fanclub -l app=fanclub-backend -o jsonpath='{.items[0].status.containerStatuses[0].imageID}'

実行結果:

k8s-registry:5000/fanclub-backend@sha256:b8113a77a17d7c7ad5ac794cce2edef9b7fa99c474154e7573c29b191529b016
採点基準配点の考え方
--severity HIGH,CRITICALスペース無しこれが無いと 0 点(スペースを入れると引数が割れます)
--exit-code 1 があるこれが無いと 0 点(付けないとゲートになりません)
bom generate--image / --output / --format json の 3 つがある必須
cosign signダイジェスト指定cosign verify がタグ形必須。署名対象はタグでは一意になりません

よくある減点: --severity HIGH, CRITICAL とスペースを入れる--exit-code 1 を付けずに「ゲートした」と思うcosign sign をタグ指定でやる(ダイジェスト指定が本来です)。cosign attach sbom を使う(DEPRECATED なので cosign attest です)。

本ラボで実際に打つ場合の注記です。

上の解答例は試験環境で通る形です。本ラボでスキャンを走らせるときは --skip-db-update が要ります(whitelist の外にある DB を取りに行って FATAL になるため)。cosign signbom generate --image は打たないでください——前者はレジストリに署名タグを増やし、後者は HTTP レジストリを読めません。第14回で作成済みの署名を cosign verify / cosign tree で読み返すだけにとどめます。

Q6 の解答例(D6・10 分)

Audit Policy です(全量)。

apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
  - RequestReceived
rules:
  - level: Metadata
    resources:
      - group: ""
        resources: ["pods/exec", "pods/attach", "pods/portforward"]
  - level: None

kube-apiserver へ渡すフラグです(全量)。

    - --audit-policy-file=/etc/kubernetes/audit/audit-policy-exec.yaml
    - --audit-log-path=/var/log/kubernetes/audit/audit.log
    - --audit-log-maxage=30
    - --audit-log-maxbackup=3
    - --audit-log-maxsize=100

3 点セットの残り 2 つ、volumeMountsvolumes です(全量)。

    volumeMounts:
    - mountPath: /etc/kubernetes/audit
      name: audit-policy
      readOnly: true
    - mountPath: /var/log/kubernetes/audit
      name: audit-log
      readOnly: false
  volumes:
  - hostPath:
      path: /etc/kubernetes/audit
      type: DirectoryOrCreate
    name: audit-policy
  - hostPath:
      path: /var/log/kubernetes/audit
      type: DirectoryOrCreate
    name: audit-log

本ラボでは第18回で 3 台の Control Plane に常設済みです。読み取りで答え合わせができます。

実行コマンド(k8s-cp-01 上・root):

# grep audit /etc/kubernetes/manifests/kube-apiserver.yaml
# cat /etc/kubernetes/audit/audit-policy-exec.yaml
採点基準配点の考え方
- level: None が最後にあるこれが無いと 0 点(「それ以外は記録しない」が実現しません)
resourcespods/exec / pods/attach / pods/portforward の 3 つがある必須。subresource として書きます
3 点セット(フラグ + volumeMounts + volumes)が揃っている1 つ欠けると apiserver が起動しません
ログ出力先が readOnly: false で、ポリシー側が readOnly: true必須(書き込み先を読み取り専用にすると起動できません)

よくある減点: - level: None を最後に置かない(全部記録されて重くなります)。3 点セットのうち volumes を忘れて apiserver が起動しないログ出力先を readOnly でマウントする。記録側の絞り込みで verbcreate だと思い込む——kubectl exec の verb は get で、絞り込みは objectRef.subresource です第18回)。

本番では違います。

このポリシー単体では、調査に足りません。第18回で「Users 面をどこまで塞げたか」を数えたとおり、記録範囲を決めることは、調査可能性を決めることです。試験は「指定どおりに絞る」ことを問いますが、本番では何を捨てたかを把握したうえで絞ります

やってみよう③:書けるかを測る —— 箱 B の弱点リスト

3 本目は、箱 B(やった上で撤去した 4 機構)を対象にした自己テストです。なぜ箱 B かというと、ここが最も抜けやすいからです。

  • 箱 A は実機で読み返せます(やってみよう① でやりました)
  • 箱 C は外部環境でやるしかありません(次節)
  • 箱 B だけが、手順は本に残っているのにクラスタには無い——「読めば思い出せるが、思い出せるかどうかを確かめていない」状態にあります

自己テストの手順

  1. 各項目について、ドキュメントも本書も開かずに YAML / コマンドを書きます(紙でもエディタでもかまいません)
  2. 書き終えてから該当回を開いて答え合わせをします
  3. 書けなかった項目を弱点リストに残します
  4. Killer.sh の 1 回目を使う前に弱点リストを潰します。模擬試験は 2 回分しかないので、潰さずに使うと 1 回分が「知らないことの確認」で終わります

自己テスト項目(箱 B・4 機構)

#機構書けるべきもの撤去した回
1OPA GatekeeperConstraintTemplatetemplates.gatekeeper.sh/v1 / spec.crd.spec.names.kind / targets[].regoRego v0)と Constraintconstraints.gatekeeper.sh/v1beta1 / spec.match.kinds)/効いているかの確認({.status.byPod[0].enforced})/主 webhook(validation.gatekeeper.sh)の failurePolicy は既定で Ignore=fail-open(本ラボの実測では、同じ設定に含まれるもう 1 本 check-ignore-label.gatekeeper.shFail1 つの ValidatingWebhookConfiguration の中で値が揃っているとは限りません第8回
2ValidatingAdmissionPolicy本体 + Binding/validationActions: ["Deny"]/CEL の定石(object.spec.containers.all(c, …) / has(…) ? … : [] / image.split('/'))/{.status.typeChecking}第8回
3RuntimeClass(gVisor)node.k8s.io/v1RuntimeClasshandler: runsc)/scheduling.nodeSelectorPod の nodeSelector と積を取ります)と scheduling.tolerations和を取ります)/overhead.podFixedcontainerd 2.x のテーブル名と runtime_type/サンドボックス判定(uname -r4.19.0-gvisor)/PSA の exemptions.runtimeClasses第10回
4ImagePolicyWebhook--enable-admission-plugins=NodeRestriction,ImagePolicyWebhook既存の値を消さない)/--admission-control-config-file の 3 点セット/AdmissionConfigurationapiserver.config.k8s.io/v1 / plugins[].name: ImagePolicyWebhook / plugins[].configuration.imagePolicy)/defaultAllow: false で fail-closed(公式の記載例は true = fail-open)/allowTTL(秒)・denyTTL(秒)・retryBackoffミリ秒第14回

1 と 2 は続けて出してください。 やってみよう② の Q4 で書いた VAP と同じ制約を、今度は ConstraintTemplate + Constraint で書きます。同じ要件を 2 つの機構で書き分けると、第8回の使い分け(まず PSS、足りない制約だけ Gatekeeper / CEL)が手で思い出せます。Rego v0 のテンプレートをゼロから書くと単独で 8〜10 分かかるため、時間を測らない自己テストとしてここに置いています。

Gatekeeper でありがちな取り違えは 2 つです。Rego v1 記法で書くと、import rego.v1bad import で ingest エラーになり——apply は通るのに効きません。もう 1 つは、Constraint の kind: をテンプレート名にしてしまうことです。書くのは、テンプレートが生やした CRD 名のほうです。

弱点リストのテンプレート

機構書けなかった箇所どの回に戻るか潰した日

「入れて外す」は失敗ではありません。

第10回の gVisor も第14回の ImagePolicyWebhook も、「適用 → 証拠 → 代償 → 撤去」という同じ型で完結させました。第15回が「入れない判断 / 入れて外す判断 / 残す判断」を 1 表にまとめたとおり、何を入れないかも設計の一部です。試験に向けては「書ける」ことが問われ、運用に向けては「入れるかどうか」が問われます——この 2 つは別の問いだと分けて持つのが、本回の到達点になります。

ラボで手を動かせなかった 4 領域と Killer.sh での補完計画(箱 C)

ここに挙げる 4 つは、本書ラボでは一度も動かせませんでした。理由は環境の構造にあり、手順を間違えたからではありません。試験環境では動くので、Killer.sh(Ubuntu ベース)で補完します。

領域ラボで動かせない理由暗記対象補完先
AppArmorAlmaLinux は LSM に AppArmor を搭載しませんcat /sys/kernel/security/lsmapparmor が無く、指定した Pod は phase: Failed / STATUS 列 AppArmor で拒否されます)securityContext.appArmorProfile の 3 パターン(RuntimeDefault / Localhost + localhostProfile / Unconfined)+ 旧アノテーション記法との対比 + apparmor_parser -q / aa-statusKiller.sh で apparmor_parser → Pod 適用まで
Kata Containers/dev/kvm がありません(Hyper-V 上のゲストのため)RuntimeClasshandler: kata と、gVisor(ユーザー空間カーネル)との分離モデルの違いKiller.sh / 座学
Istio の mTLS本書ラボは Cilium を採用しました(Istio を入れない設計判断)PeerAuthenticationmtls.modeSTRICT / PERMISSIVE / DISABLEistio.io/latest/docs/ は許可 8 番なので試験中に参照できます。 ただし検索する時間はありません
Cilium mutual authentication第6回の SELinux(containerd の enable_selinux = true)と排他です。 spire-server が hostPath ソケットへ bind() できず CrashLoopBackOff になりますauthentication: {mode: "required"}authentication.enabled=true の 2 つの Helm 値/認証と暗号化は別物(mutual authentication は暗号化しません)/ポート 4250/tcpKiller.sh / 座学

加えて、記法だけ復習しておく 2 つがあります。

項目記法
IPsec による透過暗号化encryption.type=ipsec + Secret cilium-ipsec-keyskube-system・キー名 keys)。KEYID は 1〜15 で循環します。本ラボは WireGuard を採用したので IPsec は未実行です
toFQDNstoFQDNs: [{matchName: …}] + rules.dnsDNS への egress を rules.dns 付きで許可するのとセット)。動作は確認しましたが、本番運用はしていません

Killer.sh の使い方

  • 試験登録には模擬試験 2 回分・各 36 時間が付きます(2026-08-03 公式確認)。外部リンク: https://killer.sh/
  • 1 回目を使う前に、前節の弱点リストを潰します。 潰さずに使うと、1 回分が「知らないことの確認」で終わります
  • 1 回目で AppArmor を実機で通します。 Ubuntu なので apparmor_parser が動きます。第6回で「非対応ノードでの拒否挙動」までは確認済みなので、残るは適用の 1 手だけです
  • 2 回目は本番 1 週間前に、時間配分の練習として使います

なお、Killer.sh の 1 セッションは 17 問です。これは模擬試験の設問数であって、試験本体の値ではありません。 試験本体は 15〜20 タスクで、数字が近いぶん混同しやすいところです。本番のほうが幅があると覚えておいてください。

第3巻完走宣言 —— fanclub-api のセキュリティ進化

第2巻末の時点で、fanclub-api は「動くクラスタの上で動くアプリ」でした。18 回でそれがどう変わったかを、実機で確認できた状態とあわせて 1 表にします。

加えた防御第19回時点の実機の状態
第2回CIS 準拠化(kube-bench 修復)--profiling=false ×3 / etcd データディレクトリ所有者 / kubelet ファイル権限
第3回NetworkPolicy default-deny 再設計 + メタデータ遮断fanclub に 7 本default-deny-all / allow-dns / allow-db-from-backend ほか)
第4回RBAC 最小化 + SA トークン管理SA fanclub-backend + Role fanclub-backend-reader + RoleBinding
第5回API Server 堅牢化 + パッチアップグレード--authentication-config匿名は /livez /readyz /healthz のみ)/ --profiling=false / --kubelet-certificate-authority / --service-account-max-token-expiration=24hKubernetes は v1.36.3 へ
第6回SELinux Enforcing + seccomp + capabilities5 台すべて Enforcingcontainerd の enable_selinux = true は Workload Node 2 台のみ(Control Plane は意図的に false)。Pod に seccompProfile: RuntimeDefault
第7回ホスト OS の攻撃面最小化enabled service 27 個 / firewalld public zone に 6443・10250・2379-2380・10257・10259/tcp・8472/udp・4240/tcp・51871/udp
第8回PSS Restricted + カスタム制約fanclub は enforce / audit / warn すべて restrictedConstraint と VAP は撤去(箱 B)
第9回etcd 暗号化 + SealedSecrets--encryption-provider-config + automatic-reload=trueprovider は secretbox・鍵名 key3(ローテーション 2 回の証跡)/ SealedSecret fanclub-db-secret(SYNCED=True)
第10回gVisor サンドボックス撤去(箱 B)。 RuntimeClass 0 件
第11回Calico → Cilium 一括移行 + WireGuard + L7Cilium 1.19.6・Cluster Pods 75/75 managed・CiliumNetworkPolicy fanclub-backend-l7(VALID=True)
第12回最小イメージ + 静的解析securityContext ゴールデンセット。backend は runAsUser: 10001 / frontend は 101 / db は 999
第13回Trivy スキャンと修正実行中イメージは fanclub-backend:0.4.1(chart の appVersion 0.3.2 とはずれています)
第14回SBOM + Cosign 署名 + 許可レジストリ制限registry に sha256-… 形式の署名タグが backend / frontend 双方に 1 件ずつImagePolicyWebhook は撤去(箱 B)
第15回Falco 導入Helm REVISION 14・DaemonSet 5/5 Running
第16回監査ログ設計 + 攻撃フェーズ特定(第16回では検証後に撤去しました)
第17回6 面の検知点検 + 不変性 + アラート連携backend / frontend に readOnlyRootFilesystem: truedb には付いていません(PSS Restricted が要求しないため)
第18回インシデント対応(DAIR)+ 監査ログの復活3 台の Control Plane すべてで audit-policy-exec.yaml が有効pods/exec / attach / portforward のみ Metadata・他は - level: None
fanclub-api のセキュリティ進化を第2回から第18回まで時系列の帯で示した図。帯の上に残った防御を実線の緑カードで、入れて外したもの(第8回の Constraint と VAP、第10回の gVisor、第14回の ImagePolicyWebhook)を帯から落ちる赤い破線カードで表し、足した数ではなく判断した数が成果であることを示している
図3:fanclub-api のセキュリティ進化(残した防御と、外した判断)

図で見ると、帯から落ちているものが 3 か所あります。第8回は PSS が帯に残り Constraint と VAP だけが落ち、第14回は SBOM と署名が残り ImagePolicyWebhook だけが落ち、第10回は gVisor が丸ごと落ちています。落ちた部分がそのまま、本回の箱 B です。

到達点

第2巻末の「動くクラスタ」から、CIS 準拠 → ネットワーク最小化 → 権限最小化 → カーネル層の拘束 → ホストの縮小 → Admission → Secret の保存時暗号化 → 通信の暗号化と L7 → イメージの検証 → ランタイム検知 → 監査 → インシデント対応まで、18 回で 1 周しました。防御は層になっており、どの層も単独では十分ではないことを、各回で実測してきました。

同時に、外したものも数えた

gVisor / ImagePolicyWebhook / Gatekeeper Constraint / VAP は入れて外し、mutual authentication / distroless / trivy k8s / keyless は入れないと決めFalco(第15回)と exec 限定の監査ログ(第18回)を残しました第15回の「入れない / 入れて外す / 残す」の 3 区分)。3 区分を立てた第15回の時点で「残す」は Falco 1 つでしたが、第18回の Postmortem で監査ログが 2 つ目として加わりました——同じ監査ログが第16回では「入れて外す」側だったことを含めて、判断は回をまたいで動きます。 足した数ではなく、判断した数が第3巻の成果です。

三部作の完走

第1巻(CKAD・アプリ開発)、第2巻(CKA・クラスタ構築運用)、第3巻(CKS・防御と検知)で、同じ 1 つのアプリを 54 回にわたって育て切りました。同じ fanclub-api が、kind の上で動くところから、HA クラスタで暗号化され検知され監査される状態まで来ています。

第3巻完走 —— CKS 受験準備完了。

D1 から D6 まで、公式カリキュラムのコンピテンシーは全 19 回で扱いました。残るのは、本回で棚卸しした 4 つの箱を、それぞれの方法で処理する作業です。

Kubestronaut 5 冠ロードマップ —— CARE を含む

5 資格の条件を並べます(2026-08-03 公式確認)。

資格対応する学習試験形式時間合格点前提条件有効期限
CKAD第1巻完走実技120 分66%なし2 年
CKA第2巻完走実技120 分66%なし2 年
CKS第3巻完走実技120 分67%CKA 合格歴が必須2 年
KCNA三部作の学習でカバー多肢選択90 分75%なし2 年
KCSA第3巻 全 19 回でカバー多肢選択90 分75%なし2 年

多肢選択試験の合格点 75% は公式 FAQ の明記です(”A score of 75% or above must be earned to pass the Multiple Choice Exam.”)。結果は即時ではなく、24 時間以内にメールで届きます。 また試験中に他のアプリやブラウザウィンドウを開けません——実技試験と違い、ドキュメントを参照できません。

KCSA は第3巻とどれだけ重なるか

KCSA ドメイン配点第3巻での該当
Kubernetes Cluster Component Security22%第2回第5回(kube-bench / apiserver 堅牢化)
Kubernetes Security Fundamentals22%第3回第4回第8回第9回(NetworkPolicy / RBAC / PSS / Secret)
Kubernetes Threat Model16%第1回(4C・脅威モデル)・第16回第18回(攻撃フェーズ特定)
Platform Security16%第12回第13回第14回(サプライチェーン)
Overview of Cloud Native Security14%第1回
Compliance and Security Frameworks10%第2回(CIS Benchmark)

KCSA は第3巻の知識が新しいうちに受けると効率がよくなります。 第17回で「6 面の脅威検知は KCSA の出題範囲とも重なる」と書いたとおりです。

CARE —— 維持コストの実際

Linux Foundation の CARE Program には、自動更新の経路が定義されています。本シリーズに関係するのは次の 3 本です。

更新される資格トリガー(合格または再認定)発効日
KCNACKA または CKAD2026-01-01
KCSACKS2026-01-01
CKACKS2026-06-18
  • 公式は “Your foundational certification may be current or non-current” としています。つまり期限切れでも復活します
  • 処理は自動ですが即時ではありません(数営業日)

さらに公式は、この 3 本が連鎖することを明記しています

“Earn or recertify CKS and up to three certifications automatically renew through CARE: KCSA, CKA, and KCNA.”(CKS → KCSA + CKA → KCNA)

CKS を 1 つ受け直すだけで、KCSA と CKA が更新され、その CKA がさらに KCNA を引き上げます。 つまり 3 資格が自動更新されます

CKS を再認定 ──→ KCSA が自動更新
             ├→ CKA  が自動更新(2026-06-18〜)
             └→ CKA 経由で KCNA も自動更新

実際に受け直すのは CKS と CKAD の 2 つだけ

残る CKAD だけは自分で受け直す必要があります(CKAD を更新する経路は CARE にありません)。ただし CKAD の再認定は KCNA も引き上げるので、CKS と CKAD の 2 つを回していれば 5 冠は維持できます

5 資格をすべて有効に保持した状態で CNCF に申請すると、Kubestronaut として認定されます(バッジ / ジャケット / コミュニティ参加資格)。外部リンク: https://www.cncf.io/training/kubestronaut/

残り 2 資格の進め方

  1. CKS を受けます。 第3巻完走の時点で準備は整っています。Killer.sh 2 回分を前節の計画どおりに使ってください
  2. KCSA を続けて受けます。 第3巻の知識が最も新しいタイミングです(90 分・多肢選択・75%)
  3. KCNA を受けます。 三部作全体でカバー済みです(90 分・多肢選択・75%)
  4. Kubestronaut 5 冠達成

まとめ

  • クラスタに残っているものと、試験で書けなければならないものは一致しません。 本回の作業は、積み上げを数えることではなく 4 つの箱への棚卸しでした
  • 試験に出るのに残っていない機構が 4 つあります(Gatekeeper Constraint / VAP / RuntimeClass / ImagePolicyWebhook)。すべて演習で入れて、同じ回のうちに撤去しました——ただし片付けただけのもの判断して外したものに分かれます
  • webhook が残っていることは、ポリシーが効いていることを意味しません。 Gatekeeper の webhook 2 件は残っていますが Constraint は 0 本で、constraints という型自体が存在しません
  • 残っているのに試験に出ないものも多くあります。 Helm リリース 13 本のうち出題対象は 4 つで、helm 自体が出題範囲外です
  • 参照可否は、そのページが許可 URL のサブパスにあるかだけで決まります。 crictl は引けて、バージョンスキューは引けません
  • 暗記の総量は 267 行です。 当日カード 20 行・厳選 60 行・全量は各回に戻る、という 3 層で持ちます
  • 覚えないものを決めます。 プロキシ回避・OCI 経路・inotify・GitOps 経由の反映は、試験の点数を 1 点も増やしません。ただし実務では逆になります
  • ラボで一度も動かせなかった領域が 4 つあります(AppArmor / Kata / Istio mTLS / Cilium mutual authentication)。Killer.sh の 1 回目を使う前に弱点リストを潰します
  • CARE により、5 冠の維持で実際に受け直すのは CKAD と CKS の 2 つで足ります

第3巻は 19 回で、防御を積み、その防御が何を生み、何を生まないかを毎回測ってきました。第17回で「防御は検知を生まない」ことを、第18回で「防御は調査も妨げる」ことを、本回で「到達状態はそのままでは答案にならない」ことを確認しました。足した数ではなく、判断した数が第3巻の成果です。 gVisor と ImagePolicyWebhook を外し、mutual authentication を記法に留め、Falco を残した——その判断の記録が、いま手元にあるクラスタと、この本文です。読者はいま、CKS の受験準備が整った状態にいます。

理解度チェック(○×形式・全 9 問)

次の各文が正しいか(○)誤りか(×)を判断してください。下の「解答と解説」を開くと答え合わせができます。

  1. CKS の試験環境は Kubernetes v1.35 で、本書ラボの v1.36.3 とは 1 マイナー + パッチの差がある
  2. helm.sh/docs は CKA では参照できるが、CKS では参照できない
  3. docs.cilium.io はトップページから辿って任意のページを参照できる
  4. kubectl get validatingwebhookconfiguration に Gatekeeper の webhook が並んでいれば、ポリシーは効いている
  5. CKS の試験ホストには alias k=kubectl と bash 補完、yq がプリインストールされている
  6. CKS では、SSH 先のホストからさらに別のホストへ SSH してよい
  7. bom のドキュメントは試験中に参照できないため、コマンドを暗記する必要がある
  8. CKS を取得すると、CARE により CKA の有効期限が延長される
  9. KCNA と KCSA は多肢選択式で、合格点は 75%・試験時間は 90 分である
解答と解説

1=○/2=○/3=×/4=×/5=○/6=×/7=×/8=○/9=○

  • 問1(○): 公式は “The CKS environment is currently running Kubernetes v1.35.” です。ラボは第5回のパッチアップグレードで v1.36.3 になりました。受験前に公式で再確認してください(Linux Foundation は Kubernetes リリースから 4〜8 週間で整合させるとしています)
  • 問2(○): Resources Allowed は試験ごとに別のリストです。helm.sh/docs は CKA / CKAD にのみ載っています。gateway-api.sigs.k8s.ioCKA のみです
  • 問3(×): 許可されているのは docs.cilium.io/en/stable 配下だけです。サブパス限定の 3 件(bom / ingress-nginx / Cilium)は URL ごと覚えます
  • 問4(×): webhook があることと、判断させるルールがあることは別です。constrainttemplates / constraints -A / validatingadmissionpolicy の 3 点で確かめます。本ラボは webhook 2 件が残ったまま Constraint 0 本で、constraints は型自体が存在せずエラーになります——これも「効いていない」の証拠です
  • 問5(○): 公式の Important Instructions に明記されています。本書ラボの k8s-ops には k エイリアスも yq もありませんので、そこは差分です。逆に jq は公式のプリインストール一覧にありません
  • 問6(×): 多段 SSH は使えません(”cannot use nested SSH connections”)。各タスクを終えたら base ノードへ戻ります。base の再起動もしません——再起動しても試験環境は戻りません
  • 問7(×): です。kubernetes-sigs.github.io/bom/cli-reference/ は許可 8 件の 4 番なので参照できます。同じ第14回で扱った Cosign は参照できません——2 つの扱いが正反対です
  • 問8(○): 2026-06-18 以降の CARE 経路です。しかも CKA が期限切れでも復活します(”may be current or non-current”)。さらに公式は CKS 1 つで最大 3 資格(KCSA / CKA / KCNA)が自動更新されると明記しています(”CKS → KCSA + CKA → KCNA”)
  • 問9(○): 公式 FAQ に「75% 以上」、各製品ページに「90 分・オンライン・プロクター付き・多肢選択」とあります。結果は即時ではなく 24 時間以内にメールで届きます

おわりに

本回で第3巻は完結です。第1回で「防御は足し算では強くならない」と書き、19 回かけてその意味を実測で埋めてきました。入れたものより、外したものと、外すと決めた理由のほうが多く残っています。 それが本巻の形です。

次にやることは 2 つだけです。1 つは、本回で作った当日カード 20 行を毎日 1 回読み返すこと。もう 1 つは、弱点リストの 4 領域を潰してから Killer.sh の 1 回目を使うことです。順番を逆にすると、模試 2 回のうち 1 回を「知らないことの確認」に使ってしまいます。

CKS に合格すると、CARE により KCSA・CKA・KCNA が同時に更新されます。残るのは KCNA と KCSA の取得だけで、どちらも多肢選択式・90 分の試験です。そこまで進むと Kubestronaut の 5 冠が揃います。 実技 3 資格の準備はここで整いました。あとは手を動かした回数がそのまま点数になります。

前の記事
次の記事