fix(deploy): honor explicit zero drain_delay and document instant switch
- commit
- 846c73816860a3b3b67c68ee8895430a5f6abcc7
- author
- bnema · · 2026-02-07 21:25 UTC
- parent
- 86ac292
- signature
- unsigned
3 files · +13 -4
M docs/config/deploy.md +2 -0
| @@ -69,6 +69,8 @@ the previous container during zero-downtime replacement. | |||
| 69 | 69 | - Uses Go duration format (examples: `"2s"`, `"10s"`, `"1m"`). | |
| 70 | 70 | - Applied only when a previous container exists and cache invalidation was | |
| 71 | 71 | triggered for the deployed domain. | |
| 72 | + | - If explicitly set to `"0s"` (or a negative duration), delay is disabled and | |
| 73 | + | Gordon switches immediately after invalidation. | |
| 72 | 74 | ||
| 73 | 75 | Default: `"2s"` | |
| 74 | 76 | ||
| @@ -69,6 +69,8 @@ the previous container during zero-downtime replacement. | |||||
| 69 | - Uses Go duration format (examples: `"2s"`, `"10s"`, `"1m"`). | 69 | - Uses Go duration format (examples: `"2s"`, `"10s"`, `"1m"`). | ||
| 70 | - Applied only when a previous container exists and cache invalidation was | 70 | - Applied only when a previous container exists and cache invalidation was | ||
| 71 | triggered for the deployed domain. | 71 | triggered for the deployed domain. | ||
| 72 | + | - If explicitly set to `"0s"` (or a negative duration), delay is disabled and | |||
| 73 | + | Gordon switches immediately after invalidation. | |||
| 72 | 74 | ||||
| 73 | Default: `"2s"` | 75 | Default: `"2s"` | ||
| 74 | 76 | ||||
M internal/app/run.go +4 -1
| @@ -1066,6 +1066,10 @@ func createContainerService(ctx context.Context, v *viper.Viper, cfg Config, svc | |||
| 1066 | 1066 | DrainMode: v.GetString("deploy.drain_mode"), | |
| 1067 | 1067 | DrainTimeout: v.GetDuration("deploy.drain_timeout"), | |
| 1068 | 1068 | } | |
| 1069 | + | if v.IsSet("deploy.drain_delay") { | |
| 1070 | + | containerConfig.DrainDelayConfigured = true | |
| 1071 | + | containerConfig.DrainDelay = v.GetDuration("deploy.drain_delay") | |
| 1072 | + | } | |
| 1069 | 1073 | ||
| 1070 | 1074 | if cfg.Auth.Enabled && svc.authSvc != nil { | |
| 1071 | 1075 | expiry, err := resolveServiceTokenExpiry(cfg) | |
| @@ -1862,7 +1866,6 @@ func loadConfig(v *viper.Viper, configPath string) error { | |||
| 1862 | 1866 | v.SetDefault("deploy.readiness_delay", "5s") | |
| 1863 | 1867 | v.SetDefault("deploy.readiness_mode", "auto") | |
| 1864 | 1868 | v.SetDefault("deploy.health_timeout", "90s") | |
| 1865 | - | v.SetDefault("deploy.drain_delay", "2s") | |
| 1866 | 1869 | v.SetDefault("deploy.drain_mode", "auto") | |
| 1867 | 1870 | v.SetDefault("deploy.drain_timeout", "30s") | |
| 1868 | 1871 | ||
| @@ -1066,6 +1066,10 @@ func createContainerService(ctx context.Context, v *viper.Viper, cfg Config, svc | |||||
| 1066 | DrainMode: v.GetString("deploy.drain_mode"), | 1066 | DrainMode: v.GetString("deploy.drain_mode"), | ||
| 1067 | DrainTimeout: v.GetDuration("deploy.drain_timeout"), | 1067 | DrainTimeout: v.GetDuration("deploy.drain_timeout"), | ||
| 1068 | } | 1068 | } | ||
| 1069 | + | if v.IsSet("deploy.drain_delay") { | |||
| 1070 | + | containerConfig.DrainDelayConfigured = true | |||
| 1071 | + | containerConfig.DrainDelay = v.GetDuration("deploy.drain_delay") | |||
| 1072 | + | } | |||
| 1069 | 1073 | ||||
| 1070 | if cfg.Auth.Enabled && svc.authSvc != nil { | 1074 | if cfg.Auth.Enabled && svc.authSvc != nil { | ||
| 1071 | expiry, err := resolveServiceTokenExpiry(cfg) | 1075 | expiry, err := resolveServiceTokenExpiry(cfg) | ||
| @@ -1862,7 +1866,6 @@ func loadConfig(v *viper.Viper, configPath string) error { | |||||
| 1862 | v.SetDefault("deploy.readiness_delay", "5s") | 1866 | v.SetDefault("deploy.readiness_delay", "5s") | ||
| 1863 | v.SetDefault("deploy.readiness_mode", "auto") | 1867 | v.SetDefault("deploy.readiness_mode", "auto") | ||
| 1864 | v.SetDefault("deploy.health_timeout", "90s") | 1868 | v.SetDefault("deploy.health_timeout", "90s") | ||
| 1865 | - | v.SetDefault("deploy.drain_delay", "2s") | |||
| 1866 | v.SetDefault("deploy.drain_mode", "auto") | 1869 | v.SetDefault("deploy.drain_mode", "auto") | ||
| 1867 | v.SetDefault("deploy.drain_timeout", "30s") | 1870 | v.SetDefault("deploy.drain_timeout", "30s") | ||
| 1868 | 1871 | ||||
M internal/usecase/container/service.go +7 -3
| @@ -39,6 +39,7 @@ type Config struct { | |||
| 39 | 39 | ReadinessMode string // Readiness strategy: auto, docker-health, delay | |
| 40 | 40 | HealthTimeout time.Duration // Max wait for health-based readiness | |
| 41 | 41 | DrainDelay time.Duration // Grace period after cache invalidation before stopping old container | |
| 42 | + | DrainDelayConfigured bool // True when deploy.drain_delay was explicitly configured | |
| 42 | 43 | DrainMode string // Drain strategy: auto, inflight, delay | |
| 43 | 44 | DrainTimeout time.Duration // Max wait for in-flight requests to drain | |
| 44 | 45 | } | |
| @@ -425,9 +426,12 @@ func (s *Service) waitForDrain(ctx context.Context, oldContainerID string) { | |||
| 425 | 426 | return | |
| 426 | 427 | } | |
| 427 | 428 | ||
| 428 | - | drainDelay := cfg.DrainDelay | |
| 429 | - | if drainDelay == 0 { | |
| 430 | - | drainDelay = 2 * time.Second | |
| 429 | + | drainDelay := 2 * time.Second | |
| 430 | + | if cfg.DrainDelayConfigured { | |
| 431 | + | drainDelay = cfg.DrainDelay | |
| 432 | + | } | |
| 433 | + | if drainDelay <= 0 { | |
| 434 | + | return | |
| 431 | 435 | } | |
| 432 | 436 | select { | |
| 433 | 437 | case <-time.After(drainDelay): | |
| @@ -39,6 +39,7 @@ type Config struct { | |||||
| 39 | ReadinessMode string // Readiness strategy: auto, docker-health, delay | 39 | ReadinessMode string // Readiness strategy: auto, docker-health, delay | ||
| 40 | HealthTimeout time.Duration // Max wait for health-based readiness | 40 | HealthTimeout time.Duration // Max wait for health-based readiness | ||
| 41 | DrainDelay time.Duration // Grace period after cache invalidation before stopping old container | 41 | DrainDelay time.Duration // Grace period after cache invalidation before stopping old container | ||
| 42 | + | DrainDelayConfigured bool // True when deploy.drain_delay was explicitly configured | |||
| 42 | DrainMode string // Drain strategy: auto, inflight, delay | 43 | DrainMode string // Drain strategy: auto, inflight, delay | ||
| 43 | DrainTimeout time.Duration // Max wait for in-flight requests to drain | 44 | DrainTimeout time.Duration // Max wait for in-flight requests to drain | ||
| 44 | } | 45 | } | ||
| @@ -425,9 +426,12 @@ func (s *Service) waitForDrain(ctx context.Context, oldContainerID string) { | |||||
| 425 | return | 426 | return | ||
| 426 | } | 427 | } | ||
| 427 | 428 | ||||
| 428 | - | drainDelay := cfg.DrainDelay | 429 | + | drainDelay := 2 * time.Second |
| 429 | - | if drainDelay == 0 { | 430 | + | if cfg.DrainDelayConfigured { |
| 430 | - | drainDelay = 2 * time.Second | 431 | + | drainDelay = cfg.DrainDelay |
| 432 | + | } | |||
| 433 | + | if drainDelay <= 0 { | |||
| 434 | + | return | |||
| 431 | } | 435 | } | ||
| 432 | select { | 436 | select { | ||
| 433 | case <-time.After(drainDelay): | 437 | case <-time.After(drainDelay): | ||