ข้ามไปยังเนื้อหา

Verification tools

ไม่มีเครื่องมือด้านล่างอันไหนที่ overlap กันสมบูรณ์ และไม่มีอันไหนที่ครบถ้วนด้วยตัวเองเลย — บรรทัด “Caught by” ของแต่ละข้อในทั้ง 24 ข้อของ AI Bug Catalog ระบุชัดว่าเครื่องมือไหน (ถ้ามี) ด้านล่างนี้จะจับมันได้ และ 16 จาก 24 ข้อนั้นไม่มีเครื่องมือไหนจับได้เลยตอน apply ทุก output ด้านล่างเป็นของจริง จับมาจากคลัสเตอร์ kind ของ Kubernetes 1.37.0 จริง

kubectl explain — วิธีที่เร็วที่สุดที่จะรู้ว่า field นี้คืออะไรจริง ๆ

หัวข้อที่มีชื่อว่า “kubectl explain — วิธีที่เร็วที่สุดที่จะรู้ว่า field นี้คืออะไรจริง ๆ”

kubectl explain อ่าน OpenAPI schema ของคลัสเตอร์เอง — ไม่ใช่การเดาที่ cache ไว้ ไม่ใช่เอกสารเวอร์ชันที่อาจล้าสมัย — ทำให้มันเป็นวิธีที่เร็วที่สุดที่จะเช็กว่า field หนึ่ง หรือทั้ง kind จริงบนเวอร์ชันที่คุณรันอยู่หรือไม่ --recursive ขยายทุก field ที่ซ้อนกันในครั้งเดียว แทนที่จะไล่ทีละชั้น:

$ kubectl explain deployment.spec.strategy --recursive
FIELDS:
rollingUpdate <RollingUpdateDeployment>
maxSurge <IntOrString>
maxUnavailable <IntOrString>
type <string>
enum: Recreate, RollingUpdate

--api-version คือสิ่งที่ทำให้ kubectl explain มีประโยชน์จริงสำหรับข้อ 5 ของ AI Bug Catalog — HorizontalPodAutoscaler มีรูปร่างที่ใช้งานจริงพร้อมกันสองแบบ และการ explain ทั้งสองเวอร์ชันเทียบกันพิสูจน์ว่ามันไม่เคยทับซ้อนกันเลย:

$ kubectl explain hpa.spec --api-version=autoscaling/v1
FIELDS:
maxReplicas <integer> -required-
...
$ kubectl explain hpa.spec --api-version=autoscaling/v2
FIELDS:
behavior <HorizontalPodAutoscalerBehavior>
...

field ของ v1 (targetCPUUtilizationPercentage) กับของ v2 (metrics, behavior) ไม่มีอยู่บนอีกเวอร์ชันเลย — kubectl explain กับ --api-version คือวิธีที่เร็วกว่าและออฟไลน์ในการยืนยันความไม่ตรงกันที่ข้อ 5 พิสูจน์ไว้แล้วด้วย BadRequest จริงบนคลัสเตอร์จริง

kubectl apply --dry-run=server เทียบกับ --dry-run=client — อันหนึ่งคุยกับ API server จริง อีกอันไม่คุยเลย

หัวข้อที่มีชื่อว่า “kubectl apply --dry-run=server เทียบกับ --dry-run=client — อันหนึ่งคุยกับ API server จริง อีกอันไม่คุยเลย”

นี่คือความต่างที่สำคัญที่สุดในบทนี้ และสอง flag นี้สับสนกันง่ายมากเพราะทั้งคู่พิมพ์บรรทัด success ที่แทบเหมือนกัน --dry-run=client ไม่ติดต่อ API server เลย — มันแค่ตรวจว่า request สร้างได้ โดยใช้ schema ที่ cache ไว้ในเครื่องเท่านั้น --dry-run=server ส่ง request จริง (ด้วย ?dryRun=All) ผ่าน OpenAPI validation และ admission chain เดียวกับ apply จริงทุกประการ แล้วข้ามแค่ขั้นตอนสุดท้ายคือเขียนลง etcd พิสูจน์โดยตรง บนบั๊กใน catalog สองข้อเดียวกัน ห่างกันแค่คำสั่งเดียว:

