iT邦幫忙

2025 iThome 鐵人賽

DAY 23
1
Cloud Native

駕馭商用容器叢集,汪洋漂流術系列 第 23

【Day 23】 管理叢集流量 - NetworkPolicy / EgressFirewall / EgressIP

  • 分享至 

  • xImage
  •  

前言

一座叢集中,會部署很多應用,然而在叢集內,彼此預設是互通的。 我們不能假設每個在使用叢集的程式、人都會乖乖地待在自己被允許的 namespace 中。 有些時候不安分的流量會掃描、試探 Cluster IP 去偷襲別人。 接著來理解 NetworkPolicy 核心觀念、常見範本、以及OCP 特有資源(EgressFirewall/EgressIP)。

https://ithelp.ithome.com.tw/upload/images/20250909/2013014911w4kpXi7h.jpg

簡介

  • 容器網路介面(OVN-Kubernetes CNI),是預設「完全開放」,Pod 與 Pod 之間,沒有限制沒有阻擋。這對研發人員很爽,但隱含了極高的資安風險。
  • NetworkPolicy 的本質就是「白名單」:只有被允許的流量可通過,其餘全擋。

重點觀念

  1. podSelector 選到的 Pod 才會被 NetworkPolicy 隔離;沒選到不管他。
    • 沒被列管的 Pod,就隨便他進出。
    • 有被選中列管的 Pod,只有被 明確允許 的連線會通過,沒提到的通通默認擋掉!
  2. NetworkPolicy 中,Ingress/Egress 是分管理的。
    • 不寫 ingress,就不會限制入口
    • 不寫 egress,就不會限制出口。
  3. 規則是疊加的 Allow:多個 policy 可以一起放行更多路徑。
    • 沒有「顯性 Deny」(因為沒被允許的規則,就是禁止,所以不用特別禁他)。

    多個 Policy 疊加時:允許的規則會合併成 聯集(Union),也就是「誰放行,就能通過」。
    但是 你不能寫一條規則來明確拒絕某來源,因為 NetworkPolicy 沒有 deny 語法。

建立原則

  • podSelector:要列管的目標 Pod 群
  • ingress / egress:描述誰可以進來、可以連去哪裡。
  • from / to 常用條件:
    • podSelector:同 Namespace 內、帶某些 labels 的 Pod。
    • namespaceSelector:指定哪一些 Namespace(可搭配 labels)。
    • ipBlock:CIDR(支援 except 排除段)。
  • ports:TCP/UDP/ SCTP 服務埠(建議明確列出)。

podSelector 範例

  • 假設 api Pod 被以下兩條 policy 選中:
  1. Policy A: 允許 web Pod 連進來
    ingress:
      - from:
          - podSelector:
              matchLabels:
                app: web
    
    
  2. Policy B: 允許 debug Pod 連進來
    ingress:
      - from:
          - podSelector:
              matchLabels:
                app: debug
    
    
  • 效果:
    • web 可以連
    • debug 可以連
    • db(資料庫 Pod)不能連,因為沒被任何 policy 放行
    • 不能寫一條「顯性禁止 db」的 policy,因為語法不支援

範例

以下範例,假設存在某前端、後端的 Pod 在某 namespace 中,各自都帶上 label 了。 根據下列範本建立 Lab 吧。

Build Lab

# 建兩個專案(Namespace)
oc new-project team-a
oc new-project team-b

# 在 team-a 部署兩個角色:api 與 web
oc -n team-a create deploy api --image=ghcr.io/jmalloc/echo-server:latest --port=8080
oc -n team-a expose deploy/api --port=8080 --target-port=8080
oc -n team-a label deploy api app=api tier=backend --overwrite=true

oc -n team-a create deploy web --image=nginx:alpine
oc -n team-a label deploy web app=web tier=frontend --overwrite=true

# 在 team-b 放一個 busybox 當「外人」
oc -n team-b run dbg --image=busybox:1.36 --restart=Never --command -- sh -c 'sleep 1d'

連線測試

設定 NetworkPolicy 前,來測測看什麼叫做暢行無阻。

# 先找出 team-a 內部服務的 ClusterIP 與 Port
oc -n team-a get svc api

# 從 team-a 的 web Pod 嘗試連 api
WEB_POD=$(oc -n team-a get po -l app=web -o jsonpath='{.items[0].metadata.name}')
oc -n team-a exec -ti $WEB_POD -- wget -qO- http://api:8080

# 從 team-b 的 dbg 嘗試連 team-a 的 api(預期成功,因為還沒隔離)
oc -n team-b exec -ti dbg -- wget -qO- http://api.team-a.svc.cluster.local:8080

設定 NetworkPolicy

步驟一:把後端的進出,全部擋起來!

  • 我們在部署三層是架構的服務時,前端面對普羅大眾,後端因為要配合 API 承接前端的請求,增刪修查資料庫或是其他處理。 不希望無關人士進來搗亂。
    • 前端,可以類比成餐館的用餐區,有外場服務生服務客人,客人可以依照菜單上提供的項目點餐,或者是當一個奧客搞破壞。
    • 後端,可以類比成餐館的廚房,和內場廚師。 餐飲業稱這個叫做後場或是內場。
    • 資料庫,可類比成食材庫房和倉庫。
  • 所以,對 tier=backend 的 Pod 上 Ingress/Egress 全拒絕(選到才開始隔離)
