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

Network Policy

โดย default ทุก Pod ใน cluster คุยกับ Pod อื่นได้หมด และ NetworkPolicy คือสิ่งที่ให้คุณจำกัดให้เหลือแค่ traffic ที่อนุญาตไว้อย่างชัดเจนเท่านั้น

ตั้งแต่แกะกล่องมา networking ของ Kubernetes ไม่มี isolation เลย Pod ไหนก็ส่ง traffic ไปหา Pod อื่นได้หมด ไม่ว่าจะอยู่ namespace ไหน และแล้วแต่วิธี expose Service Pod ก็มักถูกเข้าถึงจากนอก cluster ได้ด้วย ไม่มี default-deny อยู่ที่ไหนเลย เรื่องนี้เข้าใจง่ายแต่หมายความว่า Pod ที่ถูก compromise หรือ config ผิดสามารถเข้าถึงส่วนอื่นของ cluster ได้มากกว่าที่ควรต้องการเยอะ

NetworkPolicy (API group networking.k8s.io/v1) เลือกกลุ่ม Pod ด้วย podSelector แล้วกำหนด rule ingress และ/หรือ egress ที่อนุญาตให้เฉพาะ Pod พวกนั้น

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-frontend-to-api
namespace: shop
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 8080

policy นี้มีผลแค่กับ Pod ที่ label app: api ใน namespace shop และอนุญาต inbound traffic แค่จาก Pod ที่ label app: frontend เท่านั้น บน TCP port 8080 เท่านั้น อย่างอื่นทั้งหมดที่ยิงเข้า Pod พวกนี้ถูก deny โดยปริยายทันทีที่มีอย่างน้อยหนึ่ง policy เลือก Pod เหล่านั้น

Terminal window
kubectl get networkpolicy -n shop
kubectl describe networkpolicy allow-frontend-to-api -n shop

นี่คือข้อควรระวังที่สำคัญที่สุด NetworkPolicy object เฉย ๆ ไม่มีผลอะไรเลยถ้า CNI plugin ของ cluster ไม่ได้ implement การบังคับใช้ NetworkPolicy จริง Kubernetes รับและเก็บ object นี้ได้เสมอ kubectl apply จะสำเร็จ แต่ถ้า CNI ไม่บังคับใช้ policy traffic จริงก็ไม่เปลี่ยนอะไรเลย ต้องเช็คก่อนเสมอว่า CNI ที่ติดตั้งอยู่รองรับ NetworkPolicy ก่อนจะพึ่ง NetworkPolicy เพื่อทำ isolation

จุดที่คนสับสนกันบ่อย Pod ที่ไม่มี NetworkPolicy ตัวไหนเลือกเลย จะยังคงเปิดเต็มที่ (allow-all) สำหรับทิศทาง traffic นั้น isolation เป็นแบบ opt-in ต่อ Pod ไม่ใช่ทั้ง cluster ถ้าจะปิด namespace หนึ่งให้แน่นหนา ต้องสร้าง policy ที่เลือก Pod ทุกตัวแต่ไม่อนุญาตอะไรเลยโดยตั้งใจ

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
namespace: shop
spec:
podSelector: {}
policyTypes:
- Ingress
ingress: []

podSelector: {} match ทุก Pod ใน namespace และ ingress: [] ที่ว่างเปล่าไม่อนุญาตอะไรเข้ามาเลย พอ policy นี้มีอยู่ ทุก Pod ใน shop จะถูก isolate ด้าน ingress โดย default แล้วคุณค่อยเพิ่ม NetworkPolicy object อื่นเข้าไปเพื่ออนุญาต traffic ที่ต้องการจริง ๆ แบบเจาะจง เช่นตัวอย่าง allow-frontend-to-api ด้านบน

การใส่ทั้ง podSelector และ namespaceSelector ไว้ใน entry from เดียวกัน จะจำกัด rule ให้แคบลงเหลือแค่ Pod เจาะจงใน namespace เจาะจงเท่านั้น ไม่ใช่ “Pod ไหนก็ได้ใน namespace นี้” หรือ “label นี้ที่ไหนก็ได้ในทั้ง cluster”

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-monitoring-scrape
namespace: shop
spec:
podSelector:
matchLabels:
app: api
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: monitoring
podSelector:
matchLabels:
app: prometheus
ports:
- protocol: TCP
port: 9090

rule นั้นอนุญาต 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 แล้ว พิสูจน์ตรง ๆ ท้ายบทนี้

k8s/network-policies/default-deny-all.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: netpol-demo
spec:
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.yaml
networkpolicy.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.local
wget: 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 พิสูจน์ตรง ๆ ไม่ใช่แค่กล่าวอ้าง

k8s/network-policies/ingress-only-example.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ingress-only-example
namespace: netpol-demo
spec:
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.local
10.96.0.1 kubernetes.default.svc.cluster.local kubernetes.default.svc.cluster.local

policy นี้มีแค่ Ingress ใน policyTypes — แม้จะเลือก Pod web ตัวเดียวกับที่เพิ่งถูกล็อกเต็มที่เมื่อครู่ — DNS lookup ขาออกของ web เองก็สำเร็จทันที พิสูจน์จริงว่าการลืม Egress ออกจาก policyTypes ทำให้ egress ของ Pod นั้นเปิดกว้างเต็มที่ ไม่ว่า ingress rule จะเขียนไว้ยังไงก็ตาม