$ kubectl apply --dry-run=client -f hpa-v1-v2-mixed-shape.yaml
horizontalpodautoscaler.autoscaling/web created (dry run)
$ kubectl apply --dry-run=server -f hpa-v1-v2-mixed-shape.yaml
Error from server (BadRequest): ... strict decoding error: unknown field "spec.metrics"
$ kubectl apply --dry-run=client -f deployment-selector-immutable.yaml # Deployment ที่มีอยู่แล้ว เปลี่ยน selector
deployment.apps/web configured (dry run)
$ kubectl apply --dry-run=server -f deployment-selector-immutable.yaml
The Deployment "web" is invalid: spec.selector: Invalid value: ...: field is immutable

--dry-run=client รายงานว่าสำเร็จทั้งสองบั๊กจริงและปัจจุบัน — มันไม่มีทางรัน strict decoding ฝั่ง server หรือเทียบกับสถานะที่เก็บจริงของ object ได้เลย เพราะไม่เคยออกจากเครื่องคุณเลย --dry-run=server เป็นตัวเดียวในสองตัวนี้ที่ควรใช้เป็น review gate ดู kubectl และ Declarative YAML สำหรับกลไกเต็มรูปแบบของทั้งสอง flag และ Server-Side Apply

kubectl diff — เห็นสิ่งที่ apply จะเปลี่ยน ก่อนที่มันจะเปลี่ยนจริง

หัวข้อที่มีชื่อว่า “kubectl diff — เห็นสิ่งที่ apply จะเปลี่ยน ก่อนที่มันจะเปลี่ยนจริง”

kubectl diff -f รัน merge แบบเดียวกับที่ Server-Side Apply จะทำ กับ object ที่มีอยู่จริง แล้วพิมพ์ diff จริงแบบ unified โดยไม่เปลี่ยนอะไรเลย:

$ kubectl diff -f deployment-changed.yaml
--- LIVE
+++ MERGED
@@
- replicas: 2
+ replicas: 4
selector:
matchLabels:
@@
containers:
- - image: nginx:1.27-alpine
+ - image: nginx:1.29-alpine

นี่คือวิธีตรงและจริงที่สุดที่จะตอบคำถาม “manifest นี้เปลี่ยนอะไรจริง ๆ” สำหรับ manifest ที่ AI ผู้ช่วยอ้างว่า “แค่แก้นิดเดียว” — อ่าน diff ไม่ใช่คำอ้าง

kubeconform — ตรวจ OpenAPI schema จริง แบบออฟไลน์ ทั้งโหมดปกติและ strict

หัวข้อที่มีชื่อว่า “kubeconform — ตรวจ OpenAPI schema จริง แบบออฟไลน์ ทั้งโหมดปกติและ strict”

kubeconform ตรวจรูปร่างของ manifest กับ OpenAPI schema จริงของ Kubernetes สำหรับเวอร์ชันเป้าหมาย โดยไม่ต้องมีคลัสเตอร์เลย โหมด default ของมันค่อนข้างผ่อนปรนเรื่อง field เกิน -strict คือจุดที่มันแสดงฝีมือจริง:

$ docker run --rm -v "$PWD":/data ghcr.io/yannh/kubeconform:latest -strict -summary /data/bad.yaml
/data/bad.yaml - Deployment web is invalid: problem validating schema: ... at '/spec/template/spec/containers/0':
additional properties 'privileged' not allowed
Summary: 1 resource found in 1 file - Valid: 0, Invalid: 1, Errors: 0, Skipped: 0