# deny-all-backend.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: deny-all-backend
  namespace: team-a
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
    - Ingress
    - Egress

寫完 yaml 記得套用

步驟二:允許特定來源可以與後端溝通

  • 後端有開放 8080 port 的話,下列設定允許同為 team-a namespace 的 Pod 訪問 8080 port
# allow-frontend-to-backend-8080.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-frontend-to-backend-8080
  namespace: team-a
spec:
  podSelector:
    matchLabels:
      tier: backend
  policyTypes:
    - Ingress
  ingress:
    - from:
        - podSelector:
            matchLabels:
              tier: frontend
      ports:
        - protocol: TCP
          port: 8080

寫完 yaml 之後也要套用

驗證連線

# 同 NS 前端打後端 -> 成功
oc -n team-a exec -ti $WEB_POD -- wget -qO- http://api:8080

# team-b 外部打後端 -> 失敗
oc -n team-b exec -ti dbg -- wget -qO- http://api.team-a.svc.cluster.local:8080 || echo "blocked"

常見的場景

允許同 Namespace 連線

  • 允許同 Namespace 內全部 Pod 訪問我(但擋跨 Namespace)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-same-namespace
  namespace: team-a
spec:
  podSelector: {}  # 選到本 NS 所有 Pod → 都開始被隔離
  policyTypes: [Ingress]
  ingress:
    - from:
        - podSelector: {}  # 同 NS 內任何 Pod

允許指定 Namespace 的哪個 Pod 連線

  • 允許特定 Namespace(加了 label)的 Pod 訪問我
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-partner-ns
  namespace: team-a
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: [Ingress]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              access: partner
      ports:
        - protocol: TCP
          port: 8080

允出指定 IP / 禁止指定 IP

  • 明確允許 連出的 IP
  • 明確排除 連出的 IP
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: egress-to-cidr
  namespace: team-a
spec:
  podSelector:
    matchLabels:
      app: api
  policyTypes: [Egress]
  egress:
    - to:
        - ipBlock:
            cidr: 10.0.0.0/8
            except:
              - 10.10.0.0/16

指定允出協定

  • 只放行特定 Port(HTTP/HTTPS),其他一律擋
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-80-443-only
  namespace: team-a
spec:
  podSelector:
    matchLabels:
      app: web
  policyTypes: [Ingress]
  ingress:
    - ports:
        - protocol: TCP
          port: 80
        - protocol: TCP
          port: 443

OCP 特有: 「EgressFirewall」 與 「EgressIP」

  • NetworkPolicy 的 egress.to 只能寫 CIDR,不能寫 FQDN。
  • OCP 上使用 OVN-Kubernetes,有兩個方便的 OCP CRD 可以拿來用。

EgressFirewall(Namespace 級別的對外規則,支援 FQDN)

  • 在某個 Namespace 內,集中定義**「往外」的允許/拒絕**,且可用 dnsName
apiVersion: k8s.ovn.org/v1
kind: EgressFirewall
metadata:
  name: default
  namespace: team-a
spec:
  egress:
    - type: Allow
      to:
        dnsName: api.github.com
    - type: Allow
      to:
        cidrSelector: 0.0.0.0/0
      ports:
        - protocol: TCP
          port: 443

備註一: EgressFirewall 設定的目標是 namespace,不是 pod
備注二: 通常會先一條允許特定 FQDN,再用第二條收斂到 443,剩下由 DNS 名稱控管。

EgressIP(固定對外來源 IP)

為了讓某些 Namespace 的對外連線,NAT 成指定的 Egress IP(給防火牆白名單或第三方 API 綁定)。 在 Namespace 打上 k8s.ovn.org/egress-ips annotation,並在叢集層配置可用 Egress IP 與負載承載節點(此段通常由叢集管理員配置)。

結論

  • 關於管理的訣竅
    • 怎麼驗證 PodSelector 寫得對不對?
    • 用指令 oc describe networkpolicy 看 PodSelector 是否真的覆蓋到你的 Pod。
    • https://ithelp.ithome.com.tw/upload/images/20250909/20130149xaqiygmlhP.png
  • 「可以解析域名」,和「可以訪問域名」是兩回事。 多得是你知道主機在哪裡,但你到不了。
  • 連 Service / 連 Pod 的差異
    • Network Policy 是檢查 Pod 之間的 Layer 3 和 Layer4 連線 (IP、Port、Protocol),無關你是用什麼樣的 Service 名稱進行訪問。
    • 用 Service 名稱連線時,DNS 先解析成 ClusterIPiptables/OVN-Kubernetes 負載 分流 → 最後實際連到 Pod IP

    NetworkPolicy 就是在上述的最後一段,判斷要不要放行。
    解得出 Service Name 不代表有被允許連線喔!!


上一篇
【Day 22】 使用 PV 和 PVC 保存永久資料
下一篇
【Day 24】 如何進行 Secret Rotation 與 Key Management
系列文
駕馭商用容器叢集,汪洋漂流術26
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言