VpcNatGateway 支持自定义 Annotations

8 阅读4分钟

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 不会触发更新。

原因:

  1. NAT Gateway Pod 不支持热更新,任何模板变更都会触发 Pod 重建
  2. 简化实现,避免复杂的变更检测逻辑
  3. 如需更新 annotations,用户可以删除并重建 gateway

2.2 无安全过滤

设计选择: 不对用户 annotations 进行敏感 key 过滤。

原因:

  1. GenNatGwPodAnnotations 会自动覆盖所有系统 annotations,用户无法注入冲突 key
  2. handleUpdateNatGwSubnetRoute 中已有 CIDR 格式校验提供纵深防御
  3. 过滤逻辑是过度工程化,增加维护负担但无实际安全收益

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 单元测试

测试函数用例数覆盖内容
TestNeedRestartRecovery3重启计数检测
TestIsVpcNatGwChanged5Spec 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
v42025-02-03添加 Contains 检测、安全过滤函数
v52025-02-03最终实现: 移除安全过滤 (过度工程化),annotations 仅创建时生效

10. 设计取舍说明

10.1 为什么移除安全过滤?

  1. 冲突覆盖机制: GenNatGwPodAnnotations 确保系统 annotations 总是覆盖用户 key
  2. CIDR 校验: handleUpdateNatGwSubnetRoute 提供了实际的安全防护
  3. 维护成本: 敏感 key 列表需要持续维护,但无实际安全收益

10.2 为什么不检测 annotation 更新?

  1. 一致性: 与 NAT Gateway Pod 不支持热更新的架构一致
  2. 简化: 避免复杂的 STS template annotation 对比逻辑
  3. 用户体验: 明确告知用户 annotations 仅创建时生效,避免误解