新卒インフラエンジニア向け「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
目次
- 第19回のスコープ・今ここマップ
- この回のゴール
- ラボに残っているものと、試験で問われるもの —— 4 つの箱
- やってみよう①:クラスタの棚卸し —— 箱 A と箱 B を実機で確定させる
- CKS 試験の最終確認と 6 ドメインの配点
- 試験環境の作法 —— base とタスクごとの SSH ホスト
- 参照可能 8 件の使いこなし
- 時間配分 —— 120 分の 3 パス戦略
- 暗記必須コマンド総まとめ① —— 参照できない 10 ツール
- 当日カード(20 行)
- 厳選版 —— 参照できない 10 ツール(各 4 行 × 10 = 40 行)
- 1. Trivy(trivy.dev は 8 件に入っていない)
- 2. Cosign(sigstore.dev も github.com/sigstore/cosign も不可)
- 3. OPA Gatekeeper(open-policy-agent.github.io は許可 8 サイトに含まれない)
- 4. gVisor / containerd 2.x(gvisor.dev も containerd.io も含まれない)
- 5. AppArmor(不可。man は可)
- 6. SELinux(許可リスト外。man のみ)
- 7. kube-bench(許可 8 件のどこにも載っていない)
- 8. Kubesec / KubeLinter(kubesec.io も docs.kubelinter.io も不可)
- 9. kubeseal / SealedSecrets(許可 8 サイトに含まれない)
- 10. ホスト OS 系(第7回の 25 行のうち、参照できるのは最後の 1 行だけ)
- 全量版 267 行の索引
- 暗記必須コマンド総まとめ② —— 参照できるのに、開かずに打てると差がつくもの
- 暗記しないものを決める —— ラボ固有の事情の切り分け(箱 D)
- やってみよう②:CKS 形式の模擬問題 6 問
- やってみよう③:書けるかを測る —— 箱 B の弱点リスト
- ラボで手を動かせなかった 4 領域と Killer.sh での補完計画(箱 C)
- 第3巻完走宣言 —— fanclub-api のセキュリティ進化
- Kubestronaut 5 冠ロードマップ —— CARE を含む
- まとめ
- 理解度チェック(○×形式・全 9 問)
- おわりに
第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 の設定キー | 除外リストとして明示する |

