VpcNatGateway 支持自定义 Annotations
1. 背景与需求
1.1 PR #6108 原始需求
PR #6108 允许用户在 VpcNatGateway CRD 的 spec 中定义自定义 annotations,这些 annotations 会被注入到生成的 NAT Gateway Pod 的 template 中。
1.2 核心问题
用户问题: 是否需要在 Status 中存储 Annotations?
经过深入分析,最终决策是 不检测 annotation 变更,annotations 仅在创建时生效。
2. 设计决策
2.1 仅创建时生效
设计选择: Annotations 仅在 StatefulSet 创建时应用,后续修改 spec.annotations 不会触发更新。
原因:
- NAT Gateway Pod 不支持热更新,任何模板变更都会触发 Pod 重建
- 简化实现,避免复杂的变更检测逻辑
- 如需更新 annotations,用户可以删除并重建 gateway
2.2 无安全过滤
设计选择: 不对用户 annotations 进行敏感 key 过滤。
原因:
GenNatGwPodAnnotations会自动覆盖所有系统 annotations,用户无法注入冲突 keyhandleUpdateNatGwSubnetRoute中已有 CIDR 格式校验提供纵深防御- 过滤逻辑是过度工程化,增加维护负担但无实际安全收益
3. 实现方案
3.1 控制流
用户创建 VpcNatGateway spec.annotations = {"foo": "bar"}
│
▼
handleAddOrUpdateVpcNatGw()
│
▼
┌─────────────────────────────────────────────────────────────┐
│ 1. 检查 StatefulSet 是否存在 │
│ - needToCreate = true → 创建新 STS │
│ - needToCreate = false → 检查是否需要更新 │
│ │
│ 2. genNatGwStatefulSet(gw, oldSts, restartCount) │
│ └─ 调用 GenNatGwPodAnnotations(gw.Spec.Annotations, ...) │
│ 系统 annotations 覆盖用户提供的冲突 key │
│ │
│ 3. 创建场景: annotations 被应用到 Pod template │
│ 更新场景: annotations 变更不被检测 (by design) │
└─────────────────────────────────────────────────────────────┘
3.2 关键函数
| 函数 | 职责 |
|---|---|
GenNatGwPodAnnotations(baseAnnotations, gw, ...) | 生成 Pod annotations,系统 key 覆盖用户 key |
isVpcNatGwChanged(gw) | 检测 Spec vs Status (不含 annotations) |
needRestartRecovery(restartCount) | 检测容器重启恢复需求 |
3.3 更新判断逻辑
// handleAddOrUpdateVpcNatGw
needToUpdate := isVpcNatGwChanged(gw) || needRestartRecovery(natGwPodContainerRestartCount)
if needToUpdate {
// Update StatefulSet
}
注意: isVpcNatGwChanged 不检查 annotations,只检查 ExternalSubnets, Selector, Tolerations, Affinity。
4. 安全考虑
4.1 系统 Annotation 保护
GenNatGwPodAnnotations 确保系统 annotations 总是覆盖用户提供的冲突 key:
result := baseAnnotations // 用户 annotations 作为基础
if result == nil {
result = make(map[string]string)
}
// 系统 annotations 覆盖冲突
result[nadv1.NetworkAttachmentAnnot] = attachedNetworks
result[VpcNatGatewayAnnotation] = gw.Name
result[fmt.Sprintf(LogicalSwitchAnnotationTemplate, p)] = gw.Spec.Subnet
result[fmt.Sprintf(IPAddressAnnotationTemplate, p)] = gw.Spec.LanIP
4.2 CIDR 格式校验
handleUpdateNatGwSubnetRoute 中的 CIDR 校验提供纵深防御:
for _, cidr := range existingCIDR {
if err = util.CheckCidrs(cidr); err != nil {
klog.Warningf("skipping invalid CIDR %q from annotation %q: %v", cidr, annotation, err)
continue
}
oldCIDRs = append(oldCIDRs, cidr)
}
5. API 类型定义
5.1 Spec.Annotations 字段
type VpcNatGatewaySpec struct {
// ... 其他字段 ...
// User-defined annotations for the StatefulSet NAT gateway Pod template.
// Only effective at creation time; updates to this field are not detected.
Annotations map[string]string `json:"annotations,omitempty"`
}
5.2 架构注释
// VpcNatGateway represents a NAT gateway for a VPC, implemented as a StatefulSet Pod.
//
// Architecture note:
// The NAT gateway Pod does NOT support hot updates. Any changes to Spec fields
// (ExternalSubnets, Selector, Tolerations, Affinity, Annotations, etc.) will trigger
// a StatefulSet template update, which causes the Pod to be recreated via RollingUpdate
// strategy. This is by design because:
// 1. Network configuration (routes, iptables rules) is initialized at Pod startup
// 2. Runtime state (vpc_cidrs, init status) is managed by separate handlers and will be
// automatically restored after Pod recreation through the normal reconciliation flow
//
// The only exception is QoSPolicy, which can be updated without Pod restart.
6. 测试覆盖
6.1 单元测试
| 测试函数 | 用例数 | 覆盖内容 |
|---|---|---|
TestNeedRestartRecovery | 3 | 重启计数检测 |
TestIsVpcNatGwChanged | 5 | Spec vs Status 变更检测 |
TestGetSubnetProvider | 若干 | subnet provider 获取 |
TestGetExternalSubnetNad | 若干 | external NAD 获取 |
6.2 E2E 测试
MakeVpcNatGatewayWithAnnotations测试框架函数- 验证用户 annotation 正确注入到 Pod template
7. 文件变更清单
| 文件 | 变更类型 | 说明 |
|---|---|---|
pkg/apis/kubeovn/v1/vpc-nat-gateway.go | 修改 | 添加 Spec.Annotations + 架构注释 + TODO |
pkg/apis/kubeovn/v1/zz_generated.deepcopy.go | 自动生成 | map deepcopy |
pkg/controller/vpc_nat_gateway.go | 修改 | 代码结构优化 |
pkg/controller/vpc_nat_gateway_test.go | 修改 | 新增单元测试 |
pkg/util/vpc_nat_gateway.go | 修改 | GenNatGwPodAnnotations 支持 baseAnnotations |
dist/images/install.sh | 修改 | CRD annotations schema |
charts/kube-ovn/templates/kube-ovn-crd.yaml | 修改 | annotations schema |
charts/kube-ovn-v2/crds/kube-ovn-crd.yaml | 修改 | annotations schema |
test/e2e/framework/vpc-nat-gateway.go | 修改 | MakeVpcNatGatewayWithAnnotations |
test/e2e/iptables-vpc-nat-gw/e2e_test.go | 修改 | E2E 测试验证 |
8. 后续优化 TODO
在 VpcNatGatewayStatus 类型定义上保留了 TODO 注释:
// TODO: Consider removing redundant Status fields since statefulset template changes always trigger Pod recreation.
9. 版本历史
| 版本 | 日期 | 变更 |
|---|---|---|
| v1 | - | 初始设计 |
| v2 | - | 添加安全考虑 |
| v3 | - | 简化方案,直接对比 STS |
| v4 | 2025-02-03 | 添加 Contains 检测、安全过滤函数 |
| v5 | 2025-02-03 | 最终实现: 移除安全过滤 (过度工程化),annotations 仅创建时生效 |
10. 设计取舍说明
10.1 为什么移除安全过滤?
- 冲突覆盖机制:
GenNatGwPodAnnotations确保系统 annotations 总是覆盖用户 key - CIDR 校验:
handleUpdateNatGwSubnetRoute提供了实际的安全防护 - 维护成本: 敏感 key 列表需要持续维护,但无实际安全收益
10.2 为什么不检测 annotation 更新?
- 一致性: 与 NAT Gateway Pod 不支持热更新的架构一致
- 简化: 避免复杂的 STS template annotation 对比逻辑
- 用户体验: 明确告知用户 annotations 仅创建时生效,避免误解