2.8. 服务与网络
Pod 的 IP 是临时的,Service 才是稳定的访问入口。再往上,Ingress 负责七层路由,NetworkPolicy 负责「谁能访问谁」。这一章把三层网络讲清楚。
- 层级:
L2 · 核心
- 难度:
进阶
- 预计:
60 分钟
- 前置:
Pod、工作负载
2.8.1. 你将学到
Kubernetes 的 Pod 网络模型与 CNI 的职责
ClusterIP、NodePort、LoadBalancer、ExternalName、Headless 的差异
Service、Endpoints、EndpointSlice 的关系
CoreDNS 的解析规则与常见 DNS 问题
Ingress 的路由与 TLS
NetworkPolicy 的默认拒绝与精确放行
2.8.2. Pod 网络模型
Kubernetes 对网络有三条基本要求:
每个 Pod 有独立 IP;
所有 Pod 之间可以直接通信,不需要 NAT;
节点上的进程可以直接访问所有 Pod。
这套「扁平网络」由 CNI 插件实现。CNI 负责:
给 Pod 分配 IP;
配置 veth、路由、网桥;
实现跨节点通信(VXLAN / IPIP / BGP / 原生路由);
部分 CNI 还实现 NetworkPolicy。
图:跨节点 Pod 通信由 CNI 负责;具体封装或路由方式取决于插件配置。
2.8.3. Service
2.8.3.1. 为什么需要 Service
Pod 会被重建,IP 会变化。Service 提供:
稳定的 ClusterIP 和 DNS 名称;
基于标签选择器自动维护后端;
负载均衡(iptables / IPVS / eBPF)。
apiVersion: v1
kind: Service
metadata:
name: demoapp-svc
spec:
type: ClusterIP
selector:
app: demoapp
ports:
- name: http
protocol: TCP
port: 80
targetPort: http
字段 |
含义 |
|---|---|
|
Service 暴露的端口 |
|
后端 Pod 的端口(可以是名字) |
|
选中哪些 Pod |
|
ClusterIP / NodePort / LoadBalancer / ExternalName |
2.8.3.2. 四种类型
类型 |
访问范围 |
实现 |
适用 |
|---|---|---|---|
ClusterIP |
集群内 |
虚拟 IP + kube-proxy |
内部服务 |
NodePort |
集群外(节点 IP + 端口) |
每个节点开一个端口 |
实验、临时暴露 |
LoadBalancer |
集群外 |
云 LB 或 MetalLB |
生产入口 |
ExternalName |
集群内 |
CNAME 到外部域名 |
访问外部服务 |
apiVersion: v1
kind: Service
metadata:
name: demoapp-nodeport
spec:
type: NodePort
selector:
app: demoapp
ports:
- port: 80
targetPort: 80
nodePort: 30080
apiVersion: v1
kind: Service
metadata:
name: external-db
spec:
type: ExternalName
externalName: db.example.com
2.8.3.3. Headless Service
clusterIP: None 不为 Service 分配虚拟 IP,DNS 直接返回所有 Pod IP。StatefulSet 依赖它提供稳定身份。
apiVersion: v1
kind: Service
metadata:
name: demoapp-headless
spec:
clusterIP: None
selector:
app: demoapp
ports:
- port: 80
targetPort: 80
kubectl run -it --rm dns --image=busybox:1.36 --restart=Never -- \
nslookup demoapp-headless
预期返回多个 Pod IP,而不是一个 ClusterIP。
2.8.3.4. Endpoints 与 EndpointSlice
Service 通过 selector 找到 Pod,把就绪的 Pod IP 写入 EndpointSlice。kube-proxy 根据 EndpointSlice 编程转发规则。
kubectl get endpoints demoapp-svc
kubectl get endpointslice -l kubernetes.io/service-name=demoapp-svc
如果 Endpoints 为空:
selector 不匹配;
Pod 没有 Ready(readiness 失败);
Pod 在别的 namespace。
2.8.3.5. kube-proxy 模式
模式 |
原理 |
特点 |
|---|---|---|
iptables |
规则匹配 + DNAT |
成熟,规则多时性能下降 |
IPVS |
内核四层负载均衡 |
大规模性能好,需要 ipvs 模块 |
eBPF |
Cilium 替换 kube-proxy |
性能与可观测性强,复杂度高 |
2.8.4. DNS
CoreDNS 为 Service 提供 DNS:
名称 |
解析结果 |
|---|---|
|
同 namespace 的 ClusterIP |
|
指定 namespace |
|
加上 svc |
|
FQDN |
|
所有 Pod IP(Headless) |
|
StatefulSet 单 Pod |
Pod 的 /etc/resolv.conf:
nameserver 10.96.0.10
search demo.svc.cluster.local svc.cluster.local cluster.local
options ndots:5
ndots:5 意味着「名字里少于 5 个点就优先尝试 search 域」,这也是跨 namespace 访问时偶尔多一次查询的原因。
测试:
kubectl run -it --rm dns --image=busybox:1.36 --restart=Never -- sh
# 容器内
nslookup kubernetes.default
nslookup demoapp-svc.demo
cat /etc/resolv.conf
2.8.5. Ingress
Service 是四层(TCP/UDP),Ingress 是七层(HTTP/HTTPS),提供基于域名和路径的路由。
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: demoapp
spec:
ingressClassName: nginx
tls:
- hosts:
- demo.lab
secretName: demo-tls
rules:
- host: demo.lab
http:
paths:
- path: /api
pathType: Prefix
backend:
service:
name: api-svc
port:
number: 80
- path: /
pathType: Prefix
backend:
service:
name: web-svc
port:
number: 80
pathType |
行为 |
|---|---|
|
精确匹配 |
|
按路径段前缀匹配 |
|
由 Ingress Controller 决定 |
Ingress 本身只是规则,真正生效需要 Ingress Controller(如 ingress-nginx)。没有 Controller 时,Ingress 对象不会产生任何流量。
2.8.6. NetworkPolicy
默认情况下,Pod 之间全部互通。NetworkPolicy 用来实现「默认拒绝 + 按需放行」。
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny
namespace: demo
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
这条策略选中 namespace 内所有 Pod,拒绝所有入站和出站流量。然后按需放行:
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-web
namespace: demo
spec:
podSelector:
matchLabels:
app: demoapp
policyTypes:
- Ingress
ingress:
- from:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: ingress-nginx
- podSelector:
matchLabels:
app: frontend
ports:
- protocol: TCP
port: 80
警告
NetworkPolicy 需要 CNI 支持
Flannel 默认不支持 NetworkPolicy;Calico、Cilium 支持。写了策略但没有生效,先确认 CNI 是否实现了 networking.k8s.io/v1 的 NetworkPolicy。
NetworkPolicy 是「允许列表」的叠加:一旦某个 Pod 被至少一条策略选中,未被允许的流量默认拒绝。
2.8.7. 实验:Service 与 NetworkPolicy
2.8.7.1. 1. 部署两个后端
kubectl create namespace demo --dry-run=client -o yaml | kubectl apply -f -
kubectl create deploy web --image=nginx:1.27 --replicas=2 -n demo
kubectl expose deploy web --port=80 --name=web -n demo
kubectl get endpoints web -n demo
2.8.7.2. 2. 验证 ClusterIP
kubectl run curl --image=curlimages/curl:8.10.1 --rm -it --restart=Never -n demo -- \
curl -s http://web/
2.8.7.3. 3. 验证 Headless
kubectl expose deploy web --port=80 --name=web-headless --clusterip=None -n demo
kubectl run dns --image=busybox:1.36 --rm -it --restart=Never -n demo -- \
nslookup web-headless
2.8.7.4. 4. 默认拒绝
kubectl apply -f deny-all.yaml
kubectl run curl --image=curlimages/curl:8.10.1 --rm -it --restart=Never -n demo -- \
curl -m 3 -s http://web/
预期超时。然后应用 allow-web.yaml,再次访问。注意:策略中的 namespaceSelector 要匹配实际来源 namespace。
2.8.7.5. 5. 观察 EndpointSlice
kubectl get endpointslice -n demo -w
删除一个 Pod,观察 EndpointSlice 如何更新。
2.8.8. 排错表
现象 |
原因 |
处理 |
|---|---|---|
Service 访问超时 |
没有 Endpoints / 网络策略 / CNI 异常 |
|
|
后端未 Ready |
|
DNS 解析失败 |
CoreDNS 异常 / 网络策略阻断 |
|
跨 namespace 解析失败 |
用了短名 |
用 |
NodePort 访问不通 |
防火墙 / |
检查节点端口与安全组 |
Ingress 404 |
host/path 不匹配 |
|
Ingress 502 |
后端端口错 / 未 Ready |
|
NetworkPolicy 不生效 |
CNI 不支持 |
换 Calico/Cilium |
源 IP 丢失 |
SNAT / |
改 |
2.8.9. 进阶挑战
用
externalTrafficPolicy: Local部署 NodePort,观察源 IP 变化。给 StatefulSet 配置 Headless Service,验证
pod-0.svc的 DNS。写一条只允许
frontend访问api的 NetworkPolicy,并用kubectl exec验证。用
kubectl get endpointslice -o yaml观察endpoints与ports结构。把 CoreDNS 的 ConfigMap 改错,观察集群 DNS 故障,再恢复。
2.8.10. 小结
Pod 网络由 CNI 实现,Service 提供稳定入口,Ingress 提供七层路由。
Service 的 selector 与 Pod 标签不匹配,Endpoints 就为空。
DNS 名称有固定格式:
svc.namespace.svc.cluster.local。NetworkPolicy 必须由支持的 CNI 实现,默认拒绝要配合精确放行。
2.8.11. 延伸阅读
原仓库示例:examples/services
下一章:认证与安全