図にすると、B と C が同じ外枠の中で分かれていることが見えます。どちらも「残っていないのに試験に出る」ですが、B は手順が本文に残っており、C は残っていません。この違いが、対策の立て方を分けます。
B と C を分ける理由
この 2 つは「試験に出るのにクラスタに無い」という点では同じですが、補完の方法がまったく違います。 C は「やれなかった」ので、外部環境で手を動かすしかありません。B は「やった上で外した」ので、手順は本文に残っており、読者は各回に戻れば読み返せます。同じ表に混ぜると、対策の立て方を間違えます。
箱を分ける根拠は、すべて実機の事実にある
- 試験に出るのに、ラボに残っていない機構が 4 つあります。 次節のやってみよう①で、4 つとも空であることを実測します
- Gatekeeper の webhook だけは残り続けています。
ValidatingWebhookConfigurationは 5 件あり、そのうち 1 件がgatekeeper-validating-webhook-configuration(webhook 2 件)です。しかし Constraint は 0 本で、何も止めていません - ラボにあって試験に無いものが多くあります。 Helm リリースは 13 本残っていますが、
helm.sh/docsは CKS では参照すらできません。helmコマンドそのものが出題範囲外です - 試験にあってラボに無いものもあります。 試験ホストには
alias k=kubectlと Bash 補完、yqがプリインストールされていますが、本ラボの k8s-ops にはkエイリアスもyqも入っていません。逆に、ラボにあって試験に無いjqを本文で多用してきました(第4回で明記)
本回の中心メッセージ: 積み上げを数えるのではなく、棚卸しして翻訳します。
「第3巻で何を入れたか」を並べても、試験の設問には答えられません。4 つの箱のどれに入るかで、やるべきことが変わります。
やってみよう①:クラスタの棚卸し —— 箱 A と箱 B を実機で確定させる
自分のクラスタに何が残り、何が残っていないかを実測します。本演習で使うのは読み取りコマンドだけです。 apply も delete も label も打ちません——第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回 | NetworkPolicy | kubectl get netpol -n fanclub | 7 本 |
| 第4回 | RBAC 最小化 | kubectl get sa fanclub-backend -n fanclub | SA + 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 / seccomp | getenforce(5 台)/ Pod の seccompProfile | Enforcing / RuntimeDefault |
| 第8回 | PSS | kubectl get ns fanclub --show-labels | enforce / audit / warn すべて restricted |
| 第9回 | etcd 暗号化 | apiserver の --encryption-provider-config | secretbox / 鍵名 key3 |
| 第9回 | SealedSecrets | kubectl get sealedsecret -n fanclub | fanclub-db-secret(SYNCED=True) |
| 第11回 | Cilium L7 | kubectl get cnp -n fanclub | fanclub-backend-l7(VALID=True) |
| 第15回・第17回 | Falco | kubectl get ds -n falco | 5/5 Running |
| 第18回 | 監査ログ | apiserver の --audit-policy-file | audit-policy-exec.yaml(exec 限定 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 constrainttemplates | No resources found(型はあるが 0 本) |
kubectl get validatingadmissionpolicy | No resources found(型はあるが 0 本) |
kubectl get constraints -A | error: the server doesn't have a resource type "constraints" |
constraints は ConstraintTemplate が生やす 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 appVersion は 0.3.2 のままですが、実際に動いているイメージは fanclub-backend:0.4.1 です。chart と実物の管理者が別だからです——appVersion 0.3.2 は第1巻で helm install したときの値のまま更新されておらず、いま Deployment を管理しているのは ArgoCD(Kustomize の overlays/prod)です。第12回で base/backend-deployment.yaml の image: を 0.4.0 へ、第13回で 0.4.1 へ書き換えて push した結果が、いま動いているものです。chart の値は、その経路を 1 度も通っていません。 宣言値と実物がずれる典型例で、これも本回の主題と同じ形です。試験でも実務でも kubectl get pod -o jsonpath='{..image}' で実物を見るのが正しい手順になります。
CKS 試験の最終確認と 6 ドメインの配点
ここからは箱の話をいったん離れ、試験そのものの事実を確定させます。以下はすべて 2026-08-03 に公式ページから取得した値です。
| 項目 | 内容 |
|---|---|
| 試験形式 | オンライン・プロクター付き・パフォーマンスベース(コマンドライン) |
| 試験環境の Kubernetes | v1.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 ドメインの配点
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 との最大の違いです |
| 2 | base という hostname のベースシステムにはツールが入っていない | “does not have any of the above tools pre-installed” |
| 3 | 各タスクを終えたら base ノードへ戻る | 戻り忘れると、次のタスクを別ホストで解き始めます |
| 4 | 多段 SSH は使えない(nested SSH connections) | 本ラボは k8s-ops から各ノードへ多段しているので逆です |
| 5 | base ノードを再起動してはいけない | “Rebooting the base system will NOT restart your exam environment.” 再起動しても試験環境は戻りません |
| 6 | 権限昇格は sudo -i または sudo | 本ラボと同じです |
| 7 | プリインストール済み: kubectl(k エイリアス + Bash 補完)/ yq / curl / wget / man + man pages | jq は一覧にありません(第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 のタブを閉じます |

本ラボの作法は、試験とほぼ正反対です。 本書ではここまで 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-bench | k8s-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 | 主な用途 | 本巻での出典回 |
|---|---|---|---|
| 1 | https://kubernetes.io/docs/ | NetworkPolicy / RBAC / PSS / VAP / Audit Policy / seccomp / AppArmor 記法 / RuntimeClass / EncryptionConfiguration / エフェメラルコンテナ | ほぼ全回 |
| 2 | https://kubernetes.io/blog/ | 機能解説 | —— |
| 3 | https://falco.org/docs/ | Falco の rule / macro / list・フィールド名・priority | 第15回・第17回 |
| 4 | https://kubernetes-sigs.github.io/bom/cli-reference/ | bom generate / bom document outline(cli-reference/ 配下のみ) | 第14回 |
| 5 | https://etcd.io/docs/ | etcdctl のオプション | 第9回 |
| 6 | https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/ | Ingress(この配下のみ) | 第3回 |
| 7 | https://docs.cilium.io/en/stable | CiliumNetworkPolicy / 暗号化(/en/stable 配下のみ) | 第11回 |
| 8 | https://istio.io/latest/docs/ | PeerAuthentication の mTLS | 第11回 |
加えて、ターミナル上の試験指示・ディストリビューション同梱ドキュメント(/usr/share 配下 = man)・ディストリビューションのパッケージ(既定に無ければ導入も可)が許可されています。検索については、kubernetes.io/docs/ の検索機能は使用可ですが、外部検索結果を開くのは不可です。
CKS で参照できないもの(CKA / CKAD との差)
| URL | CKA / CKAD | CKS |
|---|---|---|
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 を問われたら、標準 Ingress の spec.tls + kubernetes.io/tls Secret で解答します。kubectl create secret tls は最頻出です(第3回)。
istio.io/latest/docs/ は参照できます。
許可 8 件の 8 番目に入っており、CKA / CKAD では許可されていない CKS 固有の追加です。ただし試験時間内に検索している余裕はありません。 PeerAuthentication の mtls.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 分打っても手が止まる問題は、その時点の知識では解けない。次へ行く
全問共通の鉄則(本巻で確立したもの)
- static Pod は編集前に必ずバックアップします(第2回で確立。
/etc/kubernetes/manifests/の外へ置きます) - ファイル参照フラグは 3 点セットです(フラグ +
volumeMounts+volumes)。第2回で原則を立て、第5回・第9回・第14回・第16回・第18回で 5 回組んだ型です(ログ出力先だけはreadOnlyを付けません——第16回・第18回がその例です) - API が落ちている間の真実は
crictl psだけです(第2回・第5回) - HA では全 Control Plane に同じ設定を入れます(第9回・第14回・第16回・第18回)。1 台だけだと LB の振り分け次第で結果が変わります
--dry-run=serverは API に届き、--dry-run=clientは届きません(第8回)。効くかどうかを試すなら server です- 成功表示は成功を意味しません(第8回・第9回・第10回・第15回・第17回・第18回で 6 回出た型)。
applyが通ってもstatusを見ます。helm upgradeが失敗してもrollout statusは成功と言い(第15回)、送信が200でも受け皿がnullなら誰にも届きません(第17回)
3 パス目の検証コマンド集
| 対象 | 検証コマンド |
|---|---|
| RBAC | kubectl auth can-i <verb> <res> --as=system:serviceaccount:<ns>:<sa> -n <ns> |
| NetworkPolicy | kubectl get netpol <name> -n <ns> -o yaml(AND / OR は YAML でしか判別できません・第3回) |
| PSA | kubectl label ns <ns> pod-security.kubernetes.io/enforce=<level> --dry-run=server |
| VAP | kubectl get validatingadmissionpolicy <name> -o jsonpath='{.status.typeChecking}' |
| Gatekeeper | kubectl get <ConstraintKind> <name> -o jsonpath='{.status.byPod[0].enforced}' |
| etcd 暗号化 | etcdctl … get /registry/secrets/<ns>/<name> | hexdump -C | tail -8 |
| Audit Policy | apiserver 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 sign と cosign 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.dev も github.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.v1 は bad import で ingest エラーになり、apply は通るのに効きません)。Constraint の kind: をテンプレート名にしてしまう(テンプレートが生やした CRD 名を書きます)。違反の確認は {.status.violations} です。
4. gVisor / containerd 2.x(gvisor.dev も containerd.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
よくある取り違え: RuntimeClass(node.k8s.io/v1 / handler: runsc)自体は kubernetes.io/docs で引けますが、containerd 2.x(version = 3)のテーブル名はどこにも載っていません。版を head -1 で確認してから書きます。サンドボックスで動いているかの判定は uname -r が 4.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 -DB で dontaudit を外し、調査が終わったら 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.io も docs.kubelinter.io も不可)
kubesec scan <file>
kubesec scan <file> --exit-code 2
kube-linter lint <path>
kube-linter checks list
よくある取り違え: 減点幅を覚えていないと、どこから直すかを決められません。-30(privileged / SYS_ADMIN)、-9(host 系・docker.sock・/proc)、-7(allowPrivilegeEscalation: true)の順です。KubeLinter の設定は .kube-linter.yaml の checks.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 です)。--scope は strict(既定)/ 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 root → visudo -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回) | 6 | kube-bench(取得と実行の形) |
| 第2回 kube-bench + CIS ベンチマーク修復 + バイナリ検証 | 暗記必須コマンド(第2回) | 14 | kube-bench / sha256sum --check |
| 第3回 NetworkPolicy 完全設計 + ノードメタデータ保護 + TLS Ingress | 暗記必須コマンド(第3回) | 8 | kubectl create secret tls / NetworkPolicy |
| 第4回 RBAC 監査 + ServiceAccount トークン管理 | 暗記必須コマンド(第4回) | 12 | auth can-i --as / SA トークン |
| 第5回 API Server 堅牢化 + kubeadm アップグレード | 暗記必須コマンド(第5回) | 18 | apiserver / CSR / kubeadm upgrade |
| 第6回 カーネル層の強制アクセス制御(SELinux / seccomp / capabilities / AppArmor) | 暗記必須コマンド(seccomp / SELinux / AppArmor) | 18 | seccomp / SELinux / AppArmor |
| 第7回 ホスト OS の攻撃面最小化と権限管理 | 暗記必須コマンド(ホスト OS 堅牢化) | 25 | systemd / firewalld / sshd / sudo |
| 第8回 Pod Security Standards + Admission(Gatekeeper / ValidatingAdmissionPolicy) | 暗記必須コマンド(PSA / AdmissionConfiguration / VAP / Gatekeeper) | 20 | PSA / VAP / Gatekeeper |
| 第9回 Secret 管理(etcd 暗号化 / SealedSecrets / ExternalSecrets) | 暗記必須コマンド(etcd 暗号化 / etcdctl / kubeseal) | 24 | EncryptionConfiguration / etcdctl / kubeseal |
| 第10回 マルチテナンシー分離 + サンドボックス(gVisor / RuntimeClass) | 暗記必須コマンド(RuntimeClass / gVisor / テナント境界) | 19 | RuntimeClass / containerd / gVisor |
| 第11回 Cilium 透過暗号化 + L7 NetworkPolicy(Calico → Cilium 移行) | 暗記必須コマンド(Cilium / L7 / Hubble) | 14 | CiliumNetworkPolicy / WireGuard / Hubble |
| 第12回 最小イメージ + 静的解析(Kubesec + KubeLinter) | securityContext ゴールデンセットと暗記必須コマンド | 12 | Kubesec / KubeLinter / Dockerfile |
| 第13回 Trivy イメージスキャン + CI/CD パイプラインセキュリティ統合 | 暗記必須コマンドと 2 つの設定ファイルの型 | 15 | Trivy |
| 第14回 SBOM(bom)+ Cosign イメージ署名 + 許可レジストリ制限 | 暗記必須コマンドと、ラボ固有の事情の切り分け | 16 | bom / Cosign / ImagePolicyWebhook |
| 第15回 Falco ランタイム検知 | 暗記必須コマンドと、ラボ固有の事情の切り分け | 17 | Falco のルール記法 |
| 第16回 監査ログ設計・分析 + 攻撃フェーズ特定 | 暗記必須コマンド | 7 | Audit Policy / jq |
| 第17回 多面的脅威検知の体系 + コンテナ不変性 + アラート連携 | 暗記必須コマンド | 11 | 不変性 / evt.res = EROFS / rule_matching |
| 第18回 セキュリティインシデント対応 DAIR 実戦 | 試験ではどう出るか | 11 | kubectl debug / objectRef.subresource |
| 合計 | 267 |
暗記必須コマンド総まとめ② —— 参照できるのに、開かずに打てると差がつくもの
1 問 5〜12 分の試験で、書式を確かめにドキュメントを開くたびに 1〜2 分が消えます。ここに挙げるのは「載っていないから覚える」ではなく、開かずに打てるようにしておくと差がつく項目です(第4回が立てた区分を全 18 回へ広げます)。12 領域から 20 行を選びました。
| # | 領域(参照先) | 開かずに打てるべきこと |
|---|---|---|
| 1 | RBAC(許可 1) | --as=system:serviceaccount:<ns>:<sa> はコロン 3 個、--serviceaccount=<ns>:<sa> はコロン 1 個 |
| 2 | RBAC(許可 1) | ClusterRole × RoleBinding で Namespace に閉じる/automountServiceAccountToken は SA 単位と Pod 単位があり Pod 側が勝つ |
| 3 | NetworkPolicy(許可 1) | from / to の AND / OR の書き分け(- を付けるか、同一要素内に並べるか) |
| 4 | NetworkPolicy(許可 1) | podSelector 単独は同一 Namespace のみ/ipBlock の except は cidr に含まれる範囲だけ |
| 5 | PSS / PSA(許可 1) | enforce と enforce-version は必ずセット/壊さずに数えるなら --dry-run=server |
| 6 | PSS / PSA(許可 1) | 拒否の真因は kubectl describe rs の Events(get deploy の READY では気づけません) |
| 7 | VAP(許可 1) | validationActions: ["Deny"] を忘れない/object.spec.containers.all(c, …)/initContainers とタグ省略を取りこぼさない |
| 8 | etcd 暗号化(許可 1・5) | head -c 32 /dev/urandom | base64/--encryption-provider-config-automatic-reload=true |
| 9 | etcd 暗号化(許可 1・5) | 鍵ローテーションの順序 ①新鍵を先頭 ②反映 ③全件再暗号化 ④旧鍵削除/etcdctl は --cacert --cert --key の 3 点 |
| 10 | Audit Policy(許可 1) | level 4 種と「最初にマッチしたルールが勝つ」評価順/stage 4 種と omitStages |
| 11 | Audit Policy(許可 1) | kubectl exec の verb は create ではなく get。 絞り込みは objectRef.subresource |
| 12 | Falco(許可 3) | - list: / - macro: / - rule:(desc / condition / output / priority / tags)/override: condition: replace と append の差 |
| 13 | Falco(許可 3) | rule_matching: first は定義順が勝ち、priority は無関係/阻止された書き込みは evt.res = EROFS |
| 14 | Cilium(許可 7) | toPorts[].rules.http[] の method / path(path は POSIX 正規表現・拒否は 403)/toFQDNs は rules.dns とセット |
| 15 | Cilium(許可 7) | encryption.enabled + encryption.type は Helm 値(CiliumNetworkPolicy 側ではありません)/CLI が無い環境では agent Pod の cilium-dbg と hubble |
| 16 | Istio(許可 8) | PeerAuthentication の mtls.mode(STRICT / PERMISSIVE / DISABLE)。参照はできますが、検索する時間はありません |
| 17 | securityContext(許可 1) | ゴールデンセット: Pod 側に runAsNonRoot + runAsUser + seccompProfile.type: RuntimeDefault、コンテナ側に readOnlyRootFilesystem + allowPrivilegeEscalation: false + capabilities.drop: ["ALL"] |
| 18 | securityContext(許可 1) | PSS Restricted は readOnlyRootFilesystem を要求しません/迷ったら kubectl explain … --recursive |
| 19 | kubectl debug(許可 1) | --target でプロセス名前空間を共有し /proc/1/root/ から読む/--profile=restricted は runAsUser を付けません/エフェメラルコンテナは Pod を残したまま取り除けません |
| 20 | kubeadm upgrade(許可 1) | upgrade plan → 1 台目だけ upgrade apply <ver> → 2 台目以降と Workload は upgrade node/drain --ignore-daemonsets と uncordon |
ディストロ差を覚えます(第5回で確立)。
本書ラボは AlmaLinux(dnf / dnf versionlock)ですが、CKS の試験環境は Debian 系(apt / apt-mark hold)です。コマンド文字列をそのまま暗記すると当日に手が止まります。暗記するのは型——「ロックを外す → 版を指定して入れ替える → ロックし直す」——で、対応するコマンドは kubernetes.io/docs の kubeadm upgrade のページに両方載っています(試験中に参照できます)。
暗記しないものを決める —— ラボ固有の事情の切り分け(箱 D)
ここからは箱 D の処理です。第13回から第18回までが各回で書いてきた「ラボ固有」の表を、1 つに統合します。何を覚えないかを決めるのは、覚えることを決めるのと同じくらい試験対策になります。
| 使ったもの | 本ラボで必要だった理由 | 試験では |
|---|---|---|
--noproxy '*' / no_proxy と NO_PROXY の 2 行 | Squid プロキシ環境。cosign は Go 製で大文字の NO_PROXY を優先して読みます | 不要(試験環境にプロキシはありません) |
--db-repository / --java-db-repository / --checks-bundle-repository / --skip-db-update | whitelist で Trivy DB の既定配布元に出られません | 不要(素の trivy image が動きます) |
--allow-http-registry / --use-signing-config=false / --tlog-upload=false / --insecure-ignore-tlog | k8s-registry が HTTP 平文で、TUF・Rekor に到達できません | 不要(TLS レジストリ・外部到達可) |
bom generate --image-archive + docker save | bom が HTTP レジストリを読めません | 不要(--image が使えます) |
oci://ghcr.io/falcosecurity/charts/falco | falcosecurity.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> の --custom | fanclub が 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 で止めます | fanclub は ArgoCD 管理下で、手で変えると selfHeal が戻します(第8回「クラスタの正本は Git である」)。戻る前に差分が残ります |
| Q3 | 既存 Pod で読み取り(kubectl exec で grep Seccomp /proc/1/status)。検証用 Pod を新規に作りません | fanclub は PSA enforce: restricted なので素の busybox は弾かれます。AppArmor の記法は書くだけです(箱 C・このクラスタでは適用できません) |
| Q4 | --dry-run=server まで | 箱 B。VAP は現在 0 本なので、apply すると箱 B が箱 A に変わってしまいます |
| Q5 | スキャンは実行できます(読み取りのみ)。bom generate と cosign 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 分)
| # | ドメイン | 想定時間 | 設問(要旨) | 箱 | 元の回 |
|---|---|---|---|---|---|
| Q1 | D1(15%) | 6 分 | Namespace に default-deny-all を敷いたうえで DNS だけを許可し、169.254.169.254/32 への egress が遮断されたままであることを YAML で示す | A | 第3回 |
| Q2 | D2(15%) | 8 分 | ServiceAccount が Secret を読めないことを検証し、その SA を使う Deployment の Pod にトークンをマウントさせない(Pod 単位で) | A | 第4回 |
| Q3 | D3(10%) | 8 分 | seccompProfile: RuntimeDefault と capabilities.drop: ["ALL"] を適用し、効いていることを確認する。あわせて AppArmor の Localhost プロファイルの記法を書く | A + C | 第6回 |
| Q4 | D4(20%) | 10 分 | latest タグのイメージを持つ Pod を拒否する ValidatingAdmissionPolicy と Binding を作る(validationActions: ["Deny"]) | B | 第8回 |
| Q5 | D5(20%) | 10 分 | イメージを HIGH と CRITICAL のみでスキャンし、検出時に終了コード 1 を返す。続いて SBOM を生成し、鍵ペア方式で署名して検証する | A | 第13回・第14回 |
| Q6 | D6(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: {} と policyTypes に Ingress / Egress の両方がある | これが無いと 0 点(片方だけでは default-deny になりません) |
allow-dns が UDP 53 と TCP 53 の両方を開けている | 必須。TCP を忘れると大きな応答で名前解決が失敗します |
namespaceSelector と podSelector が 同一リスト要素内にある(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: false が spec.template.spec 直下にある | 必須。コンテナ側やメタデータ側では効きません |
| SA 単位ではなく Pod 単位で書いている | 設問の指定どおり。Pod 側の指定が SA 側に勝ちます |
マウントが消えたことを spec.volumes で確認できる | 加点(第4回で確認した手順) |
よくある減点: --as と --serviceaccount の書式を取り違える(--as はコロン 3 個、kubectl create 系の --serviceaccount=<ns>:<sa> はコロン 1 個)。SA 単位で patch して、既存 Pod に効いていないのに効いたと誤認する——マウントは Pod 作成時に決まるので、作り直すまで変わりません。
本番では違います。
本ラボの fanclub は ArgoCD 管理下なので、手で変えても 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 mode、CapEff がすべて 0 なら capability は 1 つも残っていません。あわせて、AppArmor の記法は次のとおりです。
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: k8s-apparmor-example-deny-write
このクラスタでは適用できません。 AlmaLinux のカーネルは LSM に AppArmor を持たず、cat /sys/kernel/security/lsm に apparmor が現れないためです。指定した Pod は phase: Failed・STATUS 列 AppArmor で拒否されます(第6回で実測)。
| 採点基準 | 配点の考え方 |
|---|---|
seccompProfile.type: RuntimeDefault が Pod 側にある | これが無いと 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 点(ポリシーを書いただけでは何も止まりません) |
initContainers を variables で合流させている | 必須。has(…) ? … : [] を使わないと未定義で式が落ちます |
タグ省略(nginx)を latest と扱えている | 必須。split('/') の最終要素に : があるかで判定します |
matchConstraints の operations に UPDATE が入っている | 加点(作成だけ塞いでも更新で入ります) |
よくある減点: 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 sign と bom 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 つ、volumeMounts と volumes です(全量)。
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 点(「それ以外は記録しない」が実現しません) |
resources に pods/exec / pods/attach / pods/portforward の 3 つがある | 必須。subresource として書きます |
3 点セット(フラグ + volumeMounts + volumes)が揃っている | 1 つ欠けると apiserver が起動しません |
ログ出力先が readOnly: false で、ポリシー側が readOnly: true | 必須(書き込み先を読み取り専用にすると起動できません) |
よくある減点: - level: None を最後に置かない(全部記録されて重くなります)。3 点セットのうち volumes を忘れて apiserver が起動しない。ログ出力先を readOnly でマウントする。記録側の絞り込みで verb を create だと思い込む——kubectl exec の verb は get で、絞り込みは objectRef.subresource です(第18回)。
本番では違います。
このポリシー単体では、調査に足りません。第18回で「Users 面をどこまで塞げたか」を数えたとおり、記録範囲を決めることは、調査可能性を決めることです。試験は「指定どおりに絞る」ことを問いますが、本番では何を捨てたかを把握したうえで絞ります。
やってみよう③:書けるかを測る —— 箱 B の弱点リスト
3 本目は、箱 B(やった上で撤去した 4 機構)を対象にした自己テストです。なぜ箱 B かというと、ここが最も抜けやすいからです。
- 箱 A は実機で読み返せます(やってみよう① でやりました)
- 箱 C は外部環境でやるしかありません(次節)
- 箱 B だけが、手順は本に残っているのにクラスタには無い——「読めば思い出せるが、思い出せるかどうかを確かめていない」状態にあります
自己テストの手順
- 各項目について、ドキュメントも本書も開かずに YAML / コマンドを書きます(紙でもエディタでもかまいません)
- 書き終えてから該当回を開いて答え合わせをします
- 書けなかった項目を弱点リストに残します
- Killer.sh の 1 回目を使う前に弱点リストを潰します。模擬試験は 2 回分しかないので、潰さずに使うと 1 回分が「知らないことの確認」で終わります
自己テスト項目(箱 B・4 機構)
| # | 機構 | 書けるべきもの | 撤去した回 |
|---|---|---|---|
| 1 | OPA Gatekeeper | ConstraintTemplate(templates.gatekeeper.sh/v1 / spec.crd.spec.names.kind / targets[].rego・Rego v0)と Constraint(constraints.gatekeeper.sh/v1beta1 / spec.match.kinds)/効いているかの確認({.status.byPod[0].enforced})/主 webhook(validation.gatekeeper.sh)の failurePolicy は既定で Ignore=fail-open(本ラボの実測では、同じ設定に含まれるもう 1 本 check-ignore-label.gatekeeper.sh は Fail。1 つの ValidatingWebhookConfiguration の中で値が揃っているとは限りません) | 第8回 |
| 2 | ValidatingAdmissionPolicy | 本体 + Binding/validationActions: ["Deny"]/CEL の定石(object.spec.containers.all(c, …) / has(…) ? … : [] / image.split('/'))/{.status.typeChecking} | 第8回 |
| 3 | RuntimeClass(gVisor) | node.k8s.io/v1 の RuntimeClass(handler: runsc)/scheduling.nodeSelector(Pod の nodeSelector と積を取ります)と scheduling.tolerations(和を取ります)/overhead.podFixed/containerd 2.x のテーブル名と runtime_type/サンドボックス判定(uname -r → 4.19.0-gvisor)/PSA の exemptions.runtimeClasses | 第10回 |
| 4 | ImagePolicyWebhook | --enable-admission-plugins=NodeRestriction,ImagePolicyWebhook(既存の値を消さない)/--admission-control-config-file の 3 点セット/AdmissionConfiguration(apiserver.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.v1 が bad import で ingest エラーになり——apply は通るのに効きません。もう 1 つは、Constraint の kind: をテンプレート名にしてしまうことです。書くのは、テンプレートが生やした CRD 名のほうです。
弱点リストのテンプレート
| 機構 | 書けなかった箇所 | どの回に戻るか | 潰した日 |
|---|---|---|---|
「入れて外す」は失敗ではありません。
第10回の gVisor も第14回の ImagePolicyWebhook も、「適用 → 証拠 → 代償 → 撤去」という同じ型で完結させました。第15回が「入れない判断 / 入れて外す判断 / 残す判断」を 1 表にまとめたとおり、何を入れないかも設計の一部です。試験に向けては「書ける」ことが問われ、運用に向けては「入れるかどうか」が問われます——この 2 つは別の問いだと分けて持つのが、本回の到達点になります。
ラボで手を動かせなかった 4 領域と Killer.sh での補完計画(箱 C)
ここに挙げる 4 つは、本書ラボでは一度も動かせませんでした。理由は環境の構造にあり、手順を間違えたからではありません。試験環境では動くので、Killer.sh(Ubuntu ベース)で補完します。
| 領域 | ラボで動かせない理由 | 暗記対象 | 補完先 |
|---|---|---|---|
| AppArmor | AlmaLinux は LSM に AppArmor を搭載しません(cat /sys/kernel/security/lsm に apparmor が無く、指定した Pod は phase: Failed / STATUS 列 AppArmor で拒否されます) | securityContext.appArmorProfile の 3 パターン(RuntimeDefault / Localhost + localhostProfile / Unconfined)+ 旧アノテーション記法との対比 + apparmor_parser -q / aa-status | Killer.sh で apparmor_parser → Pod 適用まで |
| Kata Containers | /dev/kvm がありません(Hyper-V 上のゲストのため) | RuntimeClass の handler: kata と、gVisor(ユーザー空間カーネル)との分離モデルの違い | Killer.sh / 座学 |
| Istio の mTLS | 本書ラボは Cilium を採用しました(Istio を入れない設計判断) | PeerAuthentication の mtls.mode(STRICT / PERMISSIVE / DISABLE) | istio.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/tcp | Killer.sh / 座学 |
加えて、記法だけ復習しておく 2 つがあります。
| 項目 | 記法 |
|---|---|
| IPsec による透過暗号化 | encryption.type=ipsec + Secret cilium-ipsec-keys(kube-system・キー名 keys)。KEYID は 1〜15 で循環します。本ラボは WireGuard を採用したので IPsec は未実行です |
toFQDNs | toFQDNs: [{matchName: …}] + rules.dns(DNS への 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=24h。Kubernetes は v1.36.3 へ |
| 第6回 | SELinux Enforcing + seccomp + capabilities | 5 台すべて Enforcing。containerd の 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 すべて restricted。Constraint と VAP は撤去(箱 B) |
| 第9回 | etcd 暗号化 + SealedSecrets | --encryption-provider-config + automatic-reload=true。provider は secretbox・鍵名 key3(ローテーション 2 回の証跡)/ SealedSecret fanclub-db-secret(SYNCED=True) |
| 第10回 | gVisor サンドボックス | 撤去(箱 B)。 RuntimeClass 0 件 |
| 第11回 | Calico → Cilium 一括移行 + WireGuard + L7 | Cilium 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: true。db には付いていません(PSS Restricted が要求しないため) |
| 第18回 | インシデント対応(DAIR)+ 監査ログの復活 | 3 台の Control Plane すべてで audit-policy-exec.yaml が有効(pods/exec / attach / portforward のみ Metadata・他は - level: None) |

図で見ると、帯から落ちているものが 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 Security | 22% | 第2回・第5回(kube-bench / apiserver 堅牢化) |
| Kubernetes Security Fundamentals | 22% | 第3回・第4回・第8回・第9回(NetworkPolicy / RBAC / PSS / Secret) |
| Kubernetes Threat Model | 16% | 第1回(4C・脅威モデル)・第16回・第18回(攻撃フェーズ特定) |
| Platform Security | 16% | 第12回・第13回・第14回(サプライチェーン) |
| Overview of Cloud Native Security | 14% | 第1回 |
| Compliance and Security Frameworks | 10% | 第2回(CIS Benchmark) |
KCSA は第3巻の知識が新しいうちに受けると効率がよくなります。 第17回で「6 面の脅威検知は KCSA の出題範囲とも重なる」と書いたとおりです。
CARE —— 維持コストの実際
Linux Foundation の CARE Program には、自動更新の経路が定義されています。本シリーズに関係するのは次の 3 本です。
| 更新される資格 | トリガー(合格または再認定) | 発効日 |
|---|---|---|
| KCNA | CKA または CKAD | 2026-01-01 |
| KCSA | CKS | 2026-01-01 |
| CKA | CKS | 2026-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 資格の進め方
- CKS を受けます。 第3巻完走の時点で準備は整っています。Killer.sh 2 回分を前節の計画どおりに使ってください
- KCSA を続けて受けます。 第3巻の知識が最も新しいタイミングです(90 分・多肢選択・75%)
- KCNA を受けます。 三部作全体でカバー済みです(90 分・多肢選択・75%)
- 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 問)
次の各文が正しいか(○)誤りか(×)を判断してください。下の「解答と解説」を開くと答え合わせができます。
- CKS の試験環境は Kubernetes v1.35 で、本書ラボの v1.36.3 とは 1 マイナー + パッチの差がある
helm.sh/docsは CKA では参照できるが、CKS では参照できないdocs.cilium.ioはトップページから辿って任意のページを参照できるkubectl get validatingwebhookconfigurationに Gatekeeper の webhook が並んでいれば、ポリシーは効いている- CKS の試験ホストには
alias k=kubectlと bash 補完、yqがプリインストールされている - CKS では、SSH 先のホストからさらに別のホストへ SSH してよい
bomのドキュメントは試験中に参照できないため、コマンドを暗記する必要がある- CKS を取得すると、CARE により CKA の有効期限が延長される
- 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.ioは CKA のみです - 問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 資格の準備はここで整いました。あとは手を動かした回数がそのまま点数になります。