จับได้ว่า field privileged ถูกวางตรงใต้ container เลย (แทนที่จะซ้อนอยู่ใต้ securityContext ตามที่ควรจะเป็น) — ความผิดที่มองผ่าน ๆ เข้าใจว่าถูกต้องได้ง่ายมาก กับ apiVersion ที่ถูกลบไปแล้ว kubeconform รายงานสัญญาณอีกแบบที่จริงเช่นกัน — มันไม่มี schema สำหรับเวอร์ชันที่ไม่มีอยู่แล้ว:

$ docker run --rm -v "$PWD":/data ghcr.io/yannh/kubeconform:latest -summary /data/extensions-v1beta1-deployment.yaml
/data/extensions-v1beta1-deployment.yaml - Deployment web failed validation: could not find schema for Deployment
Summary: 1 resource found in 1 file - Valid: 0, Invalid: 0, Errors: 1, Skipped: 0

ไม่ต้องมีคลัสเตอร์เลยสำหรับทั้งสองเช็ก — นี่คือเครื่องมือที่ควรรันใน CI ก่อนที่จะไปถึงคลัสเตอร์ kind/staging จริงเสียอีก

kube-linter — ผลลัพธ์จริงและปัจจุบันจากชุดกฎด้าน security และ reliability

หัวข้อที่มีชื่อว่า “kube-linter — ผลลัพธ์จริงและปัจจุบันจากชุดกฎด้าน security และ reliability”

kube-linter (output จริงด้านล่างจาก stackrox/kube-linter รันผ่าน Docker กับ Deployment web ที่มีรูปร่างเดียวกับข้อ 18/19 ของ catalog) เช็กชุดกฎที่กว้างกว่า schema validity มาก — resource requests, image tag, anti-affinity, root filesystem, privilege escalation พร้อมกันหมด:

$ docker run --rm -v "$PWD":/data stackrox/kube-linter:latest lint /data/lint-target.yaml
Error: found 8 lint errors
KubeLinter 0.8.3
.../lint-target.yaml: container "web" is privileged (check: privileged-container, ...)
.../lint-target.yaml: container "web" is Privileged hence allows privilege escalation. (check: privilege-escalation-container, ...)
.../lint-target.yaml: container "web" is not set to runAsNonRoot (check: run-as-non-root, ...)
.../lint-target.yaml: container "web" has cpu request 0 (check: unset-cpu-requirements, ...)
.../lint-target.yaml: container "web" has memory limit 0 (check: unset-memory-requirements, ...)
.../lint-target.yaml: The container "web" is using an invalid container image, "nginx:latest" (check: latest-tag, ...)
.../lint-target.yaml: object has 3 replicas but does not specify inter pod anti-affinity (check: no-anti-affinity, ...)
.../lint-target.yaml: container "web" does not have a read-only root file system (check: no-read-only-root-fs, ...)

คำสั่งเดียวเผยต้นตอของข้อ 12 ใน AI Bug Catalog ที่ requests หายไป, แพทเทิร์น privileged: true จากข้อ 18, และเช็กอีกหลายอย่างที่ catalog ของคอร์สนี้ไม่ได้ครอบคลุมเลย (image tag :latest, anti-affinity, read-only root filesystem) — kube-score กับ polaris ครอบคลุมชุดกฎที่คาบเกี่ยวกันแต่ไม่เหมือนกันเป๊ะในแบบเดียวกัน คุ้มค่าที่จะรันทุกตัว ไม่มีตัวไหนแทนกันได้

โหมด warn ของ Pod Security Admission — linter ที่ทำงานจริงบนทุก apply จริง

หัวข้อที่มีชื่อว่า “โหมด warn ของ Pod Security Admission — linter ที่ทำงานจริงบนทุก apply จริง”

ต่างจากทุกเครื่องมือด้านบน Pod Security Admission ทำงานอยู่ข้างใน API server เอง บนทุก request จริง ไม่ว่าจะมีใครมองอยู่หรือไม่ — และระดับ warn ของมันถูกออกแบบมาให้เป็น linter แบบไม่บล็อกโดยเฉพาะ: มันพิมพ์ข้อความ violation เดียวกับที่ enforce จะปฏิเสธ แล้วก็ยอม admit object นั้นอยู่ดี

