K8s 的網路看起來很複雜,但核心就是四層:Pod 之間可以直接通訊(flat network)、Service 提供穩定入口、Ingress 做 HTTP 路由、NetworkPolicy 做存取控制。搞懂這四層,K8s 的網路就不再神秘了。
先講結論
K8s 網路 = Pod-to-Pod(flat network,任何 Pod 可以直接打到任何 Pod)+ Service(穩定入口,不用追蹤 Pod IP)+ Ingress(HTTP 路由,一個入口分配到多個 Service)+ NetworkPolicy(防火牆,限制誰能打誰)。四種 Service 類型選擇:ClusterIP(叢集內部)、NodePort(開發測試)、LoadBalancer(雲端生產)、ExternalName(對接外部服務)。
K8s 的 Flat Network 模型
K8s 的網路有一個根本假設:所有 Pod 都能直接跟其他 Pod 通訊,不需要 NAT。
這代表:
- Pod A(Node 1)可以直接打 Pod B(Node 2)的 IP
- 每個 Pod 有自己的 IP(不是共享 Node 的 IP)
- 不需要做 port mapping
這個假設由 CNI(Container Network Interface)plugin 實現。常見的 CNI:
- Flannel:最簡單,overlay network,適合學習
- Calico:支援 NetworkPolicy,適合生產
- Cilium:基於 eBPF,效能最好,功能最多
不需要自己裝——大部分 K8s 發行版(k3s, EKS, GKE)都內建好了。
四種 Service 類型
Pod 的 IP 會隨著重建改變。Service 提供一個穩定的 DNS name 和 IP,背後自動 load balance 到對應的 Pod。
1. ClusterIP(預設)
只能在叢集內部存取。最常用。
apiVersion: v1
kind: Service
metadata:
name: api-service
spec:
type: ClusterIP # 可省略,這是預設值
selector:
app: api-server
ports:
- port: 80 # Service 對外的 port
targetPort: 8080 # Pod 實際的 port
protocol: TCP叢集內的其他 Pod 可以用 http://api-service 或 http://api-service.default.svc.cluster.local 來存取。
2. NodePort(開發測試)
在每個 Node 上開一個固定的 port(30000-32767),從外部可以用 NodeIP:NodePort 存取。
apiVersion: v1
kind: Service
metadata:
name: api-nodeport
spec:
type: NodePort
selector:
app: api-server
ports:
- port: 80
targetPort: 8080
nodePort: 30080 # 不指定的話 K8s 會隨機分配實務建議:NodePort 只在開發和測試環境用。生產環境用 LoadBalancer 或 Ingress。因為你不想讓使用者記住 yoursite.com:30080。
3. LoadBalancer(雲端生產)
在雲端環境自動建立一個外部 Load Balancer(AWS ALB/NLB、GCP LB)。
apiVersion: v1
kind: Service
metadata:
name: api-lb
annotations:
# AWS 特有:指定 NLB
service.beta.kubernetes.io/aws-load-balancer-type: nlb
spec:
type: LoadBalancer
selector:
app: api-server
ports:
- port: 80
targetPort: 8080每個 LoadBalancer Service 會建立一個獨立的 LB,雲端帳單就是這樣爆的。如果你有多個服務,用 Ingress 共享一個 LB 更划算。
4. ExternalName(對接外部服務)
把外部服務映射成叢集內的 Service name。
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: mydb.rds.amazonaws.com叢集內的 Pod 可以用 external-db 這個名字來連 RDS,不用在每個 Deployment 裡寫完整的 hostname。哪天換 DB 只要改 Service,不用改所有的 app 設定。
Ingress Controller + Ingress
Ingress 是 HTTP 路由層。一個 Ingress Controller(通常是 nginx 或 traefik)監聽一個 port,根據 hostname 和 path 把流量分配到不同的 Service。
安裝 nginx Ingress Controller
# 用 Helm 安裝(推薦)
helm repo add ingress-nginx https://kubernetes.github.io/ingress-nginx
helm repo update
helm install ingress-nginx ingress-nginx/ingress-nginx \
--namespace ingress-nginx \
--create-namespace \
--set controller.replicaCount=2Hostname Routing
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: multi-app-ingress
annotations:
nginx.ingress.kubernetes.io/ssl-redirect: "true"
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
- web.example.com
secretName: example-tls
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80
- host: web.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: web-service
port:
number: 80Path Routing
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: path-routing
annotations:
nginx.ingress.kubernetes.io/rewrite-target: /$2
spec:
ingressClassName: nginx
rules:
- host: app.example.com
http:
paths:
- path: /api(/|$)(.*)
pathType: ImplementationSpecific
backend:
service:
name: api-service
port:
number: 80
- path: /(.*)
pathType: ImplementationSpecific
backend:
service:
name: web-service
port:
number: 80cert-manager 自動 TLS
手動管理 TLS 憑證是惡夢。cert-manager 幫你自動從 Let’s Encrypt 申請和續期。
# 安裝 cert-manager
helm repo add jetstack https://charts.jetstack.io
helm install cert-manager jetstack/cert-manager \
--namespace cert-manager \
--create-namespace \
--set crds.enabled=true# ClusterIssuer — 整個叢集共用
apiVersion: cert-manager.io/v1
kind: ClusterIssuer
metadata:
name: letsencrypt-prod
spec:
acme:
server: https://acme-v02.api.letsencrypt.org/directory
email: admin@example.com
privateKeySecretRef:
name: letsencrypt-prod
solvers:
- http01:
ingress:
class: nginx
---
# Ingress 加上 cert-manager annotation
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: tls-ingress
annotations:
cert-manager.io/cluster-issuer: letsencrypt-prod
spec:
ingressClassName: nginx
tls:
- hosts:
- api.example.com
secretName: api-example-tls # cert-manager 自動建立這個 Secret
rules:
- host: api.example.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: api-service
port:
number: 80cert-manager 會自動申請憑證、存到 api-example-tls Secret 裡,到期前自動續期。你什麼都不用做。
CoreDNS:服務發現的核心
K8s 內建 CoreDNS,負責把 Service name 解析成 ClusterIP。DNS 格式:
<service-name>.<namespace>.svc.cluster.local
實際上你通常只需要寫 <service-name>(同 namespace)或 <service-name>.<namespace>(跨 namespace):
# 同 namespace
curl http://api-service
# 跨 namespace
curl http://api-service.production
# 完整 FQDN
curl http://api-service.production.svc.cluster.localHeadless Service 的 DNS
StatefulSet 搭配 Headless Service(clusterIP: None),每個 Pod 有自己的 DNS record:
postgres-0.postgres-svc.default.svc.cluster.local
postgres-1.postgres-svc.default.svc.cluster.local
這就是為什麼 StatefulSet 需要 Headless Service——你的 app 可以明確連到 primary(postgres-0)或 replica(postgres-1)。
NetworkPolicy:叢集內的防火牆
K8s 預設所有 Pod 可以互相通訊。這很方便,但在生產環境你不希望前端 Pod 直接打資料庫。NetworkPolicy 就是 K8s 的防火牆。
前提:你的 CNI 要支援 NetworkPolicy。Calico 和 Cilium 支援,Flannel 不支援。
只允許特定 namespace 存取 DB
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: db-access-policy
namespace: database
spec:
podSelector:
matchLabels:
app: postgres
policyTypes:
- Ingress
ingress:
- from:
# 只允許 backend namespace 的 Pod 連進來
- namespaceSelector:
matchLabels:
name: backend
podSelector:
matchLabels:
role: api
ports:
- protocol: TCP
port: 5432預設拒絕所有流量
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {} # 套用到 namespace 內所有 Pod
policyTypes:
- Ingress
- Egress先全部擋住,再逐一開放需要的路徑。這是 zero-trust 的做法。但注意:這也會擋住 DNS(CoreDNS 在 kube-system namespace),所以你還需要:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-dns
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53常見踩坑
Service 連不到 Pod:selector 的 label 跟 Pod 的 label 對不上。用 kubectl get endpoints <service-name> 確認 endpoint list 不是空的。
Ingress 設了但外面打不進來:Ingress Controller 沒裝、或 Ingress 的 ingressClassName 打錯。用 kubectl get ingressclass 看有什麼。
跨 namespace 呼叫失敗:沒用 <service>.<namespace> 格式。只寫 api-service 只能解析同 namespace 的 Service。
NetworkPolicy 一加就全斷:default-deny 忘記放行 DNS。Pod 連 Service name 都解析不了,當然什麼都打不通。
K8s 的網路不難,但踩坑成本很高。花一小時把 Service 類型和 Ingress 搞懂,省下十小時 debug。
延伸閱讀
本系列文章
- ← 上一篇:Workloads:Deployment 之外的世界
- 本篇:網路模型:Service / Ingress / DNS
- 下一篇: StorageClass →