k8s/network-policies/allow-dns-egress.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns-egress
namespace: netpol-demo
spec:
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.local
Server: 10.96.0.10
Address: 10.96.0.10:53
Name: kubernetes.default.svc.cluster.local
Address: 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.local
wget: download timed out
command 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 สำเร็จ

k8s/network-policies/and-selector.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: and-selector
namespace: netpol-demo
spec:
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.local
wget: download timed out
command terminated with exit code 1
k8s/network-policies/or-selector.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: or-selector
namespace: netpol-demo
spec:
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.local
wget: download timed out
command terminated with exit code 1

selector สอง field เดียวกัน แต่ผลลัพธ์จริงต่างกันสิ้นเชิงสองแบบ: entry from เดียวที่รวม namespaceSelector และ podSelector คือ AND (สองเงื่อนไขต้องเป็นของ source เดียวกัน) สอง entry แยกกัน แต่ละอันมี selector เดียว คือ OR (เงื่อนไขไหนก็ได้ก็พอ) client-b-local — namespace เดียวกับ web ไม่มีทาง match namespaceSelector ได้เลย — เข้าถึงได้แค่ตอน policy แบบ OR เท่านั้น พิสูจน์ตรง ๆ ด้านบน

k8s/network-policies/ipblock-allow.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: ipblock-allow
namespace: netpol-demo
spec:
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 wide
NAME IP NODE
client-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/v1
kind: NetworkPolicy
metadata:
name: default-deny-ingress
spec:
podSelector: {}
policyTypes:
- Ingress
EOF
networkpolicy.networking.k8s.io/default-deny-ingress created
$ kubectl exec -n kindnet-fix-check busybox -- wget -q -T 3 -O- http://10.244.2.7
wget: download timed out
command terminated with exit code 1

HTTP 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 บังคับใช้ไม่ได้ในตอนนี้

การติดตั้ง 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 ปัจจุบัน

ตอนรีวิว NetworkPolicy manifest ที่ AI สร้าง ให้เช็ค

  • policy “default-deny” ที่มีแค่ policyTypes: [Ingress] comment อ้างว่าบล็อก traffic ทั้งหมด — พิสูจน์ด้านบนแล้วว่า egress ยังเปิดกว้างเหมือนเดิม
  • default-deny policy ที่ไม่มี DNS egress rule คู่กัน — พิสูจน์ด้านบนแล้วว่าทำลาย in-cluster DNS lookup ของทุก Pod ที่ถูกเลือก (เป็น bad address failure ไม่ใช่ 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 ในโค้ดนี้

k8s/network-policies/lockdown-shop.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: lockdown-shop
namespace: shop
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
ingress:
- from:
- podSelector:
matchLabels:
app: frontend
egress:
- 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

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 ใดกว้างกว่าที่ตั้งใจ
ipBlockallow/deny ด้วย CIDR ดิบ มีประโยชน์กับระบบภายนอกหรือระบบเก่าที่อิง IPPod IP ไม่ใช่ identity ที่เสถียร — พิสูจน์ด้านบนแล้วว่า ipBlock ที่ผูกกับ Pod IP ตรง ๆ เปราะบางกว่า label selector
  • “policyTypes: [Ingress] อย่างเดียวแปลว่า egress ก็ถูกจำกัดด้วย เพราะ policy ‘เกี่ยวกับ’ Pod นี้” พิสูจน์ด้านบนแล้ว — egress ยังเปิดกว้างเต็มที่ จนกว่า Egress จะถูกระบุชัดเจนใน policyTypes พร้อม rule ของตัวเอง
  • “default-deny policy บล็อกแค่ traffic ‘ไม่ดี’ DNS ยังทำงานได้เพราะเป็น infrastructure” พิสูจน์ด้านบนด้วย bad address failure จริง — 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
Network แบบแบนราบที่ทุก Pod คุยกันได้หมดโดย default เทียบกับหลังใช้ NetworkPolicy จำกัด path ที่อนุญาต
ถ้าไม่มี NetworkPolicy object ถูกสร้างเลยสักตัว Pod ใน cluster คุยกันยังไงโดย default
อะไรที่จำเป็นเพื่อให้ NetworkPolicy object จำกัด traffic ได้จริง
จะสื่อความหมาย deny-all-ingress ให้กลุ่ม Pod ด้วย NetworkPolicy ยังไง
การรวม namespaceSelector กับ podSelector ไว้ใน NetworkPolicy rule เดียวให้ผลอะไร
หลัง apply policy default-deny (Ingress+Egress) real failure คือ bad address ไม่ใช่ connection timeout error เจาะจงแบบนี้พิสูจน์อะไร
$ kubectl exec client-a -- wget -q -T 3 -O- http://web...
<!DOCTYPE html>
...
$ kubectl apply -f default-deny-all.yaml
networkpolicy.../default-deny-all created
$ kubectl exec client-a -- wget -q -T 3 -O- http://web...
wget: bad address 'web.netpol-demo.svc.cluster.local'
นี่สำเร็จบน Pod ที่ถูกเลือกโดย NetworkPolicy ที่มี policyTypes: [Ingress] อย่างเดียว (ไม่มี Egress) นี่พิสูจน์อะไร
$ kubectl exec web -- getent hosts kubernetes.default.svc.cluster.local
10.96.0.1  kubernetes.default.svc.cluster.local