$ kubectl apply -f privileged-realistic.yaml # namespace: enforce=baseline, warn=restricted
Warning: would violate PodSecurity "restricted:latest": privileged (container "c" must not set securityContext.privileged=true), allowPrivilegeEscalation != false (...), unrestricted capabilities (...), runAsNonRoot != true (...), seccompProfile (...)
pod/privileged-realistic created

การตั้ง warn ให้เข้มกว่า enforce ทั่วทั้งคลัสเตอร์ (หรือทั้ง namespace) เปลี่ยนทุก kubectl apply จริงให้เป็น linter pass ฟรี ๆ ที่ไม่ต้องตั้งค่าอะไรเพิ่ม ดู Pod & Cluster Security สำหรับการเทียบระดับ privileged/baseline/restricted เต็มรูปแบบ และการปฏิเสธจริงของ enforce ที่ข้อความเดียวกันนี้สร้าง

ValidatingAdmissionPolicy — กฎแบบ CEL สำหรับสิ่งที่ Pod Security Admission ไม่ครอบคลุม

หัวข้อที่มีชื่อว่า “ValidatingAdmissionPolicy — กฎแบบ CEL สำหรับสิ่งที่ Pod Security Admission ไม่ครอบคลุม”

ชุดกฎของ Pod Security Admission ตายตัว — มันไม่มีทางแสดงกฎเฉพาะองค์กรอย่าง “ดึง image จาก myregistry.internal/* เท่านั้น” ได้ ValidatingAdmissionPolicy (ใช้ CEL, built-in ใน API server, GA ตั้งแต่ Kubernetes 1.30) เติมช่องว่างนั้นพอดี Pod & Cluster Security พิสูจน์ policy จริงที่ปฏิเสธ image นอก allowlist ด้วย error จาก CEL จริงและปัจจุบัน ปฏิบัติกับมันแบบเดียวกับ PSA: เป็นเช็กที่ทำงานจริงบนทุก request ไม่ใช่เช็กแบบ CI หรือแบบออฟไลน์

ทุก fence ประเภท yaml ในคอร์สนี้ที่มี path comment จริงเป็นบรรทัดแรก (# k8s/<lesson>/<name>.yaml) ถูกเก็บโดย tools/verify-manifests.mjs แล้วรันผ่าน kubectl apply --dry-run=server กับคลัสเตอร์ kind ที่ใช้ร่วมกันของคอร์สนี้เอง (verify-k8s, Kubernetes 1.37, ติดตั้ง Gateway API และ VolumeSnapshot CRD ไว้ล่วงหน้า):

Terminal window
npm run verify

fence ที่ตั้งใจแสดง YAML ที่พัง (ตัวอย่าง “โค้ดที่ AI มักเขียน” ส่วนใหญ่ใน AI Bug Catalog) ขึ้นต้นด้วย # @expect-error <เหตุผล> ทำให้ harness ข้ามการเก็บ fence นั้นไปทั้งหมด — เนื้อหาของบทเรียนแบกรับ output จริงของ dry-run=server/apply/describe/curl ที่จับแยกต่างหากไว้แทน ตามที่แสดงตลอดบทเรียนนั้น node tools/verify-manifests.mjs --apply <module>/<lesson> ไปไกลกว่านั้นทีละบท: มัน apply จริง fence ของบทนั้นลง namespace ชั่วคราว รอ Pod, พิมพ์ get all และ events แล้วเก็บกวาด — วิธีเดียวที่ harness ของคอร์สนี้ทำซ้ำสัญญาณที่เป็น runtime ล้วน ๆ ได้ตามต้องการ (HPA target แบบ <unknown>, eviction ที่ถูกบล็อก, restart loop จริง) แบบเดียวกับที่การพิสูจน์ในบทนี้ถูกจับมา

kubectl apply --dry-run=server กับ API server จริง (harness ของคอร์สนี้เอง tools/verify-manifests.mjs รันแบบนี้อยู่แล้ว) คือบททดสอบจริง — แต่การตรวจแบบ static เร็ว ๆ ในเบราว์เซอร์ จับ error พื้นฐาน (field ที่ไม่มีจริง, field จำเป็นที่ขาด, type ผิด, apiVersion ที่ถูกลบไปแล้ว) ได้ก่อนจะไปถึง cluster ด้วยซ้ำ:

นี่คือเช็กแบบ static ในเบราว์เซอร์ตัวเดียวกับที่ฝังไว้แล้วใน kubectl และ Declarative YAML — ไม่ใช่เครื่องมือที่สอง แยกต่างหาก แค่เป็นอีกที่หนึ่งที่มันมีประโยชน์จริง: ก่อนที่จะ apply manifest ที่โมดูลนี้เพิ่งสอนให้คุณไม่เชื่อตั้งแต่แรกเห็น

flowchart TD
  subgraph Static["Static (ไม่ต้องมีคลัสเตอร์)"]
    Explain["kubectl explain --recursive/--api-version<br/>schema จริง ออฟไลน์"]
    Conform["kubeconform -strict<br/>error รูปร่าง, apiVersion ที่ถูกลบ"]
    Lint["kube-linter / kube-score / polaris<br/>กฎด้าน security + reliability"]
    Schema["K8sSchemaCheck (ในเบราว์เซอร์)<br/>เช็กแบบเดียวกับ kubeconform ฝั่ง client"]
  end
  subgraph Server["ต้องการ API server จริง"]
    DryRun["kubectl apply --dry-run=server<br/>OpenAPI validation เต็มรูปแบบ + admission chain"]
    Diff["kubectl diff<br/>merge จริงกับ object ที่มีอยู่"]
    PSA["Pod Security Admission warn/enforce<br/>ทำงานทุก request จริง"]
    VAP["ValidatingAdmissionPolicy<br/>กฎ CEL เฉพาะองค์กร"]
    Harness["tools/verify-manifests.mjs<br/>การกวาด dry-run=server ของคอร์สนี้เอง"]
  end
  subgraph Runtime["ต้องการเวลาจริงหรือ request จริง"]
    Describe["kubectl describe / get events<br/>HPA unknown, drain ที่ถูกบล็อก, restart loop"]
    Curl["curl จากในคลัสเตอร์<br/>Service endpoint, ทิศทางของ NetworkPolicy, DNS"]
  end
เครื่องมือแบบ static พิสูจน์สิ่งต่าง ๆ จากซอร์สอย่างเดียว; เครื่องมือแบบ dynamic ต้องการคลัสเตอร์จริง, request จริง, หรือเวลาจริง
จาก output จริงนี้ ผล --dry-run=client ที่สะอาดพิสูจน์อะไรจริง ๆ เกี่ยวกับ strict field validation ฝั่ง server?
$ kubectl apply --dry-run=client -f hpa-v1-v2-mixed-shape.yaml
horizontalpodautoscaler.autoscaling/web created (dry run)
ทำไมโหมด default (non-strict) ของ kubeconform ถึงยอมรับ container ที่มี field privileged ซ้อนอยู่ตรงใต้มันเลย (แทนที่จะอยู่ใต้ securityContext) ในขณะที่ -strict ปฏิเสธมัน?
ทำไมโหมด warn ของ Pod Security Admission ถึงถูกบรรยายว่าเป็น 'linter ที่ทำงานจริง' แทนที่จะเป็นเครื่องมือระดับ CI แบบ kube-linter หรือ kubeconform?
ทำไม tools/verify-manifests.mjs ของคอร์สนี้เองถึง mark fence ส่วนใหญ่ของ 'โค้ดที่ AI มักเขียน' ใน AI Bug Catalog ด้วย # @expect-error <เหตุผล> แทนที่จะเป็น path comment ธรรมดา?