Rolling restart (#619)

* implement rolling restart functionality

bouncing each image individually can ensure that a group of docker
containers launched with docker-compose can stay 100% up during deploy.

* move rolling restart into a function

* honor params.Cleanup

Co-authored-by: Simon Aronsson <simme@arcticbit.se>
pull/598/head^2
Ben Osheroff 4 years ago committed by GitHub
parent 6a18ee911e
commit c56e0a95a7
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -30,6 +30,7 @@ var (
notifier *notifications.Notifier notifier *notifications.Notifier
timeout time.Duration timeout time.Duration
lifecycleHooks bool lifecycleHooks bool
rollingRestart bool
scope string scope string
) )
@ -91,6 +92,7 @@ func PreRun(cmd *cobra.Command, args []string) {
enableLabel, _ = f.GetBool("label-enable") enableLabel, _ = f.GetBool("label-enable")
lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks") lifecycleHooks, _ = f.GetBool("enable-lifecycle-hooks")
rollingRestart, _ = f.GetBool("rolling-restart")
scope, _ = f.GetString("scope") scope, _ = f.GetString("scope")
log.Debug(scope) log.Debug(scope)
@ -211,6 +213,7 @@ func runUpdatesWithNotifications(filter t.Filter) {
Timeout: timeout, Timeout: timeout,
MonitorOnly: monitorOnly, MonitorOnly: monitorOnly,
LifecycleHooks: lifecycleHooks, LifecycleHooks: lifecycleHooks,
RollingRestart: rollingRestart,
} }
err := actions.Update(client, updateParams) err := actions.Update(client, updateParams)
if err != nil { if err != nil {

@ -249,6 +249,17 @@ Environment Variable: WATCHTOWER_SCHEDULE
Default: - Default: -
``` ```
## Rolling restart
Restart one image at time instead of stopping and starting all at once. Useful in conjunction with lifecycle hooks
to implement zero-downtime deploy.
```
Argument: --rolling-restart
Environment Variable: WATCHTOWER_ROLLING_RESTART
Type: Boolean
Default: false
```
## Wait until timeout ## Wait until timeout
Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds. Timeout before the container is forcefully stopped. When set, this option will change the default (`10s`) wait time to the given value. An example: `--stop-timeout 30s` will set the timeout to 30 seconds.

@ -52,15 +52,33 @@ func Update(client container.Client, params types.UpdateParams) error {
return nil return nil
} }
if params.RollingRestart {
performRollingRestart(containers, client, params)
} else {
stopContainersInReversedOrder(containers, client, params) stopContainersInReversedOrder(containers, client, params)
restartContainersInSortedOrder(containers, client, params) restartContainersInSortedOrder(containers, client, params)
}
if params.LifecycleHooks { if params.LifecycleHooks {
lifecycle.ExecutePostChecks(client, params) lifecycle.ExecutePostChecks(client, params)
} }
return nil return nil
} }
func performRollingRestart(containers []container.Container, client container.Client, params types.UpdateParams) {
cleanupImageIDs := make(map[string]bool)
for i := len(containers) - 1; i >= 0; i-- {
if containers[i].Stale {
stopStaleContainer(containers[i], client, params)
restartStaleContainer(containers[i], client, params)
}
}
if params.Cleanup {
cleanupImages(client, cleanupImageIDs)
}
}
func stopContainersInReversedOrder(containers []container.Container, client container.Client, params types.UpdateParams) { func stopContainersInReversedOrder(containers []container.Container, client container.Client, params types.UpdateParams) {
for i := len(containers) - 1; i >= 0; i-- { for i := len(containers) - 1; i >= 0; i-- {
stopStaleContainer(containers[i], client, params) stopStaleContainer(containers[i], client, params)
@ -99,14 +117,19 @@ func restartContainersInSortedOrder(containers []container.Container, client con
restartStaleContainer(staleContainer, client, params) restartStaleContainer(staleContainer, client, params)
imageIDs[staleContainer.ImageID()] = true imageIDs[staleContainer.ImageID()] = true
} }
if params.Cleanup { if params.Cleanup {
cleanupImages(client, imageIDs)
}
}
func cleanupImages(client container.Client, imageIDs map[string]bool) {
for imageID := range imageIDs { for imageID := range imageIDs {
if err := client.RemoveImageByID(imageID); err != nil { if err := client.RemoveImageByID(imageID); err != nil {
log.Error(err) log.Error(err)
} }
} }
} }
}
func restartStaleContainer(container container.Container, client container.Client, params types.UpdateParams) { func restartStaleContainer(container container.Container, client container.Client, params types.UpdateParams) {
// Since we can't shutdown a watchtower container immediately, we need to // Since we can't shutdown a watchtower container immediately, we need to

@ -123,6 +123,12 @@ func RegisterSystemFlags(rootCmd *cobra.Command) {
viper.GetBool("WATCHTOWER_LIFECYCLE_HOOKS"), viper.GetBool("WATCHTOWER_LIFECYCLE_HOOKS"),
"Enable the execution of commands triggered by pre- and post-update lifecycle hooks") "Enable the execution of commands triggered by pre- and post-update lifecycle hooks")
flags.BoolP(
"rolling-restart",
"",
viper.GetBool("WATCHTOWER_ROLLING_RESTART"),
"Restart containers one at a time")
flags.BoolP( flags.BoolP(
"http-api", "http-api",
"", "",

@ -12,4 +12,5 @@ type UpdateParams struct {
Timeout time.Duration Timeout time.Duration
MonitorOnly bool MonitorOnly bool
LifecycleHooks bool LifecycleHooks bool
RollingRestart bool
} }

Loading…
Cancel
Save