|
| 1 | +### cleanup-on-exit ### |
| 2 | +在实现 cleanup_on_exit 功能的基础上,补充单元测试(config、reconciler)、e2e 测试(daemon 模式清理/保留行为)以及 README/README_CN 文档更新。 |
| 3 | + |
| 4 | +# 进程退出时按需清理 IPVS 和 iptables 规则 |
| 5 | + |
| 6 | +添加 `cleanup_on_exit` 全局配置项(默认 `true`),控制 ezlb 退出时是否删除自身管理的 IPVS 虚拟服务/后端及 iptables SNAT 规则。IPVS 精确删除 ezlb 管理的服务,不影响系统中其他 IPVS 规则;iptables 删除整个 `EZLB-SNAT` 链(该链为本项目专用)。同步补充单元测试、e2e 测试和文档。 |
| 7 | + |
| 8 | +## Proposed Changes |
| 9 | + |
| 10 | +--- |
| 11 | + |
| 12 | +### config — 新增 cleanup_on_exit 配置项 |
| 13 | + |
| 14 | +#### [MODIFY] [config.go](file:///Users/jin.gjm/code/go-network/ezlb/pkg/config/config.go) |
| 15 | + |
| 16 | +在 `GlobalConfig` 中新增字段,并添加 `IsCleanupOnExit()` 方法: |
| 17 | + |
| 18 | +```diff |
| 19 | + type GlobalConfig struct { |
| 20 | +- LogLevel string `yaml:"log_level" mapstructure:"log_level"` |
| 21 | ++ LogLevel string `yaml:"log_level" mapstructure:"log_level"` |
| 22 | ++ CleanupOnExit *bool `yaml:"cleanup_on_exit" mapstructure:"cleanup_on_exit"` |
| 23 | + } |
| 24 | + |
| 25 | ++// IsCleanupOnExit returns whether to clean up IPVS and iptables rules on exit. |
| 26 | ++// Defaults to true if not explicitly set. |
| 27 | ++func (g GlobalConfig) IsCleanupOnExit() bool { |
| 28 | ++ if g.CleanupOnExit == nil { |
| 29 | ++ return true |
| 30 | ++ } |
| 31 | ++ return *g.CleanupOnExit |
| 32 | ++} |
| 33 | +``` |
| 34 | + |
| 35 | +在 `NewManager` 中设置 viper 默认值: |
| 36 | + |
| 37 | +```diff |
| 38 | + viperInstance.SetDefault("global.log_level", "info") |
| 39 | ++viperInstance.SetDefault("global.cleanup_on_exit", true) |
| 40 | +``` |
| 41 | + |
| 42 | +--- |
| 43 | + |
| 44 | +### lvs — Reconciler 新增精确清理方法 |
| 45 | + |
| 46 | +#### [MODIFY] [reconciler.go](file:///Users/jin.gjm/code/go-network/ezlb/pkg/lvs/reconciler.go) |
| 47 | + |
| 48 | +新增 `Cleanup()` 方法,遍历 `r.managed` 精确删除 ezlb 管理的 IPVS 服务,不影响系统中其他 IPVS 规则: |
| 49 | + |
| 50 | +```go |
| 51 | +// Cleanup removes all IPVS services currently managed by this Reconciler. |
| 52 | +// It only deletes services tracked in the managed map, leaving other IPVS |
| 53 | +// rules untouched. |
| 54 | +func (r *Reconciler) Cleanup() error { |
| 55 | + r.mu.Lock() |
| 56 | + defer r.mu.Unlock() |
| 57 | + |
| 58 | + actualServices, err := r.manager.GetServices() |
| 59 | + if err != nil { |
| 60 | + return fmt.Errorf("failed to get IPVS services for cleanup: %w", err) |
| 61 | + } |
| 62 | + |
| 63 | + actualMap := make(map[ServiceKey]*Service) |
| 64 | + for _, svc := range actualServices { |
| 65 | + actualMap[ServiceKeyFromIPVS(svc)] = svc |
| 66 | + } |
| 67 | + |
| 68 | + var errs []error |
| 69 | + for key := range r.managed { |
| 70 | + svc, exists := actualMap[key] |
| 71 | + if !exists { |
| 72 | + delete(r.managed, key) |
| 73 | + continue |
| 74 | + } |
| 75 | + if err := r.manager.DeleteService(svc); err != nil { |
| 76 | + errs = append(errs, fmt.Errorf("delete service %s: %w", key, err)) |
| 77 | + } else { |
| 78 | + delete(r.managed, key) |
| 79 | + } |
| 80 | + } |
| 81 | + |
| 82 | + if len(errs) > 0 { |
| 83 | + return errors.Join(errs...) |
| 84 | + } |
| 85 | + r.logger.Info("cleaned up all managed IPVS services") |
| 86 | + return nil |
| 87 | +} |
| 88 | +``` |
| 89 | + |
| 90 | +--- |
| 91 | + |
| 92 | +### server — shutdown 逻辑按配置分支 |
| 93 | + |
| 94 | +#### [MODIFY] [server.go](file:///Users/jin.gjm/code/go-network/ezlb/pkg/server/server.go) |
| 95 | + |
| 96 | +```diff |
| 97 | + func (s *Server) shutdown() { |
| 98 | + s.healthMgr.Stop() |
| 99 | +- if err := s.snatMgr.Cleanup(); err != nil { |
| 100 | +- s.logger.Error("failed to cleanup SNAT rules", zap.Error(err)) |
| 101 | +- } |
| 102 | ++ cfg := s.configMgr.GetConfig() |
| 103 | ++ if cfg.Global.IsCleanupOnExit() { |
| 104 | ++ if err := s.reconciler.Cleanup(); err != nil { |
| 105 | ++ s.logger.Error("failed to cleanup IPVS rules", zap.Error(err)) |
| 106 | ++ } |
| 107 | ++ if err := s.snatMgr.Cleanup(); err != nil { |
| 108 | ++ s.logger.Error("failed to cleanup SNAT rules", zap.Error(err)) |
| 109 | ++ } |
| 110 | ++ } else { |
| 111 | ++ s.logger.Info("cleanup_on_exit is false, preserving IPVS and iptables rules") |
| 112 | ++ } |
| 113 | + s.lvsMgr.Close() |
| 114 | + s.logger.Info("server stopped") |
| 115 | + } |
| 116 | +``` |
| 117 | + |
| 118 | +--- |
| 119 | + |
| 120 | +### examples — 更新示例配置 |
| 121 | + |
| 122 | +#### [MODIFY] [ezlb.yaml](file:///Users/jin.gjm/code/go-network/ezlb/examples/ezlb.yaml) |
| 123 | + |
| 124 | +```diff |
| 125 | + global: |
| 126 | + log_level: info |
| 127 | ++ # cleanup_on_exit: true # Remove managed IPVS services and EZLB-SNAT iptables chain on exit (default: true) |
| 128 | +``` |
| 129 | + |
| 130 | +--- |
| 131 | + |
| 132 | +### 单元测试 |
| 133 | + |
| 134 | +#### [MODIFY] [config_test.go](file:///Users/jin.gjm/code/go-network/ezlb/pkg/config/config_test.go) |
| 135 | + |
| 136 | +在现有测试文件末尾追加 `GlobalConfig.IsCleanupOnExit()` 的测试用例: |
| 137 | + |
| 138 | +- `TestGlobalConfig_IsCleanupOnExit_DefaultTrue` — `CleanupOnExit` 为 nil 时返回 true |
| 139 | +- `TestGlobalConfig_IsCleanupOnExit_ExplicitTrue` — 显式设置 true 时返回 true |
| 140 | +- `TestGlobalConfig_IsCleanupOnExit_ExplicitFalse` — 显式设置 false 时返回 false |
| 141 | +- `TestManager_LoadYAML_CleanupOnExitDefault` — 不配置时加载后默认为 true |
| 142 | +- `TestManager_LoadYAML_CleanupOnExitFalse` — 配置 `cleanup_on_exit: false` 后加载正确 |
| 143 | + |
| 144 | +#### [MODIFY] [reconciler_test.go](file:///Users/jin.gjm/code/go-network/ezlb/pkg/lvs/reconciler_test.go) |
| 145 | + |
| 146 | +追加 `Reconciler.Cleanup()` 的测试用例: |
| 147 | + |
| 148 | +- `TestReconciler_Cleanup_RemovesManagedServices` — reconcile 后调用 Cleanup,验证 managed 服务被删除 |
| 149 | +- `TestReconciler_Cleanup_PreservesUnmanagedServices` — 手动创建一个 ezlb 未管理的服务,调用 Cleanup 后该服务仍然存在 |
| 150 | +- `TestReconciler_Cleanup_EmptyManaged` — managed 为空时调用 Cleanup 不报错 |
| 151 | +- `TestReconciler_Cleanup_WithFullNATService` — 包含 FullNAT 服务时,Cleanup 正确删除 IPVS 服务(SNAT 清理由 snatMgr 负责,此处验证 IPVS 部分) |
| 152 | + |
| 153 | +--- |
| 154 | + |
| 155 | +### e2e 测试 |
| 156 | + |
| 157 | +#### [MODIFY] [e2e_test.go](file:///Users/jin.gjm/code/go-network/ezlb/tests/e2e/e2e_test.go) |
| 158 | + |
| 159 | +追加两个 daemon 模式测试用例: |
| 160 | + |
| 161 | +**Test 9: `cleanup_on_exit: true`(默认)— 退出后 IPVS 规则被清理** |
| 162 | + |
| 163 | +``` |
| 164 | +TestE2E_DaemonMode_CleanupOnExit_True |
| 165 | +``` |
| 166 | +- 启动 daemon,配置 `cleanup_on_exit: true` |
| 167 | +- 等待初始 reconcile 完成,验证 IPVS 服务存在 |
| 168 | +- 发送 SIGTERM,等待进程退出 |
| 169 | +- 验证 IPVS 中对应服务已被删除 |
| 170 | + |
| 171 | +**Test 10: `cleanup_on_exit: false` — 退出后 IPVS 规则保留** |
| 172 | + |
| 173 | +``` |
| 174 | +TestE2E_DaemonMode_CleanupOnExit_False |
| 175 | +``` |
| 176 | +- 启动 daemon,配置 `cleanup_on_exit: false` |
| 177 | +- 等待初始 reconcile 完成,验证 IPVS 服务存在 |
| 178 | +- 发送 SIGTERM,等待进程退出 |
| 179 | +- 验证 IPVS 中对应服务仍然存在 |
| 180 | + |
| 181 | +--- |
| 182 | + |
| 183 | +### 文档更新 |
| 184 | + |
| 185 | +#### [MODIFY] [README.md](file:///Users/jin.gjm/code/go-network/ezlb/README.md) |
| 186 | + |
| 187 | +在配置参考章节的 `global` 部分新增 `cleanup_on_exit` 字段说明。 |
| 188 | + |
| 189 | +#### [MODIFY] [README_CN.md](file:///Users/jin.gjm/code/go-network/ezlb/README_CN.md) |
| 190 | + |
| 191 | +同步更新中文文档,在 `global` 配置说明中新增 `cleanup_on_exit` 字段。 |
| 192 | + |
| 193 | +## Verification Plan |
| 194 | + |
| 195 | +### Automated Tests |
| 196 | + |
| 197 | +```bash |
| 198 | +# 单元测试(全平台) |
| 199 | +go test ./pkg/config/... ./pkg/lvs/... ./pkg/snat/... |
| 200 | + |
| 201 | +# e2e 测试(需要 Linux + root) |
| 202 | +go test -v -tags integration ./tests/e2e/... |
| 203 | +``` |
| 204 | + |
| 205 | +### Manual Verification |
| 206 | + |
| 207 | +- Linux 环境下启动 ezlb,配置 `cleanup_on_exit: true`,发送 SIGTERM,验证 `ipvsadm -L` 中对应服务已删除,`iptables -t nat -L` 中 `EZLB-SNAT` 链已消失 |
| 208 | +- 配置 `cleanup_on_exit: false`,发送 SIGTERM,验证 IPVS 规则和 `EZLB-SNAT` 链均保留 |
| 209 | + |
| 210 | + |
| 211 | +## Tasks |
| 212 | + |
| 213 | +- [ ] **pkg/config/config.go** — GlobalConfig 新增 `CleanupOnExit *bool` 字段、`IsCleanupOnExit()` 方法,viper 设置默认值 true |
| 214 | +- [ ] **pkg/lvs/reconciler.go** — 新增 `Cleanup()` 方法,精确删除 managed 的 IPVS 服务 |
| 215 | +- [ ] **pkg/server/server.go** — `shutdown()` 按 `IsCleanupOnExit()` 分支调用清理或跳过 |
| 216 | +- [ ] **examples/ezlb.yaml** — 添加 `cleanup_on_exit` 注释说明 |
| 217 | +- [ ] **pkg/config/config_test.go** — 追加 `IsCleanupOnExit()` 和配置加载相关单元测试(5 个用例) |
| 218 | +- [ ] **pkg/lvs/reconciler_test.go** — 追加 `Reconciler.Cleanup()` 单元测试(4 个用例) |
| 219 | +- [ ] **tests/e2e/e2e_test.go** — 追加 daemon 模式 cleanup_on_exit=true 和 false 的 e2e 测试(2 个用例) |
| 220 | +- [ ] **README.md** — 在 global 配置章节新增 `cleanup_on_exit` 字段说明 |
| 221 | +- [ ] **README_CN.md** — 同步更新中文文档 |
| 222 | +- [ ] 运行 `go test ./pkg/config/... ./pkg/lvs/... ./pkg/snat/...` 验证无编译错误和测试回归 |
| 223 | + |
| 224 | +updateAtTime: 2026/3/16 11:13:24 |
| 225 | + |
| 226 | +planId: 138ca5d8-5bcf-4e88-a5e3-ce78d9816ba6 |
0 commit comments