kubectl และ Declarative YAML
ไอเดียหลักในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียหลักในหนึ่งประโยค”kubectl คือวิธีที่คุณส่ง desired state ให้ cluster และตรวจดูว่ากำลังทำอะไรอยู่ และเกือบทุกอย่างที่คุณส่งให้ cluster คือ YAML manifest ที่สร้างจากสี่ field หลักชุดเดียวกัน
พื้นฐาน kubectl คือ context, namespace และ verb
หัวข้อที่มีชื่อว่า “พื้นฐาน kubectl คือ context, namespace และ verb”kubeconfig ของคุณเก็บ credential ของหลาย cluster พร้อมกันได้ จัดกลุ่มเป็น context สลับว่าคำสั่งของคุณจะยิงไปที่ตัวไหนด้วย
kubectl config use-context my-clusterkubectl get pods -n my-namespaceflag -n (หรือ --namespace) จำกัดขอบเขตคำสั่งให้อยู่ใน namespace เดียว ถ้าไม่ใส่จะยิงไปยัง namespace default ของ context ปัจจุบัน ซึ่งปกติคือ default นอกจากนี้ verb ชุดเล็ก ๆ ก็ครอบคลุมการใช้งาน kubectl ในชีวิตประจำวันส่วนใหญ่แล้ว
kubectl get podskubectl describe pod web-7d9f8c6b7-abcdekubectl logs web-7d9f8c6b7-abcdekubectl exec -it web-7d9f8c6b7-abcde -- shget แสดงรายการ object describe แสดงสถานะละเอียดและ event ล่าสุดของ object หนึ่งตัว (จุดแรกที่ควรดูเมื่อมีอะไรผิดปกติ) logs stream stdout/stderr ของ container ส่วน exec รันคำสั่งข้างใน container ที่กำลังรันอยู่ เหมาะเวลาต้องการ shell เร็ว ๆ เข้าไปดูข้างใน
Manifest ทุกอันมีสี่ field หลัก
หัวข้อที่มีชื่อว่า “Manifest ทุกอันมีสี่ field หลัก”ไม่ว่าจะสร้าง object แบบไหน Pod, Deployment, Service หรืออะไรก็ตาม YAML ของตัวเองมีโครงเดียวกัน
apiVersion: v1kind: Podmetadata: name: web labels: app: webspec: containers: - name: web image: nginx:1.31 ports: - containerPort: 80apiVersion กับ kind บอกว่าคุณหมายถึง API group และ object type ไหน metadata เก็บตัวตนและข้อมูลบัญชี เช่น name, namespace (ถ้ามี) และ labels กับ annotations ที่คุณติดเอง spec คือ desired state ที่คุณเขียนขึ้น ทุกอย่างข้างบนคือ spec ของ Pod ตัวนี้ Kubernetes เพิ่ม field ที่ห้าให้เองคือ status เมื่อ object ถูกสร้างแล้ว field นี้คุณไม่เคยเขียนเอง อ่านได้อย่างเดียว เพราะสะท้อนสิ่งที่สังเกตเห็นจริง ไม่ใช่สิ่งที่คุณขอ
kubectl explain: เช็กว่า field มีอยู่จริงไหม
หัวข้อที่มีชื่อว่า “kubectl explain: เช็กว่า field มีอยู่จริงไหม”AI (หรือ YAML จากบล็อกเก่า ๆ ที่จำมาแบบครึ่ง ๆ กลาง ๆ) จะแต่ง field ที่ฟังดูสมเหตุสมผลแต่ไม่เคยมีอยู่จริงได้อย่างมั่นใจ kubectl explain <kind>.<field.path> ถาม OpenAPI schema ของ API server ตัวจริงว่า field นี้มีจริงไหม — เป็นวิธีที่เร็วที่สุดในการเช็ก manifest ที่ AI สร้างก่อนจะ apply จริง field จริงบน cluster 1.37
$ kubectl explain deployment.spec.strategy.rollingUpdate.maxSurgeGROUP: appsKIND: DeploymentVERSION: v1
FIELD: maxSurge <IntOrString>
DESCRIPTION: The maximum number of pods that can be scheduled above the desired number of pods. Value can be an absolute number (ex: 5) or a percentage of desired pods (ex: 10%). This can not be 0 if MaxUnavailable is 0. Absolute number is calculated from percentage by rounding up. Defaults to 25%.field ที่แต่งขึ้นมาเอง บน cluster ตัวเดียวกัน
$ kubectl explain deployment.spec.strategy.rollingUpdate.maxSurgePercentageOfNodesGROUP: appsKIND: DeploymentVERSION: v1
error: field "maxSurgePercentageOfNodes" does not existerror: field "..." does not exist คือสัญญาณตรง ๆ ว่า manifest ที่ generate มาอ้าง field ที่ไม่เคยอยู่ใน schema เลย — ไม่มีชื่อ field ที่ฟังดูสมเหตุสมผลตัวไหนรอด kubectl explain ได้ เพราะมันอ่าน schema ตัวเดียวกับที่ API server ใช้ validate จริง ไม่ใช่ documentation ที่อาจเก่าไปแล้ว
apply กับ create และ label คือกลไกเชื่อมโยง
หัวข้อที่มีชื่อว่า “apply กับ create และ label คือกลไกเชื่อมโยง”kubectl apply -f file.yaml เป็นแบบ declarative โดย diff ไฟล์ของคุณกับ last-applied configuration ของ object (ที่ถูก track ไว้ใน annotation) แล้ว patch เฉพาะส่วนที่เปลี่ยน และสร้าง object ให้ถ้ายังไม่มี ทำให้รันคำสั่งเดิมซ้ำ ๆ ได้อย่างปลอดภัย นั่นคือสิ่งที่ต้องการพอดีใน script และ CI ส่วน kubectl create -f file.yaml เป็นแบบ imperative แทน คือสร้าง object ครั้งเดียวและจะ error ถ้ามี object นั้นอยู่แล้ว คอร์สนี้ใช้ apply ตลอดทั้งเล่ม
kubectl apply -f pod.yamlkubectl create -f pod.yamllabel คือคู่ key-value ตามใจที่คุณติดไว้บน object เช่น app: web ใน Pod ด้านบน ตัว label เองไม่ทำอะไรเลย พลังของ label มาจาก selector นั่นคือสิ่งที่ object อื่นใช้หา Pod ที่ถูกติด label แบบนั้น Service ที่มี selector: { app: web } จะ route traffic ไปยังทุก Pod ที่มี label นั้น ส่วน ReplicaSet ของ Deployment ใช้ matchLabels selector เพื่อรู้ว่า Pod ไหนคือ Pod “ของตัวเอง” ที่ต้องรักษาให้อยู่ตาม replica count ที่ต้องการ pattern label-กับ-selector นี้คือวิธีพื้นฐานที่สุดที่ object ใน Kubernetes ใช้หากันเจอ และคุณจะเห็น pattern นี้อีกในเกือบทุก module ถัดไป
สร้างโครง manifest แทนการเขียนเองทั้งหมด
หัวข้อที่มีชื่อว่า “สร้างโครง manifest แทนการเขียนเองทั้งหมด”kubectl create --dry-run=client -o yaml สร้าง manifest จริงจาก flag โดยไม่ส่งอะไรไปที่ cluster เลย — จุดเริ่มต้นที่เร็วและถูกต้อง แทนที่จะ copy YAML เก่ามาแล้วหวังว่าทุก field จะยังใช้ได้
kubectl create deployment web --image=nginx:1.31 --replicas=3 --dry-run=client -o yamloutput จริง
apiVersion: apps/v1kind: Deploymentmetadata: labels: app: web name: webspec: replicas: 3 selector: matchLabels: app: web strategy: {} template: metadata: labels: app: web spec: containers: - image: nginx:1.31 name: nginx resources: {}status: {}kubectl diff: เห็นการเปลี่ยนแปลงก่อนจะเปลี่ยนจริง
หัวข้อที่มีชื่อว่า “kubectl diff: เห็นการเปลี่ยนแปลงก่อนจะเปลี่ยนจริง”kubectl diff -f <file> แสดงสิ่งที่ apply จะเปลี่ยน โดยไม่เปลี่ยนอะไรจริง ๆ — รันก่อน apply ทุกครั้งใน script หรือ CI pipeline ที่คุณยังไม่มั่นใจเต็มร้อย output จริงตอน diff manifest Deployment ใหม่กับ cluster ที่ยังไม่มี object นี้อยู่เลย (การสร้างใหม่จะแสดงเป็นไฟล์ใหม่ทั้งไฟล์ exit code ของ diff ไม่เป็นศูนย์ทุกครั้งที่มีการเปลี่ยนแปลง)
$ kubectl diff -f web-deploy.yamldiff -u -N /tmp/LIVE-.../apps.v1.Deployment.foundations-demo.web /tmp/MERGED-.../apps.v1.Deployment.foundations-demo.web--- /tmp/LIVE-.../apps.v1.Deployment.foundations-demo.web+++ /tmp/MERGED-.../apps.v1.Deployment.foundations-demo.web@@ -0,0 +1,43 @@+apiVersion: apps/v1+kind: Deployment+metadata:+ creationTimestamp: "2026-09-26T08:31:54Z"+ name: web+ namespace: foundations-demo+spec:+ progressDeadlineSeconds: 600+ replicas: 3+ revisionHistoryLimit: 10+ selector:+ matchLabels:+ app: web+ strategy:+ rollingUpdate:+ maxSurge: 25%+ maxUnavailable: 25%...$ echo $?1Server-Side Apply: API server เป็นคนทำ merge ไม่ใช่เครื่องคุณ
หัวข้อที่มีชื่อว่า “Server-Side Apply: API server เป็นคนทำ merge ไม่ใช่เครื่องคุณ”kubectl apply แบบเดิม — client-side apply — คำนวณ three-way merge บนเครื่องคุณเอง โดยเทียบไฟล์ของคุณ, object ที่ live อยู่ และสำเนา config ล่าสุดที่ตัวเองเคย apply (เก็บไว้ใน annotation kubectl.kubernetes.io/last-applied-configuration บน object เอง) annotation นี้มีอยู่ก็เพราะ merge logic รันบนเครื่องคุณ และไม่มีทางอื่นให้จำว่ารอบที่แล้ว apply อะไรไปบ้าง
Server-side apply (--server-side, GA ตั้งแต่ 1.22) ย้าย merge นั้นไปที่ API server แทน ไม่ใช่ blob เดียวที่บอก “ทุกอย่างที่ apply ไปรอบที่แล้ว” แต่ API server track field ownership ต่อ manager ใน metadata.managedFields — ว่า manager ไหนตั้ง field ไหนล่าสุด และตั้งผ่าน Apply หรือ Update เพราะ ownership ถูก track ต่อ field บน server เอง server-side apply จึงไม่ต้องใช้ annotation last-applied-configuration เลย ไม่มี diff ฝั่ง client ให้ต้องสร้างใหม่ เพราะ server เป็นเจ้าของข้อมูลที่แท้จริงอยู่แล้ว
kubectl apply --server-side -f web-deploy.yamlmanagedFields จริงหลังจาก kubectl apply แบบธรรมดา (client-side) สังเกตชื่อ manager กับ operation: Update
$ kubectl get deployment web -o yaml --show-managed-fields managedFields: - manager: kubectl-client-side-apply operation: Update apiVersion: apps/v1 fieldsV1: f:spec: f:replicas: {} f:selector: {} f:template: {...} - manager: kube-controller-manager operation: Update subresource: statusfield-manager conflict จริง
หัวข้อที่มีชื่อว่า “field-manager conflict จริง”field ownership จะป้องกันได้ก็ต่อเมื่อ manager คนละตัว พยายามเปลี่ยน field ที่มีเจ้าของอยู่แล้ว — และนี่คือเคสที่ script ที่ generate มาอาจเจอได้จริง คือ apply object เดียวกันด้วย kubectl apply (client-side) ก่อน แล้วตามด้วย kubectl apply --server-side --field-manager=someone-else (manager คนละตัว เปลี่ยน replicas จาก 3 เป็น 5) โดยไม่มี force flag เลย
$ kubectl apply --server-side --field-manager=someone-else -f web-deploy-changed.yamlerror: Apply failed with 1 conflict: conflict with "kubectl-client-side-apply" using apps/v1: .spec.replicasPlease review the fields above--they currently have other managers. Hereare the ways you can resolve this warning:* If you intend to manage all of these fields, please re-run the apply command with the `--force-conflicts` flag.* If you do not intend to manage all of the fields, please edit your manifest to remove references to the fields that should keep their current managers.* You may co-own fields by updating your manifest to match the existing value; in this case, you'll become the manager if the other manager(s) stop managing the field (remove it from their configuration).นั่นคือ error ของ API server ตัวจริง เป๊ะ ๆ — พิสูจน์บน cluster 1.37 จริง ไม่ใช่เดารูปแบบข้อความเอาเอง --force-conflicts เข้ามายึด ownership แทน
$ kubectl apply --server-side --field-manager=someone-else --force-conflicts -f web-deploy-changed.yamldeployment.apps/web serverside-appliedมีจุดที่พลาดง่ายจุดหนึ่งที่สำคัญ คือถ้าคุณไม่ใส่ --field-manager เลย kubectl apply --server-side จะใช้ชื่อ field manager default เป็น kubectl — และ kubectl ปฏิบัติต่อ default ตัวนี้เป็น เส้นทาง migration ที่ตั้งใจไว้ จาก client-side apply คือยึด field ที่เคยเป็นของ kubectl-client-side-apply โดยไม่มี conflict เลย conflict ข้างบนเกิดขึ้นก็เพราะ --field-manager=someone-else เป็นชื่อ manager ที่ไม่ได้อยู่ใน migration compatibility ที่ built-in มาให้ พูดอีกแบบคือ การ migrate object ของตัวเองจาก kubectl apply ไป kubectl apply --server-side (ใช้ default manager ตัวเดียวกัน) จะไม่มี conflict โดยการออกแบบ ส่วนเครื่องมือหรือทีมที่ต่างกันจริง ๆ apply object เดียวกันต่างหากคือกรณีที่ควรคาดหวังและอยากได้ conflict
kubectl apply --prune: ยังเป็น Alpha ใน kubectl 1.36
หัวข้อที่มีชื่อว่า “kubectl apply --prune: ยังเป็น Alpha ใน kubectl 1.36”--prune ลบ object ที่เคยถูกสร้างจาก apply รอบก่อนแต่ไม่มีอยู่ในไฟล์ของคุณแล้ว มีประโยชน์ถ้า “directory คือ source of truth” แต่อันตรายถ้า label selector ผิด เช็กตรงจาก help text ของ kubectl 1.36 เอง
$ kubectl apply --help Alpha Disclaimer: the --prune functionality is not yet complete. Do not use unless you are aware of what the current state is. See https://issues.k8s.io/34274.ยังเป็น Alpha ใน kubectl 1.36 — ไม่ได้ deprecated ไม่ได้ GA เหมือนที่ disclaimer บอกมาหลายปีแล้ว ควรอ่านทำความเข้าใจไว้ก่อน ไม่ควรรันแบบไม่มีคนดูใน CI โดยไม่มี --prune-allowlist และ label selector ที่รอบคอบจริง ๆ
อ่าน state กลับออกมา: jsonpath กับ custom-columns
หัวข้อที่มีชื่อว่า “อ่าน state กลับออกมา: jsonpath กับ custom-columns”kubectl get -o jsonpath กับ -o custom-columns ดึงเฉพาะ field ที่ต้องการออกมาจาก object แทนที่จะต้อง parse YAML ทั้งก้อนใน script
kubectl get deployment web -o custom-columns='NAME:.metadata.name,REPLICAS:.spec.replicas,IMAGE:.spec.template.spec.containers[0].image'kubectl get deployment web -o jsonpath='{.metadata.name}{"\t"}{.spec.replicas}{"\t"}{.spec.template.spec.containers[0].image}{"\n"}'output จริง เทียบกัน
$ kubectl get deployment web -o custom-columns='NAME:.metadata.name,REPLICAS:.spec.replicas,IMAGE:.spec.template.spec.containers[0].image'NAME REPLICAS IMAGEweb 5 nginx:1.31
$ kubectl get deployment web -o jsonpath='{.metadata.name}{"\t"}{.spec.replicas}{"\t"}{.spec.template.spec.containers[0].image}{"\n"}'web 5 nginx:1.31custom-columns อ่านง่ายกว่าเวลามองผ่าน ๆ ส่วน jsonpath เอาไปต่อใน shell script ที่ต้องการค่าเดี่ยว ๆ ได้ดีกว่า
flowchart LR cmd["kubectl apply -f pod.yaml"] --> api["kube-apiserver"] api --> etcd["etcd"] api --> ctrl["Controllers notice the change and react"]
ตรวจ YAML แบบ static
หัวข้อที่มีชื่อว่า “ตรวจ YAML แบบ static”Manifest ที่ AI สร้างแล้ว “ดูถูกต้อง” อาจมี field อยู่ผิดที่ได้ — privileged ต้องอยู่ใต้ securityContext ของ container ไม่ใช่วางตรง ๆ บน container และการเยื้องบรรทัด (indentation) ก็ไม่ได้ทำให้เห็นว่าผิดตรงไหนเลย แก้ manifest ด้านล่างแล้วกด Check เพื่อตรวจในเบราว์เซอร์ของคุณเทียบกับ Kubernetes 1.37 OpenAPI schema ที่คอร์สนี้ bundle ไว้ (สร้างจาก cluster จริง ไม่ได้พิมพ์เอง):
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”ตอนรีวิว manifest หรือลำดับคำสั่ง kubectl ที่ AI สร้าง เช็กสิ่งเหล่านี้
- ชื่อ field ที่ฟังดูถูกแต่ไม่ใช่ — เช็กด้วย
kubectl explain <kind>.<field.path>กับ cluster จริง พิสูจน์ข้างบนแล้วว่า field จริงจะพิมพ์คำอธิบายออกมา ส่วน field ที่แต่งขึ้นจะพิมพ์error: field "..." does not exist - ค่า
apiVersionจาก training data เก่า (extensions/v1beta1สำหรับ Deployment/Ingress,apps/v1beta1,apps/v1beta2) — ถูกถอดไปหลายปีแล้ว API server จะปฏิเสธตรง ๆ ไม่ได้ downgrade behavior ให้เงียบ ๆ - ลำดับคำสั่ง imperative (
kubectl run,kubectl expose,kubectl scale) ที่ถูก paste ลงใน runbook หรือ CI script ราวกับเป็น “infrastructure” — ไม่มีอันไหน re-runnable หรือ diff ได้เหมือน manifest บวกapply/diffและไม่มีไฟล์ให้ review - object เดียวกันที่ถูก apply ด้วย
kubectl applyธรรมดาที่หนึ่ง กับkubectl apply --server-sideอีกที่หนึ่ง (เช่น เครื่อง human กับ GitOps controller) — พิสูจน์ข้างบนแล้วว่าผสมสองแบบนี้คือสิ่งที่ทำให้เกิด field-manager conflict ทันทีที่ชื่อ field manager ของทั้งคู่ต่างจาก default migration-compatible ของ kubectl --forceหรือ--force-conflictsที่ถูกใส่เข้าไป “แก้” conflict โดยไม่อ่านเลยว่า field ไหนขัดกัน — พิสูจน์ข้างบนแล้วว่า error ของ conflict บอกชื่อ field และชื่อ manager อีกฝั่งตรง ๆ อยู่แล้ว force ควรเป็นการตัดสินใจว่า “ใช่ อยากได้ field นี้” ไม่ใช่ reflexkubectl apply -f ./manifests/ที่ apply กับทั้ง directory ที่ดันมีvalues.yamlหรือ YAML ที่ไม่ใช่ manifest ปนอยู่ (config file ของ Helm chart เอง, config ของ CI) —apply -fกับ directory ไม่รู้ความต่างและจะพยายาม apply ทุก.yamlที่เจอ
🐛 หา bug ในโค้ดนี้
# A "safety" script someone added before every deploykubectl apply --server-side --force-conflicts -f web-deploy.yamlเฉลย
สคริปต์นี้ “แก้” ทุก conflict โดยไม่เคยอ่านเลยว่ายึด ownership อะไรไปบ้าง พิสูจน์ข้างบนแล้วว่า error ของ conflict จริงบอกชื่อ field ที่ขัดกัน (.spec.replicas) และชื่อ manager อีกฝั่ง ("kubectl-client-side-apply") ตรง ๆ อยู่แล้ว
error: Apply failed with 1 conflict: conflict with "kubectl-client-side-apply" using apps/v1: .spec.replicas--force-conflicts ควรถูกพิมพ์หลังจากอ่าน message นั้นแล้วตัดสินใจว่า manager ตัวนี้ควรเป็นเจ้าของ field นั้นจริง ๆ ไม่ใช่ paste ใส่ deploy script ทุกอันเพื่อให้ error หายไปเฉย ๆ ถ้าใช้แบบไม่มีเงื่อนไข มันจะยึด ownership ของ field ใด ๆ ที่ manager อื่น (การ kubectl apply ของ human, controller, pipeline คนละตัว) ถืออยู่แบบเงียบ ๆ รวมถึง field ที่ manifest นี้ไม่เคยตั้งใจจะแตะ ถ้าไฟล์นี้ถูกแก้กลับมาเพิ่ม field นั้นโดยไม่ตั้งใจในภายหลัง
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”kubectl คือตัวแปลบาง ๆ ที่ไม่มี state ระหว่างคุณกับแหล่งความจริงเดียวที่มีอำนาจ คือ schema ของ API server เองกับ object ที่มันเก็บไว้ ทุก feature ในบทนี้จริง ๆ แล้วคือไอเดียเดียวกันที่ถูกใช้ในจุดต่าง ๆ — explain ถาม schema ว่า “field นี้มีจริงไหม” diff ถาม server ว่า “อะไรจะเปลี่ยน” apply ขอให้ server เปลี่ยนจริง และ server-side apply ขอให้ server จำด้วยว่า ใคร เป็นคนขอ ไม่มีอะไรในนี้คือความฉลาดฝั่ง client เลย ข้อยกเว้นเดียวคือ annotation last-applied-configuration ของ client-side apply ซึ่งมีอยู่ก็เพราะ client (ไม่ใช่ server) เคยเป็นคนทำ diff เอง — server-side apply เอา need นั้นออกไปโดยย้าย bookkeeping ไปที่จุดเดียวที่มี authority อยู่แล้วตั้งแต่แรก
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ทางเลือก | ข้อดี | ต้นทุน |
|---|---|---|
kubectl apply (client-side) | ใช้ได้กับ cluster เก่า ไม่ต้องมี server feature | Diff เกิดขึ้นบนเครื่องเทียบกับ annotation last-applied-configuration ที่ drift หรือถูกทับได้ (พิสูจน์ข้างบน: rollout undo ใน บทเรื่อง Deployments เตือนเรื่องนี้ตรง ๆ) |
kubectl apply --server-side | field ownership ชัดเจนและ server เป็นคน authoritative conflict หลายคนเขียนคือ error จริงที่มีชื่อ แทนที่จะเขียนทับกันเงียบ ๆ (พิสูจน์ข้างบน) | conflict จริงจะหยุด apply จนกว่าคุณจะตัดสินใจ --force-conflicts หรือแก้ manifest — เป็นการตัดสินใจเพิ่มที่ client-side apply ไม่เคยบังคับ |
รัน kubectl diff -f ก่อน apply ทุกครั้ง | จับการเปลี่ยนแปลงที่ไม่คาดคิดได้ก่อน ทั้งใน CI และตอนรันมือ | เพิ่มขั้นตอนอีกหนึ่งใน loop ต้องมีสิทธิ์อ่าน cluster ทุกที่ที่รัน |
kubectl apply --prune | คำสั่งเดียวรักษา object ทั้ง directory ให้ sync กัน รวมถึงลบด้วย | ยังเป็น Alpha ใน kubectl 1.36 (พิสูจน์ข้างบน) label selector ผิดอาจลบเกินที่ตั้งใจ |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “Server-side apply ก็แค่ client-side apply ที่ไปรันบน server” พิสูจน์ข้างบนแล้วว่าทั้งสองใช้ bookkeeping คนละแบบเลย — client-side apply สร้าง diff ใหม่จาก annotation
last-applied-configurationที่ตัวเองดูแลบนเครื่อง ส่วน server-side apply track field ownership ต่อ field ในmanagedFieldsและไม่ต้องมี annotation แบบนั้นเลย - “Migrate object จาก
kubectl applyไปkubectl apply --server-sideจะเจอ field-manager conflict เสมอ” พิสูจน์ข้างบนแล้วว่าไม่ใช่ โดยการออกแบบ — ชื่อ field manager default ของ--server-sideใน kubectl เองถูกปฏิบัติเป็นเส้นทาง migration จากkubectl-client-side-applyโดยไม่มี conflict conflict จะเกิดก็ต่อเมื่อ field manager คนละชื่อจริง ๆ มาแตะ field ที่มีเจ้าของอยู่แล้ว - “
--force-conflictsคือวิธีแก้ที่ถูกต้องทุกครั้งที่apply --server-sideคืน error” พิสูจน์ข้างบนแล้วว่า error บอกชื่อ field และชื่อเจ้าของปัจจุบันตรง ๆ อยู่แล้ว — สิ่งที่ควรทำคือตัดสินใจว่าควรเป็นเจ้าของ field นั้นจริงไหม ไม่ใช่ force ทุก conflict แบบ reflex - “
kubectl explainอ่าน documentation แบบ static เลยเก่าได้เหมือน doc ทั่วไป” พิสูจน์ข้างบนแล้วว่ามันถาม OpenAPI schema ของ API server ตัวจริง — schema เดียวกับที่applyใช้ validate ดังนั้น field ที่มันยอมรับรับประกันว่ามีอยู่จริงบน cluster version นั้นเป๊ะ ๆ ไม่เหมือนบล็อกหรือ training data ของโมเดลที่อาจเก่าไปแล้ว