Network Policy
ไอเดียในหนึ่งประโยค
หัวข้อที่มีชื่อว่า “ไอเดียในหนึ่งประโยค”โดย default ทุก Pod ใน cluster คุยกับ Pod อื่นได้หมด และ NetworkPolicy คือสิ่งที่ให้คุณจำกัดให้เหลือแค่ traffic ที่อนุญาตไว้อย่างชัดเจนเท่านั้น
ค่า default: network แบบแบนราบและเปิดหมด
หัวข้อที่มีชื่อว่า “ค่า default: network แบบแบนราบและเปิดหมด”ตั้งแต่แกะกล่องมา networking ของ Kubernetes ไม่มี isolation เลย Pod ไหนก็ส่ง traffic ไปหา Pod อื่นได้หมด ไม่ว่าจะอยู่ namespace ไหน และแล้วแต่วิธี expose Service Pod ก็มักถูกเข้าถึงจากนอก cluster ได้ด้วย ไม่มี default-deny อยู่ที่ไหนเลย เรื่องนี้เข้าใจง่ายแต่หมายความว่า Pod ที่ถูก compromise หรือ config ผิดสามารถเข้าถึงส่วนอื่นของ cluster ได้มากกว่าที่ควรต้องการเยอะ
NetworkPolicy: เลือก Pod ก่อน แล้วค่อยอนุญาต traffic
หัวข้อที่มีชื่อว่า “NetworkPolicy: เลือก Pod ก่อน แล้วค่อยอนุญาต traffic”NetworkPolicy (API group networking.k8s.io/v1) เลือกกลุ่ม Pod ด้วย podSelector แล้วกำหนด rule ingress และ/หรือ egress ที่อนุญาตให้เฉพาะ Pod พวกนั้น
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: allow-frontend-to-api namespace: shopspec: podSelector: matchLabels: app: api policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: frontend ports: - protocol: TCP port: 8080policy นี้มีผลแค่กับ Pod ที่ label app: api ใน namespace shop และอนุญาต inbound traffic แค่จาก Pod ที่ label app: frontend เท่านั้น บน TCP port 8080 เท่านั้น อย่างอื่นทั้งหมดที่ยิงเข้า Pod พวกนี้ถูก deny โดยปริยายทันทีที่มีอย่างน้อยหนึ่ง policy เลือก Pod เหล่านั้น
kubectl get networkpolicy -n shopkubectl describe networkpolicy allow-frontend-to-api -n shopNetworkPolicy จะทำงานก็ต่อเมื่อ CNI บังคับใช้จริง
หัวข้อที่มีชื่อว่า “NetworkPolicy จะทำงานก็ต่อเมื่อ CNI บังคับใช้จริง”นี่คือข้อควรระวังที่สำคัญที่สุด NetworkPolicy object เฉย ๆ ไม่มีผลอะไรเลยถ้า CNI plugin ของ cluster ไม่ได้ implement การบังคับใช้ NetworkPolicy จริง Kubernetes รับและเก็บ object นี้ได้เสมอ kubectl apply จะสำเร็จ แต่ถ้า CNI ไม่บังคับใช้ policy traffic จริงก็ไม่เปลี่ยนอะไรเลย ต้องเช็คก่อนเสมอว่า CNI ที่ติดตั้งอยู่รองรับ NetworkPolicy ก่อนจะพึ่ง NetworkPolicy เพื่อทำ isolation
รูปแบบ default-deny
หัวข้อที่มีชื่อว่า “รูปแบบ default-deny”จุดที่คนสับสนกันบ่อย Pod ที่ไม่มี NetworkPolicy ตัวไหนเลือกเลย จะยังคงเปิดเต็มที่ (allow-all) สำหรับทิศทาง traffic นั้น isolation เป็นแบบ opt-in ต่อ Pod ไม่ใช่ทั้ง cluster ถ้าจะปิด namespace หนึ่งให้แน่นหนา ต้องสร้าง policy ที่เลือก Pod ทุกตัวแต่ไม่อนุญาตอะไรเลยโดยตั้งใจ
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: default-deny-ingress namespace: shopspec: podSelector: {} policyTypes: - Ingress ingress: []podSelector: {} match ทุก Pod ใน namespace และ ingress: [] ที่ว่างเปล่าไม่อนุญาตอะไรเข้ามาเลย พอ policy นี้มีอยู่ ทุก Pod ใน shop จะถูก isolate ด้าน ingress โดย default แล้วคุณค่อยเพิ่ม NetworkPolicy object อื่นเข้าไปเพื่ออนุญาต traffic ที่ต้องการจริง ๆ แบบเจาะจง เช่นตัวอย่าง allow-frontend-to-api ด้านบน
รวม podSelector กับ namespaceSelector เข้าด้วยกัน
หัวข้อที่มีชื่อว่า “รวม podSelector กับ namespaceSelector เข้าด้วยกัน”การใส่ทั้ง podSelector และ namespaceSelector ไว้ใน entry from เดียวกัน จะจำกัด rule ให้แคบลงเหลือแค่ Pod เจาะจงใน namespace เจาะจงเท่านั้น ไม่ใช่ “Pod ไหนก็ได้ใน namespace นี้” หรือ “label นี้ที่ไหนก็ได้ในทั้ง cluster”
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: allow-monitoring-scrape namespace: shopspec: podSelector: matchLabels: app: api policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: monitoring podSelector: matchLabels: app: prometheus ports: - protocol: TCP port: 9090rule นั้นอนุญาต traffic เฉพาะจาก Pod ที่ label app: prometheus และรันอยู่ใน namespace monitoring เท่านั้น เป็น rule ควบคุมการเข้าถึงข้าม namespace ที่เจาะจงมาก
ทุกอย่างด้านล่างพิสูจน์บน kind cluster ของโมดูลนี้เอง (storage-net) ที่มี Calico ติดตั้งอยู่ (v3.31.5 ผ่าน Tigera operator) เป็น CNI ที่บังคับใช้จริง — เลือกใช้เพราะ policy surface ที่ครอบคลุมกว่า (AdminNetworkPolicy/BaselineAdminNetworkPolicy พูดถึงด้านล่าง) ไม่ใช่เพราะ CNI default ของ kind เองบังคับใช้ไม่ได้: kindnet บังคับใช้ NetworkPolicy มาตั้งแต่ kind v0.24.0 แล้ว พิสูจน์ตรง ๆ ท้ายบทนี้
Default-deny (ingress กับ egress) พิสูจน์ด้วย timeout จริง
หัวข้อที่มีชื่อว่า “Default-deny (ingress กับ egress) พิสูจน์ด้วย timeout จริง”apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: default-deny-all namespace: netpol-demospec: podSelector: {} policyTypes: - Ingress - Egress$ kubectl exec -n netpol-demo client-a -- wget -q -T 3 -O- http://web.netpol-demo.svc.cluster.local<!DOCTYPE html><html><head>...
$ kubectl apply -f default-deny-all.yamlnetworkpolicy.networking.k8s.io/default-deny-all created
$ kubectl exec -n netpol-demo client-a -- wget -q -T 3 -O- http://web.netpol-demo.svc.cluster.localwget: bad address 'web.netpol-demo.svc.cluster.local'command terminated with exit code 1ก่อน policy คือ HTML จริง หลัง policy request ไม่ทันไปถึงขั้น HTTP connection fail เลยด้วยซ้ำ — bad address คือ DNS resolution ล้มเหลว: พอทั้ง Ingress และ Egress ถูก deny DNS query ของ client-a เองไปหา CoreDNS (port 53 UDP) ก็ถูกบล็อกด้วย ชื่อเลยไม่ resolve เลย นี่คือ outage จริงที่พบบ่อยที่สุดจาก default-deny พิสูจน์ตรง ๆ ไม่ใช่แค่กล่าวอ้าง
policyTypes ลืม Egress: egress ยังเปิดกว้างเหมือนเดิม
หัวข้อที่มีชื่อว่า “policyTypes ลืม Egress: egress ยังเปิดกว้างเหมือนเดิม”apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: ingress-only-example namespace: netpol-demospec: podSelector: matchLabels: app: web policyTypes: - Ingress ingress: - from: - podSelector: matchLabels: app: client-a$ kubectl exec -n netpol-demo web-5dcf5c46bf-hjnh8 -- getent hosts kubernetes.default.svc.cluster.local10.96.0.1 kubernetes.default.svc.cluster.local kubernetes.default.svc.cluster.localpolicy นี้มีแค่ Ingress ใน policyTypes — แม้จะเลือก Pod web ตัวเดียวกับที่เพิ่งถูกล็อกเต็มที่เมื่อครู่ — DNS lookup ขาออกของ web เองก็สำเร็จทันที พิสูจน์จริงว่าการลืม Egress ออกจาก policyTypes ทำให้ egress ของ Pod นั้นเปิดกว้างเต็มที่ ไม่ว่า ingress rule จะเขียนไว้ยังไงก็ตาม
DNS egress: rule ที่ default-deny ทุกอันต้องมี
หัวข้อที่มีชื่อว่า “DNS egress: rule ที่ default-deny ทุกอันต้องมี”apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: allow-dns-egress namespace: netpol-demospec: podSelector: {} policyTypes: - Egress egress: - to: - namespaceSelector: {} ports: - protocol: UDP port: 53 - protocol: TCP port: 53# with default-deny-all + allow-dns-egress both active:$ kubectl exec -n netpol-demo client-a -- nslookup kubernetes.default.svc.cluster.localServer: 10.96.0.10Address: 10.96.0.10:53
Name: kubernetes.default.svc.cluster.localAddress: 10.96.0.1
# but HTTP egress to web is still blocked -- no rule allows port 80$ kubectl exec -n netpol-demo client-a -- wget -q -T 3 -O- http://web.netpol-demo.svc.cluster.localwget: download timed outcommand terminated with exit code 1สังเกตว่า DNS lookup สำเร็จ รอบนี้ แต่ HTTP request ยัง timeout อยู่ดี (ไม่ใช่ bad address) — ชื่อ resolve ได้แล้ว แต่ TCP connection จริงไปที่ port 80 ยังถูก deny เพราะ allow-dns-egress เปิดแค่ port 53 เท่านั้น real failure mode สองแบบเคียงข้างกัน: ไม่มี DNS rule ทำให้ name resolution เองพัง; มี DNS rule แต่ไม่มี port-80 rule ทำให้ connection พังหลัง lookup สำเร็จ
namespaceSelector + podSelector: AND (entry from เดียว) เทียบกับ OR (สอง entry)
หัวข้อที่มีชื่อว่า “namespaceSelector + podSelector: AND (entry from เดียว) เทียบกับ OR (สอง entry)”apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: and-selector namespace: netpol-demospec: podSelector: matchLabels: app: web policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: netpol-other podSelector: matchLabels: app: client-b# and-selector active. client-b อยู่ใน netpol-other, label app: client-b -- match ทั้งสองเงื่อนไขใน entry เดียว:$ kubectl exec -n netpol-other client-b -- wget -q -T 3 -O- http://web.netpol-demo.svc.cluster.local<!DOCTYPE html><html><head>
# client-a อยู่ใน netpol-demo (ไม่ใช่ netpol-other) -- ไม่ผ่านครึ่ง namespaceSelector ของ AND:$ kubectl exec -n netpol-demo client-a -- wget -q -T 3 -O- http://web.netpol-demo.svc.cluster.localwget: download timed outcommand terminated with exit code 1apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: or-selector namespace: netpol-demospec: podSelector: matchLabels: app: web policyTypes: - Ingress ingress: - from: - namespaceSelector: matchLabels: kubernetes.io/metadata.name: netpol-other - podSelector: matchLabels: app: client-b# or-selector active (แค่ policy นี้ ไม่มีอันอื่น) client อีกตัวที่อยู่ใน netpol-demo เองเลย label app: client-b:$ kubectl exec -n netpol-demo client-b-local -- wget -q -T 3 -O- http://web.netpol-demo.svc.cluster.local<!DOCTYPE html><html><head>
# client-a (netpol-demo, label app: client-a) ไม่ match branch ไหนของ OR เลย -- ยังถูกบล็อก:$ kubectl exec -n netpol-demo client-a -- wget -q -T 3 -O- http://web.netpol-demo.svc.cluster.localwget: download timed outcommand terminated with exit code 1selector สอง field เดียวกัน แต่ผลลัพธ์จริงต่างกันสิ้นเชิงสองแบบ: entry from เดียวที่รวม namespaceSelector และ podSelector คือ AND (สองเงื่อนไขต้องเป็นของ source เดียวกัน) สอง entry แยกกัน แต่ละอันมี selector เดียว คือ OR (เงื่อนไขไหนก็ได้ก็พอ) client-b-local — namespace เดียวกับ web ไม่มีทาง match namespaceSelector ได้เลย — เข้าถึงได้แค่ตอน policy แบบ OR เท่านั้น พิสูจน์ตรง ๆ ด้านบน
ipBlock
หัวข้อที่มีชื่อว่า “ipBlock”apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: ipblock-allow namespace: netpol-demospec: podSelector: matchLabels: app: web policyTypes: - Ingress ingress: - from: - ipBlock: cidr: 10.244.0.0/16 except: - 10.244.1.0/24$ kubectl get pod client-a -n netpol-demo -o wideNAME IP NODEclient-a 10.244.147.21 storage-net-worker
$ kubectl exec -n netpol-demo client-a -- wget -q -T 3 -O- http://web.netpol-demo.svc.cluster.local<!DOCTYPE html><html><head>IP จริงของ client-a (10.244.147.21) อยู่ใน 10.244.0.0/16 และอยู่นอกช่วง exclude 10.244.1.0/24 เลยได้รับอนุญาต พิสูจน์ด้วย request ที่สำเร็จจริง ipBlock เลือกด้วย CIDR ดิบล้วน ๆ ไม่เกี่ยวกับ label เลย Pod ที่ถูกย้ายไป node อื่น (IP range คนละอัน) อาจหลุดเข้าหรือออกจาก rule ipBlock แบบเงียบ ๆ โดยไม่มี label เปลี่ยนเลยแม้แต่นิดเดียว — นี่คือจุดเปราะบางหลักของมันเทียบกับ label selector
การบังคับใช้ขึ้นอยู่กับ CNI — พิสูจน์บน kindnet ด้วยเช่นกัน
หัวข้อที่มีชื่อว่า “การบังคับใช้ขึ้นอยู่กับ CNI — พิสูจน์บน kindnet ด้วยเช่นกัน”NetworkPolicy object จะจริงแค่ไหนขึ้นอยู่กับ CNI plugin ที่บังคับใช้มันเท่านั้น และเรื่องนี้ไม่ใช่ค่าคงที่ข้าม CNI ทุกตัว หรือแม้แต่ข้าม version ของ CNI ตัวเดียวกัน — บาง CNI รับ object นี้ไว้เฉย ๆ แล้วไม่บังคับใช้อะไรเลย วิธีเดียวที่จะรู้แน่ชัดสำหรับ cluster หนึ่ง ๆ คือทำ connectivity test จริง ไม่ใช่แค่ kubectl get networkpolicy สำเร็จ และไม่ใช่แค่ดูชื่อ CNI
CNI default ของ kind เอง คือ kindnet (kindnetd) เป็นตัวอย่างที่ชัดที่สุด: version ก่อนหน้านี้ไม่มี NetworkPolicy controller เลยจริง ๆ — เป็นแค่ CNI แบบขั้นต่ำ bridge + host-local IPAM เท่านั้น เรื่องนี้เปลี่ยนไปตอน kind v0.24.0 (2024-08-15) ที่ release note เขียนไว้ว่า: “Out-of-the-box support for network policy via sigs.k8s.io/kube-network-policies” — kindnetd ตอนนี้รวม controller ตัวนั้นมาด้วยแล้ว บน shared cluster ปัจจุบัน (kind 0.33, kindnetd:v20260820-69b56db7) นี่คือเรื่องจริง พิสูจน์ตรง ๆ ไม่ใช่เดาจากเลข version ใน namespace ชั่วคราวที่สร้างมาเพื่อเช็คเรื่องนี้โดยเฉพาะแล้วลบทิ้งทันทีหลังจากนั้น
$ kubectl exec -n kindnet-fix-check busybox -- wget -q -T 3 -O- http://10.244.2.7<!DOCTYPE html><html><head><title>Welcome to nginx!</title><style>...
$ kubectl apply -f - <<'EOF'apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata: name: default-deny-ingressspec: podSelector: {} policyTypes: - IngressEOFnetworkpolicy.networking.k8s.io/default-deny-ingress created
$ kubectl exec -n kindnet-fix-check busybox -- wget -q -T 3 -O- http://10.244.2.7wget: download timed outcommand terminated with exit code 1HTTP response จริง แล้วก็ timeout จริงหลัง policy default-deny แบบ podSelector: {} / policyTypes: [Ingress] — บน kindnet บน cluster นี้เป๊ะ ไม่มี Calico เกี่ยวข้องเลย ความพยายามครั้งก่อนของ test เดียวกันนี้ใช้ Service DNS name ที่ไม่มี Service ตรงกันเลย แล้วได้ bad address ซึ่งเป็น name-resolution failure ที่ไม่บอกอะไรเรื่องการบังคับใช้ policy เลยทั้งสองทาง การยิงตรงไปที่ Pod IP เลยตัดความกำกวมนั้นออกไปหมด
บทเรียนตรงนี้ยังคงเป็น “พิสูจน์เสมอ” แต่มีเส้นแบ่ง version ติดมาด้วย: บน kind cluster ที่เก่ากว่า v0.24.0 (หรือปิด kindnetd แล้วไม่ใส่ CNI อื่นแทน) NetworkPolicy object เดียวกันเป๊ะนี้จะไม่เปลี่ยนอะไรเลย Calico ยังเป็น CNI หลักที่บังคับใช้จริงของบทนี้ เพราะ policy surface ที่ครอบคลุมกว่า — AdminNetworkPolicy/BaselineAdminNetworkPolicy (ด้านล่าง) และเป็น policy engine ที่โตเต็มที่เฉพาะทาง — ไม่ใช่เพราะ kindnet บังคับใช้ไม่ได้ในตอนนี้
AdminNetworkPolicy: ภาพรวม docs-only
หัวข้อที่มีชื่อว่า “AdminNetworkPolicy: ภาพรวม docs-only”การติดตั้ง operator bundle ของ Calico (operator-crds.yaml ที่ใช้ตั้ง CNI ที่บังคับใช้จริงสำหรับบทนี้) พา CRD policy.networking.k8s.io AdminNetworkPolicy/BaselineAdminNetworkPolicy มาด้วยเป็นผลพลอยได้ — ของจริง ตรวจสอบได้บน cluster นี้ (kubectl api-resources | grep networking.k8s.io แสดงให้เห็น) แต่การเขียนอันหนึ่งจริง ๆ อยู่นอก scope ความลึกของบทนี้ พูดสั้น ๆ ในย่อหน้าเดียว: AdminNetworkPolicy กับ BaselineAdminNetworkPolicy (จาก subproject sig-network-policy-api) คือ policy ระดับ cluster-admin ที่อยู่ เหนือ และ ใต้ NetworkPolicy แบบ namespaced ตามลำดับความสำคัญ — rule ของ AdminNetworkPolicy override สิ่งที่ NetworkPolicy ของเจ้าของ namespace เองจะอนุญาตได้ ส่วน BaselineAdminNetworkPolicy ตั้งค่า default ที่ NetworkPolicy ธรรมดายัง override ได้อยู่ ไม่ใช่ทุก CNI ที่ implement สองตัวนี้แล้ว ดู เอกสาร AdminNetworkPolicy ของ Kubernetes สำหรับสถานะ implementation ปัจจุบัน
อ่านโค้ดที่ AI เขียน
หัวข้อที่มีชื่อว่า “อ่านโค้ดที่ AI เขียน”ตอนรีวิว NetworkPolicy manifest ที่ AI สร้าง ให้เช็ค
- policy “default-deny” ที่มีแค่
policyTypes: [Ingress]comment อ้างว่าบล็อก traffic ทั้งหมด — พิสูจน์ด้านบนแล้วว่า egress ยังเปิดกว้างเหมือนเดิม - default-deny policy ที่ไม่มี DNS egress rule คู่กัน — พิสูจน์ด้านบนแล้วว่าทำลาย in-cluster DNS lookup ของทุก Pod ที่ถูกเลือก (เป็น
bad addressfailure ไม่ใช่ connection timeout) เป็น outage ที่พบบ่อยมากที่คนสร้างเอง - NetworkPolicy manifest ที่ apply แล้วถือว่า “verified working” แค่เพราะ
kubectl get networkpolicyสำเร็จ — พิสูจน์ด้านบน (หัวข้อการบังคับใช้ของ CNI) แล้วว่า object มีอยู่และถูกยอมรับได้โดยไม่บังคับใช้อะไรเลยบาง CNI หรือ CNI version เก่า ๆ การบังคับใช้ขึ้นอยู่กับ CNI ทั้งหมด และมีแค่ connectivity test จริงเท่านั้นที่พิสูจน์ได้ทั้งสองทาง - list
fromที่คิดว่าเป็น OR-ของ-AND ทั้งที่ผู้เขียนจริง ๆ เขียน entry เดียวรวมnamespaceSelector+podSelector(AND) กับตั้งใจให้เป็นสองเงื่อนไขแยกกัน (ต้องใช้สอง entry แยก) — พิสูจน์ด้านบนแล้วว่าให้ traffic ที่อนุญาตต่างกันจริง
🐛 หา bug ในโค้ดนี้
apiVersion: networking.k8s.io/v1kind: NetworkPolicymetadata:name: lockdown-shopnamespace: shopspec:podSelector: {}policyTypes: - Ingress - Egressingress: - from: - podSelector: matchLabels: app: frontendegress: - to: - podSelector: matchLabels: app: databaseเฉลย
policy แบบ default-deny นี้เลือกทุก Pod ใน shop ทั้ง Ingress และ Egress แต่ rule egress อนุญาตแค่ traffic ไปหา Pod ที่ label app: database เท่านั้น — พิสูจน์ด้านบนด้วย bad address failure จริงในสถานการณ์เดียวกันเป๊ะ นี่ทำลาย DNS ของทุก Pod ใน namespace เพราะไม่มีอะไรอนุญาต egress ไปหา CoreDNS บน port 53 เลย ทุก Pod ใน shop รวมถึง frontend และ database เองด้วย จะ resolve DNS name ไม่ได้เลยสักชื่อ — Service ที่พวกมันพึ่งพาด้วยชื่อจะเข้าถึงไม่ได้เลย โดย resolution failure ที่ดูไม่เหมือนปัญหาเรื่อง permission เลยตอนมองครั้งแรก ทางแก้: เพิ่ม egress rule อนุญาต UDP/TCP port 53 ไปหา kube-system (หรือแบบที่พิสูจน์ด้านบน ไปที่ namespaceSelector: {} เพื่อความง่าย) คู่กับ rule ของ database
Mental Model
หัวข้อที่มีชื่อว่า “Mental Model”NetworkPolicy คือคำขอที่ส่งให้ CNI ไม่ใช่การควบคุมที่ Kubernetes เองบังคับใช้ — kubectl apply สำเร็จพิสูจน์แค่ว่า object ถูกเก็บไว้เท่านั้น ไม่เคยพิสูจน์ว่า traffic เปลี่ยนไปจริง พิสูจน์ด้านบนทั้งสองทาง: policy object เดียวกันเป๊ะบล็อก traffic ได้จริงบน kindnet ปัจจุบัน แต่ไม่ทำอะไรเลยบน kindnet ก่อน kind v0.24.0 ภายใน ingress/egress rule ของ policy เดียว แต่ละ entry ใน list from/to คือ OR-branch ของตัวเอง และ selector field หลายตัว ภายใน entry เดียว ถูก AND เข้าด้วยกัน (พิสูจน์ด้านบนด้วยสอง policy จริงเทียบกัน) — นี่คือข้อเท็จจริงเรื่อง syntax หนึ่งเดียวที่ความลึกทั้งหมดของบทนี้แขวนอยู่กับมัน
ข้อแลกเปลี่ยน
หัวข้อที่มีชื่อว่า “ข้อแลกเปลี่ยน”| ทางเลือก | ข้อดี | ต้นทุน |
|---|---|---|
podSelector: {} + ingress: []/egress: [] ว่างเปล่า (default-deny) | baseline ที่ชัดเจน ตรวจสอบได้ — ไม่มีอะไรเข้า/ออกได้ถ้าไม่มี rule บอก | ทุก flow ที่ถูกต้อง รวมถึง DNS ต้องมี rule ของตัวเอง ไม่งั้น workload พัง พิสูจน์ด้านบนด้วย DNS failure จริง |
entry from เดียว สอง selector (AND) | แม่นยำ: label นี้ AND namespace นี้ พิสูจน์ด้านบน | เข้มงวดกว่าที่ผู้เขียนบางคนตั้งใจไว้ — เขียนง่ายผิดตอนตั้งใจให้เป็น “OR” |
สอง entry from แยกกัน (OR) | กว้างกว่า match “แหล่งไหนในนี้ก็ได้” พิสูจน์ด้านบน | เผลออนุญาตกว้างเกินไปง่าย ถ้า entry ใดกว้างกว่าที่ตั้งใจ |
ipBlock | allow/deny ด้วย CIDR ดิบ มีประโยชน์กับระบบภายนอกหรือระบบเก่าที่อิง IP | Pod IP ไม่ใช่ identity ที่เสถียร — พิสูจน์ด้านบนแล้วว่า ipBlock ที่ผูกกับ Pod IP ตรง ๆ เปราะบางกว่า label selector |
ความเข้าใจผิดที่พบบ่อย
หัวข้อที่มีชื่อว่า “ความเข้าใจผิดที่พบบ่อย”- “policyTypes: [Ingress] อย่างเดียวแปลว่า egress ก็ถูกจำกัดด้วย เพราะ policy ‘เกี่ยวกับ’ Pod นี้” พิสูจน์ด้านบนแล้ว — egress ยังเปิดกว้างเต็มที่ จนกว่า
EgressจะถูกระบุชัดเจนในpolicyTypesพร้อม rule ของตัวเอง - “default-deny policy บล็อกแค่ traffic ‘ไม่ดี’ DNS ยังทำงานได้เพราะเป็น infrastructure” พิสูจน์ด้านบนด้วย
bad addressfailure จริง — DNS (port 53) ก็แค่ egress destination อีกอันที่ถูกบล็อกเหมือนอย่างอื่นถ้าไม่มี allow rule ชัดเจน - “ถ้า kubectl apply ยอมรับ NetworkPolicy แปลว่ามันถูกบังคับใช้แล้ว” พิสูจน์ด้านบนแล้วทั้งสองทาง — การบังคับใช้ขึ้นอยู่กับ CNI และ version ของมันทั้งหมด: kindnet ไม่บังคับใช้อะไรเลยก่อน kind v0.24.0 บังคับใช้จริงตั้งแต่นั้นมา (พิสูจน์ตรง ๆ ด้านบน) และ CNI ไหนก็ตามยังต้องใช้ connectivity test จริงเพื่อยืนยัน ไม่ใช่แค่
applyสำเร็จ - “namespaceSelector กับ podSelector ใน entry from เดียวกันเหมือนกับเขียนแยกสอง entry” พิสูจน์ด้านบนด้วยสอง policy จริงที่ตรงข้ามกัน — entry เดียว AND เงื่อนไขเข้าด้วยกัน (label นี้ AND namespace นี้) สอง entry แยกกัน OR (เงื่อนไขไหนก็ได้)
flowchart LR
subgraph before["Before: default flat network"]
a1["Pod A"] --- a2["Pod B"]
a2 --- a3["Pod C"]
a1 --- a3
end
subgraph after["After: NetworkPolicy applied"]
b1["frontend"] -->|"allowed"| b2["api"]
b3["random Pod"] -.->|"blocked"| b2
end