Kubernetes Service 的 .spec.externalIPs 字段,从 1.21 起就被官方建议禁用,但一直没敢动手——因为怕破坏现有集群。五年后,v1.36 终于把这个"默认不安全"的功能正式标记为废弃,并给出了明确的移除时间线。如果你还在用这个字段,现在就是迁移的最后窗口。
为什么非废不可
externalIPs 的设计初衷很简单:在没有云厂商 LoadBalancer 的裸金属集群里,让 Service 能响应一个额外的外部 IP。比如:
apiVersion: v1
kind: Service
metadata:
name: my-example-service
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: my-example-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
externalIPs:
- "192.0.2.4"
看起来方便,但问题在于:任何有 Service 创建权限的用户都能随意声明任意 IP。没有校验、没有冲突检测、没有管理员管控。这意味着集群里的普通用户可以:
- 把别人的 Service IP 劫持到自己手上
- 把节点 IP、网关 IP 甚至集群外系统的 IP 挪用过来
- 实现中间人攻击或流量窃取
这就是 CVE-2020-8554 描述的攻击场景。在多租户或权限未严格隔离的集群里,这是一个真实可利用的漏洞。
别混淆:这次只废 .spec.externalIPs
Kubernetes 里 "External IP" 这个词出现在好几个地方,容易搞混:
| 位置 | 含义 | 是否废弃 |
|---|---|---|
Service .spec.externalIPs |
让 Service 响应额外 IP | ✅ 本次废弃 |
Node .status.addresses 中 type=ExternalIP |
节点的外部地址 | ❌ 不受影响 |
kubectl get svc 的 EXTERNAL-IP 列 |
LoadBalancer 类型 Service 的 LB 入口 IP | ❌ 不受影响 |
简单判断标准:如果你没有在任何 Service 里设置 externalIPs 字段,这件事跟你无关。但作为预防措施,建议仍然启用 DenyServiceExternalIPs 准入控制器,防止未来有人误用。
三条替代路径
1. 手动管理 LoadBalancer Service——最简单但最不推荐
把 externalIPs 换成 type: LoadBalancer,然后手动 patch .status.loadBalancer.ingress 写入 IP。关键区别:.status 在 RBAC 开启时普通用户默认改不了,所以管理员可以控制谁有权限。
但本质上,安全模型没变——拿到权限的用户照样能劫持 IP,只是门槛高了一点。
操作步骤:
# 第一步:创建 Service(不带 LB IP)
cat loadbalancer-service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-example-service
spec:
# 用一个不存在的 loadBalancerClass,阻止真实 LB 控制器接管
loadBalancerClass: non-existent-class
type: LoadBalancer
selector:
app.kubernetes.io/name: my-example-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
kubectl apply -f loadbalancer-service.yaml
# 第二步:patch status 写入 IP
kubectl patch service my-example-service --subresource=status \
--type=merge -p '{"status":{"loadBalancer":{"ingress":[{"ip":"192.0.2.4"}]}}}'
注意 --subresource=status 这个参数——Kubernetes 1.24+ 才支持对 status 子资源的独立 patch,老版本集群需要确认 API 支持。
这条路径只适合临时过渡,长期方案看下面两条。
2. 第三方 LoadBalancer 控制器——推荐方案
MetalLB 是最成熟的选择。管理员定义 IP 池,控制器自动分配、保证不冲突、保证权限可控。
先安装 MetalLB(以 Helm 为例):
helm repo add metallb https://metallb.github.io/metallb
helm install metallb metallb/metallb -n metallb-system --create-namespace
然后配置 IP 池:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production
namespace: metallb-system
spec:
addresses:
- 192.0.2.0/24
autoAssign: true
avoidBuggyIPs: false
用户创建普通 LoadBalancer Service,MetalLB 自动从池里分配 IP:
apiVersion: v1
kind: Service
metadata:
name: my-example-service
spec:
type: LoadBalancer
selector:
app.kubernetes.io/name: my-example-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
如果需要指定 IP(兼容旧 externalIPs 的使用习惯),MetalLB 还支持已废弃的 loadBalancerIP 字段做请求式分配——只要该 IP 在池内且未被占用:
apiVersion: v1
kind: Service
metadata:
name: my-example-service
spec:
type: LoadBalancer
loadBalancerIP: "192.0.2.4"
selector:
app.kubernetes.io/name: my-example-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
核心优势:管理员控制 IP 范围,控制器保证唯一性,用户不能越界。OpenELB、PureLB 等其他控制器思路类似。
3. Gateway API——面向未来的方案
Gateway API 是 Kubernetes Ingress / Load Balancing / Service Mesh 的下一代标准。管理员创建 Gateway 资源并绑定 IP,用户只创建 Route 挂到已有 Gateway 上——权限分离天然到位。
# 管理员创建 Gateway(绑定外部 IP)
apiVersion: gateway.networking.k8s.io/v1
kind: Gateway
metadata:
name: example-gateway
spec:
gatewayClassName: example-gateway-class
addresses:
- type: IPAddress
value: "192.0.2.4"
# 用户创建路由(无需知道外部 IP)
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
spec:
parentRefs:
- name: example-gateway
rules:
- backendRefs:
- name: example-svc
port: 80
# 后端仍然是普通 ClusterIP Service
apiVersion: v1
kind: Service
metadata:
name: example-svc
spec:
type: ClusterIP
selector:
app.kubernetes.io/name: example-app
ports:
- protocol: TCP
port: 80
targetPort: 8080
Gateway API 仍在快速演进,生态实现(如 Istio、Envoy Gateway、Traefik)已经可用。如果你的集群已经在用或计划引入 Service Mesh / Ingress 重构,这是最干净的路径。
废弃时间线
| 版本 | 动作 |
|---|---|
| v1.36(现在) | .spec.externalIPs 正式标记废弃,使用时 API Server 会发出警告 |
| v1.40+(约一年后) | kube-proxy 默认停止实现 externalIPs 行为,但提供 opt-in 机制允许临时回开 |
| v1.43+(约两年后) | 完全移除,不可回开,一致性测试要求实现不得支持该字段 |
时间线是"最早"版本号,实际可能顺延,但方向不会变。
迁移检查清单
在 v1.36 发布后,建议立即做以下检查:
# 1. 搜索集群中所有使用了 externalIPs 的 Service
kubectl get svc -A -o json | \
jq -r '.items[] | select(.spec.externalIPs != null and .spec.externalIPs | length > 0) | "\(.metadata.namespace)/\(.metadata.name): \(.spec.externalIPs)"'
# 2. 启用准入控制器,阻止新的 externalIPs 创建(kube-apiserver 启动参数)
# --enable-admission-plugins=DenyServiceExternalIPs
# 3. 如果搜索结果为空——恭喜,你什么都不用改,只需启用准入控制器做防御
# 4. 如果有结果——逐条评估,选择 MetalLB 或 Gateway API 替代
externalIPs 从"不安全但舍不得删"到"正式废弃",走了五年。剩下的迁移窗口大概还有两年。现在查一遍集群,比将来 kube-proxy 突然不转发流量时要从容得多。