Kubernetes v1.36:Service ExternalIPs 的弃用和移除
Service 的
.spec.externalIPs 字段是为非云集群提供类似云负载均衡器功能的早期尝试。
不幸的是,该 API 假设集群中的每个用户都是完全可信的,
而在任何不满足此条件的情况下,它会导致各种安全漏洞,
如 CVE-2020-8554 中所述。
自 Kubernetes 1.21 起,Kubernetes 项目建议所有用户禁用 .spec.externalIPs。
为了简化这一过程,Kubernetes 还添加了一个准入控制器(DenyServiceExternalIPs),
可以启用它来实现此目的。当时,SIG Network 认为默认阻止该特性是一个太大的破坏性变更,不予考虑。
然而,安全问题仍然存在,作为一个项目,我们对该特性"默认不安全"的状态越来越不满意。 此外,对于想要类似负载均衡器功能的非云集群,现在有几种更好的替代方案。
因此,Service 的 .spec.externalIPs 字段现在在 Kubernetes 1.36 中正式弃用。
我们预计 Kubernetes 的未来次要版本将从 kube-proxy 中删除该行为的实现,
并更新 Kubernetes 一致性标准,
要求一致性实现不提供支持。
关于术语的说明,以及未被弃用的内容
短语 external IP 在 Kubernetes 中有些过度使用:
- Service API 有一个字段
.spec.externalIPs,可用于添加 Service 将响应的额外 IP 地址。
- Node API 的
.status.addresses字段可以列出几种不同类型的地址,其中一种称为ExternalIP。
kubectl工具在以默认输出格式显示 LoadBalancer 类型的 Service 信息时, 会在列标题EXTERNAL-IP下显示负载均衡器 IP 地址。
此弃用是关于第一项的。如果你没有在任何 Service 中设置 externalIPs 字段,则这不适用于你。
话虽如此,作为预防措施,你可能仍希望启用
DenyServiceExternalIPs
准入控制器来阻止将来对 externalIPs 字段的任何使用。
externalIPs 的替代方案
如果你正在使用 .spec.externalIPs,那么有几种替代方案。
考虑如下的 Service:
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"
使用手动管理的 LoadBalancer Service 代替 externalIPs
最简单(但也最糟糕)的选择是从使用 externalIPs 切换到使用 type: LoadBalancer 服务,
并手动分配负载均衡器 IP。这本质上与 externalIPs 完全相同,
但有一个重要区别:负载均衡器 IP 是 Service 的 .status 的一部分,
而不是 .spec,在启用 RBAC 的集群中,默认情况下普通用户无法编辑它。
因此,这种 externalIPs 的替代方案仅对管理员授予权限的用户可用
(尽管这些用户随后将完全有能力复制 CVE-2020-8554;
仍然不会有任何进一步的检查来确保一个用户没有窃取另一个用户的 IP 等)。
由于 .status 在 Kubernetes 中的工作方式,你必须创建不带负载均衡器 IP 的 Service,
然后在第二步添加 IP:
$ cat loadbalancer-service.yaml
apiVersion: v1
kind: Service
metadata:
name: my-example-service
spec:
# prevent any real load balancer controllers from managing this service
# by using a non-existent loadBalancerClass
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
service/my-example-service created
$ kubectl patch service my-example-service --subresource=status --type=merge -p '{"status":{"loadBalancer":{"ingress":[{"ip":"192.0.2.4"}]}}}'
使用非云负载均衡器控制器
虽然 LoadBalancer 服务最初设计为由云负载均衡器支持,
但 Kubernetes 也可以通过使用第三方负载均衡器控制器(如 MetalLB)
在非云平台上支持它们。
这解决了与 externalIPs 相关的安全问题,因为管理员可以配置控制器将分配给服务的 IP 地址范围,
并且控制器将确保两个服务不能同时使用同一个 IP。
因此,例如,在安装和 配置 MetalLB 后, 集群管理员可以配置集群中使用的 IP 地址池:
apiVersion: metallb.io/v1beta1
kind: IPAddressPool
metadata:
name: production
namespace: metallb-system
spec:
addresses:
- 192.0.2.0/24
autoAssign: true
avoidBuggyIPs: false
之后,用户可以创建 type: LoadBalancer Service,MetalLB 将处理 IP 地址的分配。
MetalLB 甚至支持 Service 中已弃用的 loadBalancerIP 字段,
因此最终用户可以请求特定的 IP(假设可用)以实现与 externalIPs 方法的向后兼容性,
而不是随机分配一个:
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
loadBalancerIP: "192.0.2.4"
类似的方法也适用于其他负载均衡器控制器。 这种方法允许集群管理员控制分配哪些 IP 地址,而不是由用户控制。
使用 Gateway API
另一个可能的解决方案是使用 Gateway API 的实现。
Gateway API 允许集群管理员定义 Gateway 资源,
该资源可以通过 .spec.addresses 字段附加 IP 地址。
由于 Gateway 资源设计为由集群管理员管理,
因此可以设置 RBAC 规则,仅允许特权用户管理它们。
示例如下:
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"
---
apiVersion: gateway.networking.k8s.io/v1
kind: HTTPRoute
metadata:
name: example-route
spec:
parentRefs:
- name: example-gateway
rules:
- backendRefs:
- name: example-svc
port: 80
---
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 项目是 Kubernetes 中 Kubernetes Ingress、负载均衡和服务网格 API 的下一代。 Gateway API 旨在修复 Service 和 Ingress 资源的缺点, 使其成为正在积极开发的非常可靠的稳健解决方案。
externalIPs 弃用时间表
此弃用的粗略时间表如下:
- 随着 Kubernetes 1.36 的发布,该字段被弃用; 当用户使用此字段时,Kubernetes 现在会发出警告
- 大约一年后(最早为 v1.40),对
.spec.externalIPs的支持将在 kube-proxy 中被禁用, 但用户将有一种方法可以选择重新启用,以便他们需要更多时间进行迁移 - 大约再过一年后(最早为 v1.43),支持将被完全禁用;用户将无法选择重新启用