Configure HealthCheck with `podman update`

New flags in a `podman update` can change the configuration of HealthCheck when the container is started, without having to restart or recreate the container.

This can help determine why a given container suddenly started failing HealthCheck without interfering with the services it provides. For example, reconfigure HealthCheck to keep logs longer than the usual last X results, store logs to other destinations, etc.

Fixes: https://issues.redhat.com/browse/RHEL-60561

Signed-off-by: Jan Rodák <hony.com@seznam.cz>
This commit is contained in:
Jan Rodák 2024-10-24 14:01:58 +02:00
parent 77e67e7a54
commit a1249425bd
No known key found for this signature in database
GPG Key ID: D458A9B20435C2BF
34 changed files with 958 additions and 198 deletions

View File

@ -168,78 +168,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(groupAddFlagName, completion.AutocompleteNone)
healthCmdFlagName := "health-cmd"
createFlags.StringVar(
&cf.HealthCmd,
healthCmdFlagName, "",
"set a healthcheck command for the container ('none' disables the existing healthcheck)",
)
_ = cmd.RegisterFlagCompletionFunc(healthCmdFlagName, completion.AutocompleteNone)
healthIntervalFlagName := "health-interval"
createFlags.StringVar(
&cf.HealthInterval,
healthIntervalFlagName, define.DefaultHealthCheckInterval,
"set an interval for the healthcheck (a value of disable results in no automatic timer setup)",
)
_ = cmd.RegisterFlagCompletionFunc(healthIntervalFlagName, completion.AutocompleteNone)
healthLogDestinationFlagName := "health-log-destination"
createFlags.StringVar(
&cf.HealthLogDestination,
healthLogDestinationFlagName, define.DefaultHealthCheckLocalDestination,
"set the destination of the HealthCheck log. Directory path, local or events_logger (local use container state file)",
)
_ = cmd.RegisterFlagCompletionFunc(healthLogDestinationFlagName, completion.AutocompleteNone)
healthMaxLogCountFlagName := "health-max-log-count"
createFlags.UintVar(
&cf.HealthMaxLogCount,
healthMaxLogCountFlagName, define.DefaultHealthMaxLogCount,
"set maximum number of attempts in the HealthCheck log file. ('0' value means an infinite number of attempts in the log file)",
)
_ = cmd.RegisterFlagCompletionFunc(healthMaxLogCountFlagName, completion.AutocompleteNone)
healthMaxLogSizeFlagName := "health-max-log-size"
createFlags.UintVar(
&cf.HealthMaxLogSize,
healthMaxLogSizeFlagName, define.DefaultHealthMaxLogSize,
"set maximum length in characters of stored HealthCheck log. ('0' value means an infinite log length)",
)
_ = cmd.RegisterFlagCompletionFunc(healthMaxLogSizeFlagName, completion.AutocompleteNone)
healthRetriesFlagName := "health-retries"
createFlags.UintVar(
&cf.HealthRetries,
healthRetriesFlagName, define.DefaultHealthCheckRetries,
"the number of retries allowed before a healthcheck is considered to be unhealthy",
)
_ = cmd.RegisterFlagCompletionFunc(healthRetriesFlagName, completion.AutocompleteNone)
healthStartPeriodFlagName := "health-start-period"
createFlags.StringVar(
&cf.HealthStartPeriod,
healthStartPeriodFlagName, define.DefaultHealthCheckStartPeriod,
"the initialization time needed for a container to bootstrap",
)
_ = cmd.RegisterFlagCompletionFunc(healthStartPeriodFlagName, completion.AutocompleteNone)
healthTimeoutFlagName := "health-timeout"
createFlags.StringVar(
&cf.HealthTimeout,
healthTimeoutFlagName, define.DefaultHealthCheckTimeout,
"the maximum time allowed to complete the healthcheck before an interval is considered failed",
)
_ = cmd.RegisterFlagCompletionFunc(healthTimeoutFlagName, completion.AutocompleteNone)
healthOnFailureFlagName := "health-on-failure"
createFlags.StringVar(
&cf.HealthOnFailure,
healthOnFailureFlagName, "none",
"action to take once the container turns unhealthy",
)
_ = cmd.RegisterFlagCompletionFunc(healthOnFailureFlagName, AutocompleteHealthOnFailure)
createFlags.BoolVar(
&cf.HTTPProxy,
"http-proxy", podmanConfig.ContainersConfDefaultsRO.Containers.HTTPProxy,
@ -311,11 +239,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(logOptFlagName, AutocompleteLogOpt)
createFlags.BoolVar(
&cf.NoHealthCheck,
"no-healthcheck", false,
"Disable healthchecks on container",
)
createFlags.BoolVar(
&cf.OOMKillDisable,
"oom-kill-disable", false,
@ -452,46 +375,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
)
_ = cmd.RegisterFlagCompletionFunc(secretFlagName, AutocompleteSecrets)
startupHCCmdFlagName := "health-startup-cmd"
createFlags.StringVar(
&cf.StartupHCCmd,
startupHCCmdFlagName, "",
"Set a startup healthcheck command for the container",
)
_ = cmd.RegisterFlagCompletionFunc(startupHCCmdFlagName, completion.AutocompleteNone)
startupHCIntervalFlagName := "health-startup-interval"
createFlags.StringVar(
&cf.StartupHCInterval,
startupHCIntervalFlagName, define.DefaultHealthCheckInterval,
"Set an interval for the startup healthcheck",
)
_ = cmd.RegisterFlagCompletionFunc(startupHCIntervalFlagName, completion.AutocompleteNone)
startupHCRetriesFlagName := "health-startup-retries"
createFlags.UintVar(
&cf.StartupHCRetries,
startupHCRetriesFlagName, 0,
"Set the maximum number of retries before the startup healthcheck will restart the container",
)
_ = cmd.RegisterFlagCompletionFunc(startupHCRetriesFlagName, completion.AutocompleteNone)
startupHCSuccessesFlagName := "health-startup-success"
createFlags.UintVar(
&cf.StartupHCSuccesses,
startupHCSuccessesFlagName, 0,
"Set the number of consecutive successes before the startup healthcheck is marked as successful and the normal healthcheck begins (0 indicates any success will start the regular healthcheck)",
)
_ = cmd.RegisterFlagCompletionFunc(startupHCSuccessesFlagName, completion.AutocompleteNone)
startupHCTimeoutFlagName := "health-startup-timeout"
createFlags.StringVar(
&cf.StartupHCTimeout,
startupHCTimeoutFlagName, define.DefaultHealthCheckTimeout,
"Set the maximum amount of time that the startup healthcheck may take before it is considered failed",
)
_ = cmd.RegisterFlagCompletionFunc(startupHCTimeoutFlagName, completion.AutocompleteNone)
stopSignalFlagName := "stop-signal"
createFlags.StringVar(
&cf.StopSignal,
@ -665,6 +548,140 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
`If a container with the same name exists, replace it`,
)
}
if mode == entities.CreateMode || mode == entities.UpdateMode {
createFlags.BoolVar(
&cf.NoHealthCheck,
"no-healthcheck", false,
"Disable healthchecks on container",
)
healthCmdFlagName := "health-cmd"
createFlags.StringVar(
&cf.HealthCmd,
healthCmdFlagName, "",
"set a healthcheck command for the container ('none' disables the existing healthcheck)",
)
_ = cmd.RegisterFlagCompletionFunc(healthCmdFlagName, completion.AutocompleteNone)
info := ""
if mode == entities.UpdateMode {
info = "Changing this setting resets timer."
}
healthIntervalFlagName := "health-interval"
createFlags.StringVar(
&cf.HealthInterval,
healthIntervalFlagName, define.DefaultHealthCheckInterval,
"set an interval for the healthcheck. (a value of disable results in no automatic timer setup) "+info,
)
_ = cmd.RegisterFlagCompletionFunc(healthIntervalFlagName, completion.AutocompleteNone)
warning := ""
if mode == entities.UpdateMode {
warning = "Warning: Changing this setting may cause the loss of previous logs!"
}
healthLogDestinationFlagName := "health-log-destination"
createFlags.StringVar(
&cf.HealthLogDestination,
healthLogDestinationFlagName, define.DefaultHealthCheckLocalDestination,
"set the destination of the HealthCheck log. Directory path, local or events_logger (local use container state file) "+warning,
)
_ = cmd.RegisterFlagCompletionFunc(healthLogDestinationFlagName, completion.AutocompleteNone)
healthMaxLogCountFlagName := "health-max-log-count"
createFlags.UintVar(
&cf.HealthMaxLogCount,
healthMaxLogCountFlagName, define.DefaultHealthMaxLogCount,
"set maximum number of attempts in the HealthCheck log file. ('0' value means an infinite number of attempts in the log file)",
)
_ = cmd.RegisterFlagCompletionFunc(healthMaxLogCountFlagName, completion.AutocompleteNone)
healthMaxLogSizeFlagName := "health-max-log-size"
createFlags.UintVar(
&cf.HealthMaxLogSize,
healthMaxLogSizeFlagName, define.DefaultHealthMaxLogSize,
"set maximum length in characters of stored HealthCheck log. ('0' value means an infinite log length)",
)
_ = cmd.RegisterFlagCompletionFunc(healthMaxLogSizeFlagName, completion.AutocompleteNone)
healthRetriesFlagName := "health-retries"
createFlags.UintVar(
&cf.HealthRetries,
healthRetriesFlagName, define.DefaultHealthCheckRetries,
"the number of retries allowed before a healthcheck is considered to be unhealthy",
)
_ = cmd.RegisterFlagCompletionFunc(healthRetriesFlagName, completion.AutocompleteNone)
healthStartPeriodFlagName := "health-start-period"
createFlags.StringVar(
&cf.HealthStartPeriod,
healthStartPeriodFlagName, define.DefaultHealthCheckStartPeriod,
"the initialization time needed for a container to bootstrap",
)
_ = cmd.RegisterFlagCompletionFunc(healthStartPeriodFlagName, completion.AutocompleteNone)
healthTimeoutFlagName := "health-timeout"
createFlags.StringVar(
&cf.HealthTimeout,
healthTimeoutFlagName, define.DefaultHealthCheckTimeout,
"the maximum time allowed to complete the healthcheck before an interval is considered failed",
)
_ = cmd.RegisterFlagCompletionFunc(healthTimeoutFlagName, completion.AutocompleteNone)
healthOnFailureFlagName := "health-on-failure"
createFlags.StringVar(
&cf.HealthOnFailure,
healthOnFailureFlagName, "none",
"action to take once the container turns unhealthy",
)
_ = cmd.RegisterFlagCompletionFunc(healthOnFailureFlagName, AutocompleteHealthOnFailure)
// Startup HealthCheck
startupHCCmdFlagName := "health-startup-cmd"
createFlags.StringVar(
&cf.StartupHCCmd,
startupHCCmdFlagName, "",
"Set a startup healthcheck command for the container",
)
_ = cmd.RegisterFlagCompletionFunc(startupHCCmdFlagName, completion.AutocompleteNone)
info = ""
if mode == entities.UpdateMode {
info = "Changing this setting resets the timer, depending on the state of the container."
}
startupHCIntervalFlagName := "health-startup-interval"
createFlags.StringVar(
&cf.StartupHCInterval,
startupHCIntervalFlagName, define.DefaultHealthCheckInterval,
"Set an interval for the startup healthcheck. "+info,
)
_ = cmd.RegisterFlagCompletionFunc(startupHCIntervalFlagName, completion.AutocompleteNone)
startupHCRetriesFlagName := "health-startup-retries"
createFlags.UintVar(
&cf.StartupHCRetries,
startupHCRetriesFlagName, 0,
"Set the maximum number of retries before the startup healthcheck will restart the container",
)
_ = cmd.RegisterFlagCompletionFunc(startupHCRetriesFlagName, completion.AutocompleteNone)
startupHCSuccessesFlagName := "health-startup-success"
createFlags.UintVar(
&cf.StartupHCSuccesses,
startupHCSuccessesFlagName, 0,
"Set the number of consecutive successes before the startup healthcheck is marked as successful and the normal healthcheck begins (0 indicates any success will start the regular healthcheck)",
)
_ = cmd.RegisterFlagCompletionFunc(startupHCSuccessesFlagName, completion.AutocompleteNone)
startupHCTimeoutFlagName := "health-startup-timeout"
createFlags.StringVar(
&cf.StartupHCTimeout,
startupHCTimeoutFlagName, define.DefaultHealthCheckTimeout,
"Set the maximum amount of time that the startup healthcheck may take before it is considered failed",
)
_ = cmd.RegisterFlagCompletionFunc(startupHCTimeoutFlagName, completion.AutocompleteNone)
}
// Restart is allowed for created, updated, and infra ctr
if mode == entities.InfraMode || mode == entities.CreateMode || mode == entities.UpdateMode {
restartFlagName := "restart"

View File

@ -17,7 +17,7 @@ import (
)
var (
updateDescription = `Updates the cgroup configuration of a given container`
updateDescription = `Updates the configuration of an existing container, allowing changes to resource limits and healthchecks`
updateCommand = &cobra.Command{
Use: "update [options] CONTAINER",
@ -61,6 +61,58 @@ func init() {
updateFlags(containerUpdateCommand)
}
func GetChangedHealthCheckConfiguration(cmd *cobra.Command, vals *entities.ContainerCreateOptions) define.UpdateHealthCheckConfig {
updateHealthCheckConfig := define.UpdateHealthCheckConfig{}
if cmd.Flags().Changed("health-log-destination") {
updateHealthCheckConfig.HealthLogDestination = &vals.HealthLogDestination
}
if cmd.Flags().Changed("health-max-log-size") {
updateHealthCheckConfig.HealthMaxLogSize = &vals.HealthMaxLogSize
}
if cmd.Flags().Changed("health-max-log-count") {
updateHealthCheckConfig.HealthMaxLogCount = &vals.HealthMaxLogCount
}
if cmd.Flags().Changed("health-on-failure") {
updateHealthCheckConfig.HealthOnFailure = &vals.HealthOnFailure
}
if cmd.Flags().Changed("no-healthcheck") {
updateHealthCheckConfig.NoHealthCheck = &vals.NoHealthCheck
}
if cmd.Flags().Changed("health-cmd") {
updateHealthCheckConfig.HealthCmd = &vals.HealthCmd
}
if cmd.Flags().Changed("health-interval") {
updateHealthCheckConfig.HealthInterval = &vals.HealthInterval
}
if cmd.Flags().Changed("health-retries") {
updateHealthCheckConfig.HealthRetries = &vals.HealthRetries
}
if cmd.Flags().Changed("health-timeout") {
updateHealthCheckConfig.HealthTimeout = &vals.HealthTimeout
}
if cmd.Flags().Changed("health-start-period") {
updateHealthCheckConfig.HealthStartPeriod = &vals.HealthStartPeriod
}
if cmd.Flags().Changed("health-startup-cmd") {
updateHealthCheckConfig.HealthStartupCmd = &vals.StartupHCCmd
}
if cmd.Flags().Changed("health-startup-interval") {
updateHealthCheckConfig.HealthStartupInterval = &vals.StartupHCInterval
}
if cmd.Flags().Changed("health-startup-retries") {
updateHealthCheckConfig.HealthStartupRetries = &vals.StartupHCRetries
}
if cmd.Flags().Changed("health-startup-timeout") {
updateHealthCheckConfig.HealthStartupTimeout = &vals.StartupHCTimeout
}
if cmd.Flags().Changed("health-startup-success") {
updateHealthCheckConfig.HealthStartupSuccess = &vals.StartupHCSuccesses
}
return updateHealthCheckConfig
}
func update(cmd *cobra.Command, args []string) error {
var err error
// use a specgen since this is the easiest way to hold resource info
@ -89,9 +141,15 @@ func update(cmd *cobra.Command, args []string) error {
return err
}
healthCheckConfig := GetChangedHealthCheckConfiguration(cmd, &updateOpts)
if err != nil {
return err
}
opts := &entities.ContainerUpdateOptions{
NameOrID: strings.TrimPrefix(args[0], "/"),
Specgen: s,
NameOrID: strings.TrimPrefix(args[0], "/"),
Specgen: s,
ChangedHealthCheckConfiguration: &healthCheckConfig,
}
rep, err := registry.ContainerEngine().ContainerUpdate(context.Background(), opts)
if err != nil {

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-cmd**=*"command"* | *'["command", "arg1", ...]'*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-interval**=*interval*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-log-destination**=*directory_path*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-max-log-count**=*number of stored logs*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-max-log-size**=*size of stored logs*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-on-failure**=*action*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-retries**=*retries*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-start-period**=*period*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-startup-cmd**=*"command"* | *'["command", "arg1", ...]'*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-startup-interval**=*interval*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-startup-retries**=*retries*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-startup-success**=*retries*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-startup-timeout**=*timeout*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--health-timeout**=*timeout*

View File

@ -1,5 +1,5 @@
####> This option file is used in:
####> podman create, run
####> podman create, run, update
####> If file is edited, make sure the changes
####> are applicable to all of those.
#### **--no-healthcheck**

View File

@ -10,8 +10,7 @@ podman\-update - Update the configuration of a given container
## DESCRIPTION
Updates the configuration of an already existing container, allowing different resource limits to be set.
The currently supported options are a subset of the podman create/run resource limit options.
Updates the configuration of an existing container, allowing changes to resource limits and healthchecks.
## OPTIONS
@ -43,6 +42,40 @@ The currently supported options are a subset of the podman create/run resource l
@@option device-write-iops
@@option health-cmd
@@option health-interval
Changing this setting resets the timer.
@@option health-log-destination
Warning: Changing this setting may cause the loss of previous logs.
@@option health-max-log-count
@@option health-max-log-size
@@option health-on-failure
@@option health-retries
@@option health-start-period
@@option health-startup-cmd
@@option health-startup-interval
Changing this setting resets the timer, depending on the state of the container.
@@option health-startup-retries
@@option health-startup-success
@@option health-startup-timeout
@@option health-timeout
@@option memory
@@option memory-reservation
@ -51,6 +84,8 @@ The currently supported options are a subset of the podman create/run resource l
@@option memory-swappiness
@@option no-healthcheck
@@option pids-limit
@@option restart

View File

@ -116,10 +116,11 @@ func (c *Container) Start(ctx context.Context, recursive bool) (finalErr error)
}
// Update updates the given container.
// Either resource limits or restart policy can be updated.
// Either resources or restartPolicy must not be nil.
// Either resource limits, restart policies, or HealthCheck configuration can be updated.
// Either resources, restartPolicy or changedHealthCheckConfiguration must not be nil.
// If restartRetries is not nil, restartPolicy must be set and must be "on-failure".
func (c *Container) Update(resources *spec.LinuxResources, restartPolicy *string, restartRetries *uint) error {
// Nil values of changedHealthCheckConfiguration are not updated.
func (c *Container) Update(resources *spec.LinuxResources, restartPolicy *string, restartRetries *uint, changedHealthCheckConfiguration *define.UpdateHealthCheckConfig) error {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
@ -133,6 +134,41 @@ func (c *Container) Update(resources *spec.LinuxResources, restartPolicy *string
return fmt.Errorf("container %s is being removed, cannot update: %w", c.ID(), define.ErrCtrStateInvalid)
}
healthCheckConfig, changedHealthCheck, err := GetNewHealthCheckConfig(&HealthCheckConfig{Schema2HealthConfig: c.HealthCheckConfig()}, *changedHealthCheckConfiguration)
if err != nil {
return err
}
if changedHealthCheck {
if err := c.updateHealthCheck(
healthCheckConfig,
&HealthCheckConfig{Schema2HealthConfig: c.config.HealthCheckConfig},
); err != nil {
return err
}
}
startupHealthCheckConfig, changedStartupHealthCheck, err := GetNewHealthCheckConfig(&StartupHealthCheckConfig{StartupHealthCheck: c.Config().StartupHealthCheckConfig}, *changedHealthCheckConfiguration)
if err != nil {
return err
}
if changedStartupHealthCheck {
if err := c.updateHealthCheck(
startupHealthCheckConfig,
&StartupHealthCheckConfig{StartupHealthCheck: c.config.StartupHealthCheckConfig},
); err != nil {
return err
}
}
globalHealthCheckOptions, err := changedHealthCheckConfiguration.GetNewGlobalHealthCheck()
if err != nil {
return err
}
if err := c.updateGlobalHealthCheckConfiguration(globalHealthCheckOptions); err != nil {
return err
}
defer c.newContainerEvent(events.Update)
return c.update(resources, restartPolicy, restartRetries)
}

View File

@ -2733,8 +2733,123 @@ func (c *Container) update(resources *spec.LinuxResources, restartPolicy *string
}
logrus.Debugf("updated container %s", c.ID())
c.newContainerEvent(events.Update)
return nil
}
func (c *Container) resetHealthCheckTimers(noHealthCheck bool, changedTimer bool, wasEnabledHealthCheck bool, isStartup bool) error {
if !c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStatePaused) {
return nil
}
if noHealthCheck {
if err := c.removeTransientFiles(context.Background(),
c.config.StartupHealthCheckConfig != nil && !c.state.StartupHCPassed,
c.state.HCUnitName); err != nil {
return err
}
return nil
}
if !changedTimer {
return nil
}
if !isStartup {
if c.state.StartupHCPassed || c.config.StartupHealthCheckConfig == nil {
c.recreateHealthCheckTimer(context.Background(), false, false)
}
return nil
}
if !c.state.StartupHCPassed {
c.state.StartupHCPassed = !wasEnabledHealthCheck
c.state.StartupHCSuccessCount = 0
c.state.StartupHCFailureCount = 0
if err := c.save(); err != nil {
return err
}
if wasEnabledHealthCheck {
c.recreateHealthCheckTimer(context.Background(), true, true)
}
return nil
}
return nil
}
func (c *Container) updateHealthCheck(newHealthCheckConfig IHealthCheckConfig, currentHealthCheckConfig IHealthCheckConfig) error {
oldHealthCheckConfig := currentHealthCheckConfig
if !oldHealthCheckConfig.IsNil() {
if err := JSONDeepCopy(currentHealthCheckConfig, oldHealthCheckConfig); err != nil {
return err
}
}
newHealthCheckConfig.SetTo(c.config)
if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil {
// Assume DB write failed, revert to old resources block
oldHealthCheckConfig.SetTo(c.config)
return err
}
oldInterval := time.Duration(0)
if !oldHealthCheckConfig.IsNil() {
oldInterval = oldHealthCheckConfig.GetInterval()
}
changedTimer := false
if !newHealthCheckConfig.IsNil() {
changedTimer = newHealthCheckConfig.IsTimeChanged(oldInterval)
}
noHealthCheck := c.config.HealthCheckConfig != nil && slices.Contains(c.config.HealthCheckConfig.Test, "NONE")
if err := c.resetHealthCheckTimers(noHealthCheck, changedTimer, !oldHealthCheckConfig.IsNil(), newHealthCheckConfig.IsStartup()); err != nil {
return err
}
checkType := "HealthCheck"
if newHealthCheckConfig.IsStartup() {
checkType = "Startup HealthCheck"
}
logrus.Debugf("%s configuration updated for container %s", checkType, c.ID())
return nil
}
func (c *Container) updateGlobalHealthCheckConfiguration(globalOptions define.GlobalHealthCheckOptions) error {
oldHealthCheckOnFailureAction := c.config.HealthCheckOnFailureAction
oldHealthLogDestination := c.config.HealthLogDestination
oldHealthMaxLogCount := c.config.HealthMaxLogCount
oldHealthMaxLogSize := c.config.HealthMaxLogSize
if globalOptions.HealthCheckOnFailureAction != nil {
c.config.HealthCheckOnFailureAction = *globalOptions.HealthCheckOnFailureAction
}
if globalOptions.HealthMaxLogCount != nil {
c.config.HealthMaxLogCount = *globalOptions.HealthMaxLogCount
}
if globalOptions.HealthMaxLogSize != nil {
c.config.HealthMaxLogSize = *globalOptions.HealthMaxLogSize
}
if globalOptions.HealthLogDestination != nil {
dest, err := define.GetValidHealthCheckDestination(*globalOptions.HealthLogDestination)
if err != nil {
return err
}
c.config.HealthLogDestination = dest
}
if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil {
// Assume DB write failed, revert to old resources block
c.config.HealthCheckOnFailureAction = oldHealthCheckOnFailureAction
c.config.HealthLogDestination = oldHealthLogDestination
c.config.HealthMaxLogCount = oldHealthMaxLogCount
c.config.HealthMaxLogSize = oldHealthMaxLogSize
return err
}
logrus.Debugf("Global HealthCheck configuration updated for container %s", c.ID())
return nil
}

View File

@ -2,6 +2,8 @@ package define
import (
"fmt"
"os"
"path/filepath"
"strings"
"github.com/containers/image/v5/manifest"
@ -155,3 +157,186 @@ type StartupHealthCheck struct {
// If set to 0, a single success will mark the HC as passed.
Successes int `json:",omitempty"`
}
type UpdateHealthCheckConfig struct {
// HealthLogDestination set the destination of the HealthCheck log.
// Directory path, local or events_logger (local use container state file)
// Warning: Changing this setting may cause the loss of previous logs!
HealthLogDestination *string `json:"health_log_destination,omitempty"`
// HealthMaxLogSize set maximum length in characters of stored HealthCheck log.
// ('0' value means an infinite log length)
HealthMaxLogSize *uint `json:"health_max_log_size,omitempty"`
// HealthMaxLogCount set maximum number of attempts in the HealthCheck log file.
// ('0' value means an infinite number of attempts in the log file)
HealthMaxLogCount *uint `json:"health_max_log_count,omitempty"`
// HealthOnFailure set the action to take once the container turns unhealthy.
HealthOnFailure *string `json:"health_on_failure,omitempty"`
// Disable healthchecks on container.
NoHealthCheck *bool `json:"no_healthcheck,omitempty"`
// HealthCmd set a healthcheck command for the container. ('none' disables the existing healthcheck)
HealthCmd *string `json:"health_cmd,omitempty"`
// HealthInterval set an interval for the healthcheck.
// (a value of disable results in no automatic timer setup) Changing this setting resets timer.
HealthInterval *string `json:"health_interval,omitempty"`
// HealthRetries set the number of retries allowed before a healthcheck is considered to be unhealthy.
HealthRetries *uint `json:"health_retries,omitempty"`
// HealthTimeout set the maximum time allowed to complete the healthcheck before an interval is considered failed.
HealthTimeout *string `json:"health_timeout,omitempty"`
// HealthStartPeriod set the initialization time needed for a container to bootstrap.
HealthStartPeriod *string `json:"health_start_period,omitempty"`
// HealthStartupCmd set a startup healthcheck command for the container.
HealthStartupCmd *string `json:"health_startup_cmd,omitempty"`
// HealthStartupInterval set an interval for the startup healthcheck.
// Changing this setting resets the timer, depending on the state of the container.
HealthStartupInterval *string `json:"health_startup_interval,omitempty"`
// HealthStartupRetries set the maximum number of retries before the startup healthcheck will restart the container.
HealthStartupRetries *uint `json:"health_startup_retries,omitempty"`
// HealthStartupTimeout set the maximum amount of time that the startup healthcheck may take before it is considered failed.
HealthStartupTimeout *string `json:"health_startup_timeout,omitempty"`
// HealthStartupSuccess set the number of consecutive successes before the startup healthcheck is marked as successful
// and the normal healthcheck begins (0 indicates any success will start the regular healthcheck)
HealthStartupSuccess *uint `json:"health_startup_success,omitempty"`
}
func (u *UpdateHealthCheckConfig) IsStartupHealthCheckCommandSet(startupHealthCheck *StartupHealthCheck) bool {
containsStartupHealthCheckCmd := u.HealthStartupCmd != nil
containsFlags := (u.HealthStartupInterval != nil || u.HealthStartupRetries != nil ||
u.HealthStartupTimeout != nil || u.HealthStartupSuccess != nil)
return startupHealthCheck == nil && !containsStartupHealthCheckCmd && containsFlags
}
func (u *UpdateHealthCheckConfig) IsHealthCheckCommandSet(healthCheck *manifest.Schema2HealthConfig) bool {
containsStartupHealthCheckCmd := u.HealthCmd != nil
containsFlags := (u.HealthInterval != nil || u.HealthRetries != nil ||
u.HealthTimeout != nil || u.HealthStartPeriod != nil)
return healthCheck == nil && !containsStartupHealthCheckCmd && containsFlags
}
func (u *UpdateHealthCheckConfig) SetNewStartupHealthCheckConfigTo(healthCheckOptions *HealthCheckOptions) bool {
changed := false
if u.HealthStartupCmd != nil {
healthCheckOptions.Cmd = *u.HealthStartupCmd
changed = true
}
if u.HealthStartupInterval != nil {
healthCheckOptions.Interval = *u.HealthStartupInterval
changed = true
}
if u.HealthStartupRetries != nil {
healthCheckOptions.Retries = int(*u.HealthStartupRetries)
changed = true
}
if u.HealthStartupTimeout != nil {
healthCheckOptions.Timeout = *u.HealthStartupTimeout
changed = true
}
if u.HealthStartupSuccess != nil {
healthCheckOptions.Successes = int(*u.HealthStartupSuccess)
changed = true
}
healthCheckOptions.StartPeriod = "1s"
return changed
}
func (u *UpdateHealthCheckConfig) SetNewHealthCheckConfigTo(healthCheckOptions *HealthCheckOptions) bool {
changed := false
if u.HealthCmd != nil {
healthCheckOptions.Cmd = *u.HealthCmd
changed = true
}
if u.HealthInterval != nil {
healthCheckOptions.Interval = *u.HealthInterval
changed = true
}
if u.HealthRetries != nil {
healthCheckOptions.Retries = int(*u.HealthRetries)
changed = true
}
if u.HealthTimeout != nil {
healthCheckOptions.Timeout = *u.HealthTimeout
changed = true
}
if u.HealthStartPeriod != nil {
healthCheckOptions.StartPeriod = *u.HealthStartPeriod
changed = true
}
return changed
}
func GetValidHealthCheckDestination(destination string) (string, error) {
if destination == HealthCheckEventsLoggerDestination || destination == DefaultHealthCheckLocalDestination {
return destination, nil
}
fileInfo, err := os.Stat(destination)
if err != nil {
return "", fmt.Errorf("HealthCheck Log '%s' destination error: %w", destination, err)
}
mode := fileInfo.Mode()
if !mode.IsDir() {
return "", fmt.Errorf("HealthCheck Log '%s' destination must be directory", destination)
}
absPath, err := filepath.Abs(destination)
if err != nil {
return "", err
}
return absPath, nil
}
func (u *UpdateHealthCheckConfig) GetNewGlobalHealthCheck() (GlobalHealthCheckOptions, error) {
globalOptions := GlobalHealthCheckOptions{}
healthLogDestination := u.HealthLogDestination
if u.HealthLogDestination != nil {
dest, err := GetValidHealthCheckDestination(*u.HealthLogDestination)
if err != nil {
return GlobalHealthCheckOptions{}, err
}
healthLogDestination = &dest
}
globalOptions.HealthLogDestination = healthLogDestination
globalOptions.HealthMaxLogSize = u.HealthMaxLogSize
globalOptions.HealthMaxLogCount = u.HealthMaxLogCount
if u.HealthOnFailure != nil {
val, err := ParseHealthCheckOnFailureAction(*u.HealthOnFailure)
if err != nil {
return globalOptions, err
}
globalOptions.HealthCheckOnFailureAction = &val
}
return globalOptions, nil
}
type HealthCheckOptions struct {
Cmd string
Interval string
Retries int
Timeout string
StartPeriod string
Successes int
}
type GlobalHealthCheckOptions struct {
HealthLogDestination *string
HealthMaxLogCount *uint
HealthMaxLogSize *uint
HealthCheckOnFailureAction *HealthCheckOnFailureAction
}

View File

@ -258,18 +258,6 @@ func (c *Container) incrementStartupHCSuccessCounter(ctx context.Context) {
}
if recreateTimer {
logrus.Infof("Startup healthcheck for container %s passed, recreating timer", c.ID())
oldUnit := c.state.HCUnitName
// Create the new, standard healthcheck timer first.
if err := c.createTimer(c.HealthCheckConfig().Interval.String(), false); err != nil {
logrus.Errorf("Error recreating container %s healthcheck: %v", c.ID(), err)
return
}
if err := c.startTimer(false); err != nil {
logrus.Errorf("Error restarting container %s healthcheck timer: %v", c.ID(), err)
}
// This kills the process the healthcheck is running.
// Which happens to be us.
// So this has to be last - after this, systemd serves us a
@ -281,10 +269,31 @@ func (c *Container) incrementStartupHCSuccessCounter(ctx context.Context) {
// is the case here as we should not alter the exit code of another process that just
// happened to call this.
shutdown.SetExitCode(0)
if err := c.removeTransientFiles(ctx, true, oldUnit); err != nil {
logrus.Errorf("Error removing container %s healthcheck: %v", c.ID(), err)
return
}
c.recreateHealthCheckTimer(ctx, false, true)
}
}
func (c *Container) recreateHealthCheckTimer(ctx context.Context, isStartup bool, isStartupRemoved bool) {
logrus.Infof("Startup healthcheck for container %s passed, recreating timer", c.ID())
oldUnit := c.state.HCUnitName
// Create the new, standard healthcheck timer first.
interval := c.HealthCheckConfig().Interval.String()
if isStartup {
interval = c.config.StartupHealthCheckConfig.StartInterval.String()
}
if err := c.createTimer(interval, isStartup); err != nil {
logrus.Errorf("Error recreating container %s (isStartup: %t) healthcheck: %v", c.ID(), isStartup, err)
return
}
if err := c.startTimer(isStartup); err != nil {
logrus.Errorf("Error restarting container %s (isStartup: %t) healthcheck timer: %v", c.ID(), isStartup, err)
}
if err := c.removeTransientFiles(ctx, isStartupRemoved, oldUnit); err != nil {
logrus.Errorf("Error removing container %s healthcheck: %v", c.ID(), err)
return
}
}

View File

@ -0,0 +1,165 @@
//go:build !remote
package libpod
import (
"errors"
"strings"
"time"
"github.com/containers/image/v5/manifest"
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/pkg/specgenutil"
)
type HealthCheckConfig struct {
*manifest.Schema2HealthConfig
}
type StartupHealthCheckConfig struct {
*define.StartupHealthCheck
}
type IHealthCheckConfig interface {
SetTo(config *ContainerConfig)
IsStartup() bool
IsNil() bool
IsTimeChanged(oldInterval time.Duration) bool
GetInterval() time.Duration
SetCurrentConfigTo(healthCheckOptions *define.HealthCheckOptions)
IsHealthCheckCommandSet(updateHealthCheckConfig define.UpdateHealthCheckConfig) bool
SetNewHealthCheckOptions(updateHealthCheckConfig define.UpdateHealthCheckConfig, healthCheckOptions *define.HealthCheckOptions) bool
}
func (h *HealthCheckConfig) SetTo(config *ContainerConfig) {
config.HealthCheckConfig = h.Schema2HealthConfig
}
func (h *StartupHealthCheckConfig) SetTo(config *ContainerConfig) {
config.StartupHealthCheckConfig = h.StartupHealthCheck
}
func (h *HealthCheckConfig) IsNil() bool {
return h.Schema2HealthConfig == nil
}
func (h *StartupHealthCheckConfig) IsNil() bool {
return h.StartupHealthCheck == nil
}
func (h *HealthCheckConfig) IsStartup() bool {
return false
}
func (h *StartupHealthCheckConfig) IsStartup() bool {
return true
}
func (h *HealthCheckConfig) IsTimeChanged(oldInterval time.Duration) bool {
return h.Interval != oldInterval
}
func (h *StartupHealthCheckConfig) IsTimeChanged(oldInterval time.Duration) bool {
return h.Interval != oldInterval
}
func (h *HealthCheckConfig) GetInterval() time.Duration {
return h.Interval
}
func (h *StartupHealthCheckConfig) GetInterval() time.Duration {
return h.Interval
}
func (h *HealthCheckConfig) SetCurrentConfigTo(healthCheckOptions *define.HealthCheckOptions) {
healthCheckOptions.Cmd = strings.Join(h.Test, " ")
healthCheckOptions.Interval = h.Interval.String()
healthCheckOptions.Retries = h.Retries
healthCheckOptions.Timeout = h.Timeout.String()
healthCheckOptions.StartPeriod = h.StartPeriod.String()
}
func (h *StartupHealthCheckConfig) SetCurrentConfigTo(healthCheckOptions *define.HealthCheckOptions) {
healthCheckOptions.Cmd = strings.Join(h.Test, " ")
healthCheckOptions.Interval = h.Interval.String()
healthCheckOptions.Retries = h.Retries
healthCheckOptions.Timeout = h.Timeout.String()
healthCheckOptions.Successes = h.Successes
}
func (h *HealthCheckConfig) IsHealthCheckCommandSet(updateHealthCheckConfig define.UpdateHealthCheckConfig) bool {
return updateHealthCheckConfig.IsHealthCheckCommandSet(h.Schema2HealthConfig)
}
func (h *StartupHealthCheckConfig) IsHealthCheckCommandSet(updateHealthCheckConfig define.UpdateHealthCheckConfig) bool {
return updateHealthCheckConfig.IsStartupHealthCheckCommandSet(h.StartupHealthCheck)
}
func (h *HealthCheckConfig) SetNewHealthCheckOptions(updateHealthCheckConfig define.UpdateHealthCheckConfig, healthCheckOptions *define.HealthCheckOptions) bool {
return updateHealthCheckConfig.SetNewHealthCheckConfigTo(healthCheckOptions)
}
func (h *StartupHealthCheckConfig) SetNewHealthCheckOptions(updateHealthCheckConfig define.UpdateHealthCheckConfig, healthCheckOptions *define.HealthCheckOptions) bool {
return updateHealthCheckConfig.SetNewStartupHealthCheckConfigTo(healthCheckOptions)
}
func GetNewHealthCheckConfig(originalHealthCheckConfig IHealthCheckConfig, updateHealthCheckConfig define.UpdateHealthCheckConfig) (IHealthCheckConfig, bool, error) {
if originalHealthCheckConfig.IsHealthCheckCommandSet(updateHealthCheckConfig) {
return nil, false, errors.New("startup healthcheck command is not set")
}
healthCheckOptions := define.HealthCheckOptions{
Cmd: "",
Interval: define.DefaultHealthCheckInterval,
Retries: int(define.DefaultHealthCheckRetries),
Timeout: define.DefaultHealthCheckTimeout,
StartPeriod: define.DefaultHealthCheckStartPeriod,
Successes: 0,
}
if originalHealthCheckConfig.IsStartup() {
healthCheckOptions.Retries = 0
}
if !originalHealthCheckConfig.IsNil() {
originalHealthCheckConfig.SetCurrentConfigTo(&healthCheckOptions)
}
noHealthCheck := false
if updateHealthCheckConfig.NoHealthCheck != nil {
noHealthCheck = *updateHealthCheckConfig.NoHealthCheck
}
changed := originalHealthCheckConfig.SetNewHealthCheckOptions(updateHealthCheckConfig, &healthCheckOptions)
if noHealthCheck && changed {
return nil, false, errors.New("cannot specify both --no-healthcheck and other HealthCheck flags")
}
if noHealthCheck {
if originalHealthCheckConfig.IsStartup() {
return &StartupHealthCheckConfig{StartupHealthCheck: nil}, true, nil
}
return &HealthCheckConfig{Schema2HealthConfig: &manifest.Schema2HealthConfig{Test: []string{"NONE"}}}, true, nil
}
newHealthCheckConfig, err := specgenutil.MakeHealthCheckFromCli(
healthCheckOptions.Cmd,
healthCheckOptions.Interval,
uint(healthCheckOptions.Retries),
healthCheckOptions.Timeout,
healthCheckOptions.StartPeriod,
true,
)
if err != nil {
return nil, false, err
}
if originalHealthCheckConfig.IsStartup() {
newStartupHealthCheckConfig := new(define.StartupHealthCheck)
newStartupHealthCheckConfig.Schema2HealthConfig = *newHealthCheckConfig
newStartupHealthCheckConfig.Successes = healthCheckOptions.Successes
return &StartupHealthCheckConfig{StartupHealthCheck: newStartupHealthCheckConfig}, changed, nil
}
return &HealthCheckConfig{Schema2HealthConfig: newHealthCheckConfig}, changed, nil
}

View File

@ -6,8 +6,6 @@ import (
"errors"
"fmt"
"net"
"os"
"path/filepath"
"strings"
"syscall"
"time"
@ -1521,25 +1519,11 @@ func WithHealthCheckLogDestination(destination string) CtrCreateOption {
if ctr.valid {
return define.ErrCtrFinalized
}
switch destination {
case define.HealthCheckEventsLoggerDestination, define.DefaultHealthCheckLocalDestination:
ctr.config.HealthLogDestination = destination
default:
fileInfo, err := os.Stat(destination)
if err != nil {
return fmt.Errorf("HealthCheck Log '%s' destination error: %w", destination, err)
}
mode := fileInfo.Mode()
if !mode.IsDir() {
return fmt.Errorf("HealthCheck Log '%s' destination must be directory", destination)
}
absPath, err := filepath.Abs(destination)
if err != nil {
return err
}
ctr.config.HealthLogDestination = absPath
dest, err := define.GetValidHealthCheckDestination(destination)
if err != nil {
return err
}
ctr.config.HealthLogDestination = dest
return nil
}
}

View File

@ -786,7 +786,7 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) {
restartRetries = &localRetries
}
if err := ctr.Update(resources, restartPolicy, restartRetries); err != nil {
if err := ctr.Update(resources, restartPolicy, restartRetries, &define.UpdateHealthCheckConfig{}); err != nil {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("updating container: %w", err))
return
}

View File

@ -20,7 +20,6 @@ import (
"github.com/containers/podman/v5/pkg/domain/infra/abi"
"github.com/containers/podman/v5/pkg/util"
"github.com/gorilla/schema"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/sirupsen/logrus"
)
@ -443,12 +442,12 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) {
return
}
options := &handlers.UpdateEntities{Resources: &specs.LinuxResources{}}
if err := json.NewDecoder(r.Body).Decode(&options.Resources); err != nil {
options := &handlers.UpdateEntities{}
if err := json.NewDecoder(r.Body).Decode(&options); err != nil {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decode(): %w", err))
return
}
err = ctr.Update(options.Resources, restartPolicy, restartRetries)
err = ctr.Update(&options.LinuxResources, restartPolicy, restartRetries, &options.UpdateHealthCheckConfig)
if err != nil {
utils.InternalServerError(w, err)
return

View File

@ -54,4 +54,6 @@ type networkUpdateRequestLibpod entities.NetworkUpdateOptions
// Container update
// swagger:model
type containerUpdateRequest container.UpdateConfig
type containerUpdateRequest struct {
container.UpdateConfig
}

View File

@ -1,6 +1,7 @@
package handlers
import (
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/pkg/domain/entities"
docker "github.com/docker/docker/api/types"
dockerBackend "github.com/docker/docker/api/types/backend"
@ -73,7 +74,8 @@ type LibpodContainersRmReport struct {
// UpdateEntities used to wrap the oci resource spec in a swagger model
// swagger:model
type UpdateEntities struct {
Resources *specs.LinuxResources
specs.LinuxResources
define.UpdateHealthCheckConfig
}
type Info struct {

View File

@ -1778,8 +1778,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// ---
// tags:
// - containers
// summary: Update an existing containers cgroup configuration
// description: Update an existing containers cgroup configuration.
// summary: Updates the configuration of an existing container, allowing changes to resource limits and healthchecks
// description: Updates the configuration of an existing container, allowing changes to resource limits and healthchecks.
// parameters:
// - in: path
// name: name

View File

@ -7,6 +7,7 @@ import (
"strconv"
"strings"
"github.com/containers/podman/v5/pkg/api/handlers"
"github.com/containers/podman/v5/pkg/bindings"
"github.com/containers/podman/v5/pkg/domain/entities/types"
jsoniter "github.com/json-iterator/go"
@ -25,12 +26,15 @@ func Update(ctx context.Context, options *types.ContainerUpdateOptions) (string,
params.Set("restartRetries", strconv.Itoa(int(*options.Specgen.RestartRetries)))
}
}
resources, err := jsoniter.MarshalToString(options.Specgen.ResourceLimits)
updateEntities := &handlers.UpdateEntities{
LinuxResources: *options.Specgen.ResourceLimits,
UpdateHealthCheckConfig: *options.ChangedHealthCheckConfiguration,
}
requestData, err := jsoniter.MarshalToString(updateEntities)
if err != nil {
return "", err
}
stringReader := strings.NewReader(resources)
stringReader := strings.NewReader(requestData)
response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/containers/%s/update", params, nil, options.NameOrID)
if err != nil {
return "", err

View File

@ -36,6 +36,7 @@ type ContainerStatsReport struct {
}
type ContainerUpdateOptions struct {
NameOrID string
Specgen *specgen.SpecGenerator
NameOrID string
Specgen *specgen.SpecGenerator
ChangedHealthCheckConfiguration *define.UpdateHealthCheckConfig
}

View File

@ -1806,13 +1806,14 @@ func (ic *ContainerEngine) ContainerUpdate(ctx context.Context, updateOptions *e
if len(containers) != 1 {
return "", fmt.Errorf("container not found")
}
container := containers[0].Container
var restartPolicy *string
if updateOptions.Specgen.RestartPolicy != "" {
restartPolicy = &updateOptions.Specgen.RestartPolicy
}
if err = containers[0].Update(updateOptions.Specgen.ResourceLimits, restartPolicy, updateOptions.Specgen.RestartRetries); err != nil {
if err = container.Update(updateOptions.Specgen.ResourceLimits, restartPolicy, updateOptions.Specgen.RestartRetries, updateOptions.ChangedHealthCheckConfiguration); err != nil {
return "", err
}
return containers[0].ID(), nil

View File

@ -354,7 +354,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
if c.NoHealthCheck {
return errors.New("cannot specify both --no-healthcheck and --health-cmd")
}
s.HealthConfig, err = makeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod, false)
s.HealthConfig, err = MakeHealthCheckFromCli(c.HealthCmd, c.HealthInterval, c.HealthRetries, c.HealthTimeout, c.HealthStartPeriod, false)
if err != nil {
return err
}
@ -383,7 +383,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
// The hardcoded "1s" will be discarded, as the startup
// healthcheck does not have a period. So just hardcode
// something that parses correctly.
tmpHcConfig, err := makeHealthCheckFromCli(c.StartupHCCmd, c.StartupHCInterval, c.StartupHCRetries, c.StartupHCTimeout, "1s", true)
tmpHcConfig, err := MakeHealthCheckFromCli(c.StartupHCCmd, c.StartupHCInterval, c.StartupHCRetries, c.StartupHCTimeout, "1s", true)
if err != nil {
return err
}
@ -948,7 +948,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
return nil
}
func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string, isStartup bool) (*manifest.Schema2HealthConfig, error) {
func MakeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, startPeriod string, isStartup bool) (*manifest.Schema2HealthConfig, error) {
cmdArr := []string{}
isArr := true
err := json.Unmarshal([]byte(inCmd), &cmdArr) // array unmarshalling
@ -1017,7 +1017,6 @@ func makeHealthCheckFromCli(inCmd, interval string, retries uint, timeout, start
return nil, errors.New("healthcheck-start-period must be 0 seconds or greater")
}
hc.StartPeriod = startPeriodDuration
return &hc, nil
}

View File

@ -161,4 +161,152 @@ device-write-iops = /dev/zero:4000 | - | -
run_podman rm -f -t0 testctr
}
# HealthCheck configuration
function nrand() {
# 1-59 seconds. Don't exceed 59, because podman then shows as "1mXXs"
echo $((1 + RANDOM % 58))
}
# bats test_tags=ci:parallel
@test "podman update - test all HealthCheck flags" {
local ctrname="c-h-$(safename)"
local msg="healthmsg-$(random_string)"
local TMP_DIR_HEALTHCHECK="$PODMAN_TMPDIR/healthcheck"
mkdir $TMP_DIR_HEALTHCHECK
# flag-name | value | inspect format, .Config.Xxx
tests="
cmd | echo $msg | Healthcheck.Test
interval | $(nrand)s | Healthcheck.Interval
log-destination | $TMP_DIR_HEALTHCHECK | HealthLogDestination
max-log-count | $(nrand) | HealthMaxLogCount
max-log-size | $(nrand) | HealthMaxLogSize
on-failure | restart | HealthcheckOnFailureAction
retries | $(nrand) | Healthcheck.Retries
timeout | $(nrand)s | Healthcheck.Timeout
start-period | $(nrand)s | Healthcheck.StartPeriod
startup-cmd | echo $msg | StartupHealthCheck.Test
startup-interval | $(nrand)s | StartupHealthCheck.Interval
startup-retries | $(nrand) | StartupHealthCheck.Retries
startup-success | $(nrand) | StartupHealthCheck.Successes
startup-timeout | $(nrand)s | StartupHealthCheck.Timeout
"
run_podman run -d --name $ctrname $IMAGE top
cid="$output"
# Pass 1: read the table above, gather up the options, format and expected values
local -a opts
local -A formats
local -A checks
while read opt value format ; do
fullopt="--health-$opt=$value"
opts+=("$fullopt")
formats["$fullopt"]="{{.Config.$format}}"
expected=$value
# Special case for commands
if [[ $opt =~ cmd ]]; then
expected="[CMD-SHELL $value]"
fi
checks["$fullopt"]=$expected
done < <(parse_table "$tests")
# Now do the update in one fell swoop
run_podman update "${opts[@]}" $ctrname
# ...and check one by one
defer-assertion-failures
for opt in "${opts[@]}"; do
run_podman inspect $ctrname --format "${formats[$opt]}"
assert "$output" == "${checks[$opt]}" "$opt"
done
immediate-assertion-failures
# Clean up
run_podman rm -f -t0 $cid
}
# bats test_tags=ci:parallel
@test "podman update - test HealthCheck flags without HealthCheck commands" {
local ctrname="c-h-$(safename)"
# flag-name=value
tests="
interval=10s
retries=5
timeout=10s
start-period=10s
startup-interval=10s
startup-retries=5
startup-success=10
startup-timeout=10s
"
run_podman run -d --name $ctrname $IMAGE top
cid="$output"
defer-assertion-failures
for opt in $tests; do
run_podman 125 update "--health-$opt" $ctrname
assert "$output" =~ "healthcheck command is not set" "--$opt with no startup"
done
immediate-assertion-failures
run_podman rm -f -t0 $cid
}
# bats test_tags=ci:parallel
@test "podman update - --no-healthcheck" {
local msg="healthmsg-$(random_string)"
local ctrname="c-h-$(safename)"
run_podman run -d --name $ctrname \
--health-cmd "echo $msg" \
--health-startup-cmd "echo startup$msg" \
$IMAGE /home/podman/pause
cid="$output"
run_podman update $ctrname --no-healthcheck
run_podman inspect $ctrname --format {{.Config.Healthcheck.Test}}
assert "$output" == "[NONE]" "HealthCheck command is disabled"
run_podman inspect $ctrname --format {{.Config.StartupHealthCheck}}
assert "$output" == "<nil>" "startup HealthCheck command is disabled"
run_podman rm -t 0 -f $ctrname
}
# bats test_tags=ci:parallel
@test "podman update - check behavior - change cmd and destination healthcheck" {
local TMP_DIR_HEALTHCHECK="$PODMAN_TMPDIR/healthcheck"
mkdir $TMP_DIR_HEALTHCHECK
local ctrname="c-h-$(safename)"
local msg="healthmsg-$(random_string)"
run_podman run -d --name $ctrname \
--health-cmd "echo $msg" \
$IMAGE /home/podman/pause
cid="$output"
run_podman healthcheck run $ctrname
is "$output" "" "output from 'podman healthcheck run'"
# Run podman update in two separate runs to make sure HealthCheck is overwritten correctly.
run_podman update $ctrname --health-cmd "echo healthmsg-new"
run_podman update $ctrname --health-log-destination $TMP_DIR_HEALTHCHECK
run_podman healthcheck run $ctrname
is "$output" "" "output from 'podman healthcheck run'"
healthcheck_log_path="${TMP_DIR_HEALTHCHECK}/${cid}-healthcheck.log"
# The healthcheck is triggered by the podman when the container is started, but its execution depends on systemd.
# And since `run_podman healthcheck run` is also run manually, it will result in two runs.
count=$(grep -co "healthmsg-new" $healthcheck_log_path)
assert "$count" -ge 1 "Number of matching health log messages"
run_podman rm -t 0 -f $ctrname
}
# vim: filetype=sh