mirror of https://github.com/containers/podman.git
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:
parent
77e67e7a54
commit
a1249425bd
|
@ -168,78 +168,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
|
||||||
)
|
)
|
||||||
_ = cmd.RegisterFlagCompletionFunc(groupAddFlagName, completion.AutocompleteNone)
|
_ = 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(
|
createFlags.BoolVar(
|
||||||
&cf.HTTPProxy,
|
&cf.HTTPProxy,
|
||||||
"http-proxy", podmanConfig.ContainersConfDefaultsRO.Containers.HTTPProxy,
|
"http-proxy", podmanConfig.ContainersConfDefaultsRO.Containers.HTTPProxy,
|
||||||
|
@ -311,11 +239,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
|
||||||
)
|
)
|
||||||
_ = cmd.RegisterFlagCompletionFunc(logOptFlagName, AutocompleteLogOpt)
|
_ = cmd.RegisterFlagCompletionFunc(logOptFlagName, AutocompleteLogOpt)
|
||||||
|
|
||||||
createFlags.BoolVar(
|
|
||||||
&cf.NoHealthCheck,
|
|
||||||
"no-healthcheck", false,
|
|
||||||
"Disable healthchecks on container",
|
|
||||||
)
|
|
||||||
createFlags.BoolVar(
|
createFlags.BoolVar(
|
||||||
&cf.OOMKillDisable,
|
&cf.OOMKillDisable,
|
||||||
"oom-kill-disable", false,
|
"oom-kill-disable", false,
|
||||||
|
@ -452,46 +375,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
|
||||||
)
|
)
|
||||||
_ = cmd.RegisterFlagCompletionFunc(secretFlagName, AutocompleteSecrets)
|
_ = 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"
|
stopSignalFlagName := "stop-signal"
|
||||||
createFlags.StringVar(
|
createFlags.StringVar(
|
||||||
&cf.StopSignal,
|
&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 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
|
// Restart is allowed for created, updated, and infra ctr
|
||||||
if mode == entities.InfraMode || mode == entities.CreateMode || mode == entities.UpdateMode {
|
if mode == entities.InfraMode || mode == entities.CreateMode || mode == entities.UpdateMode {
|
||||||
restartFlagName := "restart"
|
restartFlagName := "restart"
|
||||||
|
|
|
@ -17,7 +17,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
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{
|
updateCommand = &cobra.Command{
|
||||||
Use: "update [options] CONTAINER",
|
Use: "update [options] CONTAINER",
|
||||||
|
@ -61,6 +61,58 @@ func init() {
|
||||||
updateFlags(containerUpdateCommand)
|
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 {
|
func update(cmd *cobra.Command, args []string) error {
|
||||||
var err error
|
var err error
|
||||||
// use a specgen since this is the easiest way to hold resource info
|
// 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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
healthCheckConfig := GetChangedHealthCheckConfiguration(cmd, &updateOpts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
opts := &entities.ContainerUpdateOptions{
|
opts := &entities.ContainerUpdateOptions{
|
||||||
NameOrID: strings.TrimPrefix(args[0], "/"),
|
NameOrID: strings.TrimPrefix(args[0], "/"),
|
||||||
Specgen: s,
|
Specgen: s,
|
||||||
|
ChangedHealthCheckConfiguration: &healthCheckConfig,
|
||||||
}
|
}
|
||||||
rep, err := registry.ContainerEngine().ContainerUpdate(context.Background(), opts)
|
rep, err := registry.ContainerEngine().ContainerUpdate(context.Background(), opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-cmd**=*"command"* | *'["command", "arg1", ...]'*
|
#### **--health-cmd**=*"command"* | *'["command", "arg1", ...]'*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-interval**=*interval*
|
#### **--health-interval**=*interval*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-log-destination**=*directory_path*
|
#### **--health-log-destination**=*directory_path*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-max-log-count**=*number of stored logs*
|
#### **--health-max-log-count**=*number of stored logs*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-max-log-size**=*size of stored logs*
|
#### **--health-max-log-size**=*size of stored logs*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-on-failure**=*action*
|
#### **--health-on-failure**=*action*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-retries**=*retries*
|
#### **--health-retries**=*retries*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-start-period**=*period*
|
#### **--health-start-period**=*period*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-startup-cmd**=*"command"* | *'["command", "arg1", ...]'*
|
#### **--health-startup-cmd**=*"command"* | *'["command", "arg1", ...]'*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-startup-interval**=*interval*
|
#### **--health-startup-interval**=*interval*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-startup-retries**=*retries*
|
#### **--health-startup-retries**=*retries*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-startup-success**=*retries*
|
#### **--health-startup-success**=*retries*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-startup-timeout**=*timeout*
|
#### **--health-startup-timeout**=*timeout*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--health-timeout**=*timeout*
|
#### **--health-timeout**=*timeout*
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
####> This option file is used in:
|
####> This option file is used in:
|
||||||
####> podman create, run
|
####> podman create, run, update
|
||||||
####> If file is edited, make sure the changes
|
####> If file is edited, make sure the changes
|
||||||
####> are applicable to all of those.
|
####> are applicable to all of those.
|
||||||
#### **--no-healthcheck**
|
#### **--no-healthcheck**
|
||||||
|
|
|
@ -10,8 +10,7 @@ podman\-update - Update the configuration of a given container
|
||||||
|
|
||||||
## DESCRIPTION
|
## DESCRIPTION
|
||||||
|
|
||||||
Updates the configuration of an already existing container, allowing different resource limits to be set.
|
Updates the configuration of an existing container, allowing changes to resource limits and healthchecks.
|
||||||
The currently supported options are a subset of the podman create/run resource limit options.
|
|
||||||
|
|
||||||
## OPTIONS
|
## OPTIONS
|
||||||
|
|
||||||
|
@ -43,6 +42,40 @@ The currently supported options are a subset of the podman create/run resource l
|
||||||
|
|
||||||
@@option device-write-iops
|
@@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
|
||||||
|
|
||||||
@@option memory-reservation
|
@@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 memory-swappiness
|
||||||
|
|
||||||
|
@@option no-healthcheck
|
||||||
|
|
||||||
@@option pids-limit
|
@@option pids-limit
|
||||||
|
|
||||||
@@option restart
|
@@option restart
|
||||||
|
|
|
@ -116,10 +116,11 @@ func (c *Container) Start(ctx context.Context, recursive bool) (finalErr error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update updates the given container.
|
// Update updates the given container.
|
||||||
// Either resource limits or restart policy can be updated.
|
// Either resource limits, restart policies, or HealthCheck configuration can be updated.
|
||||||
// Either resources or restartPolicy must not be nil.
|
// Either resources, restartPolicy or changedHealthCheckConfiguration must not be nil.
|
||||||
// If restartRetries is not nil, restartPolicy must be set and must be "on-failure".
|
// 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 {
|
if !c.batched {
|
||||||
c.lock.Lock()
|
c.lock.Lock()
|
||||||
defer c.lock.Unlock()
|
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)
|
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)
|
return c.update(resources, restartPolicy, restartRetries)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -2733,8 +2733,123 @@ func (c *Container) update(resources *spec.LinuxResources, restartPolicy *string
|
||||||
}
|
}
|
||||||
|
|
||||||
logrus.Debugf("updated container %s", c.ID())
|
logrus.Debugf("updated container %s", c.ID())
|
||||||
|
return nil
|
||||||
c.newContainerEvent(events.Update)
|
}
|
||||||
|
|
||||||
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,6 +2,8 @@ package define
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/containers/image/v5/manifest"
|
"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.
|
// If set to 0, a single success will mark the HC as passed.
|
||||||
Successes int `json:",omitempty"`
|
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
|
||||||
|
}
|
||||||
|
|
|
@ -258,18 +258,6 @@ func (c *Container) incrementStartupHCSuccessCounter(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if recreateTimer {
|
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.
|
// This kills the process the healthcheck is running.
|
||||||
// Which happens to be us.
|
// Which happens to be us.
|
||||||
// So this has to be last - after this, systemd serves us a
|
// 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
|
// is the case here as we should not alter the exit code of another process that just
|
||||||
// happened to call this.
|
// happened to call this.
|
||||||
shutdown.SetExitCode(0)
|
shutdown.SetExitCode(0)
|
||||||
if err := c.removeTransientFiles(ctx, true, oldUnit); err != nil {
|
c.recreateHealthCheckTimer(ctx, false, true)
|
||||||
logrus.Errorf("Error removing container %s healthcheck: %v", c.ID(), err)
|
}
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -6,8 +6,6 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"time"
|
"time"
|
||||||
|
@ -1521,25 +1519,11 @@ func WithHealthCheckLogDestination(destination string) CtrCreateOption {
|
||||||
if ctr.valid {
|
if ctr.valid {
|
||||||
return define.ErrCtrFinalized
|
return define.ErrCtrFinalized
|
||||||
}
|
}
|
||||||
switch destination {
|
dest, err := define.GetValidHealthCheckDestination(destination)
|
||||||
case define.HealthCheckEventsLoggerDestination, define.DefaultHealthCheckLocalDestination:
|
if err != nil {
|
||||||
ctr.config.HealthLogDestination = destination
|
return err
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
ctr.config.HealthLogDestination = dest
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -786,7 +786,7 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) {
|
||||||
restartRetries = &localRetries
|
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))
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("updating container: %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,7 +20,6 @@ import (
|
||||||
"github.com/containers/podman/v5/pkg/domain/infra/abi"
|
"github.com/containers/podman/v5/pkg/domain/infra/abi"
|
||||||
"github.com/containers/podman/v5/pkg/util"
|
"github.com/containers/podman/v5/pkg/util"
|
||||||
"github.com/gorilla/schema"
|
"github.com/gorilla/schema"
|
||||||
"github.com/opencontainers/runtime-spec/specs-go"
|
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -443,12 +442,12 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
options := &handlers.UpdateEntities{Resources: &specs.LinuxResources{}}
|
options := &handlers.UpdateEntities{}
|
||||||
if err := json.NewDecoder(r.Body).Decode(&options.Resources); err != nil {
|
if err := json.NewDecoder(r.Body).Decode(&options); err != nil {
|
||||||
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decode(): %w", err))
|
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decode(): %w", err))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err = ctr.Update(options.Resources, restartPolicy, restartRetries)
|
err = ctr.Update(&options.LinuxResources, restartPolicy, restartRetries, &options.UpdateHealthCheckConfig)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
utils.InternalServerError(w, err)
|
utils.InternalServerError(w, err)
|
||||||
return
|
return
|
||||||
|
|
|
@ -54,4 +54,6 @@ type networkUpdateRequestLibpod entities.NetworkUpdateOptions
|
||||||
|
|
||||||
// Container update
|
// Container update
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type containerUpdateRequest container.UpdateConfig
|
type containerUpdateRequest struct {
|
||||||
|
container.UpdateConfig
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package handlers
|
package handlers
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/containers/podman/v5/libpod/define"
|
||||||
"github.com/containers/podman/v5/pkg/domain/entities"
|
"github.com/containers/podman/v5/pkg/domain/entities"
|
||||||
docker "github.com/docker/docker/api/types"
|
docker "github.com/docker/docker/api/types"
|
||||||
dockerBackend "github.com/docker/docker/api/types/backend"
|
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
|
// UpdateEntities used to wrap the oci resource spec in a swagger model
|
||||||
// swagger:model
|
// swagger:model
|
||||||
type UpdateEntities struct {
|
type UpdateEntities struct {
|
||||||
Resources *specs.LinuxResources
|
specs.LinuxResources
|
||||||
|
define.UpdateHealthCheckConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type Info struct {
|
type Info struct {
|
||||||
|
|
|
@ -1778,8 +1778,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
|
||||||
// ---
|
// ---
|
||||||
// tags:
|
// tags:
|
||||||
// - containers
|
// - containers
|
||||||
// summary: Update an existing containers cgroup configuration
|
// summary: Updates the configuration of an existing container, allowing changes to resource limits and healthchecks
|
||||||
// description: Update an existing containers cgroup configuration.
|
// description: Updates the configuration of an existing container, allowing changes to resource limits and healthchecks.
|
||||||
// parameters:
|
// parameters:
|
||||||
// - in: path
|
// - in: path
|
||||||
// name: name
|
// name: name
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/containers/podman/v5/pkg/api/handlers"
|
||||||
"github.com/containers/podman/v5/pkg/bindings"
|
"github.com/containers/podman/v5/pkg/bindings"
|
||||||
"github.com/containers/podman/v5/pkg/domain/entities/types"
|
"github.com/containers/podman/v5/pkg/domain/entities/types"
|
||||||
jsoniter "github.com/json-iterator/go"
|
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)))
|
params.Set("restartRetries", strconv.Itoa(int(*options.Specgen.RestartRetries)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
updateEntities := &handlers.UpdateEntities{
|
||||||
resources, err := jsoniter.MarshalToString(options.Specgen.ResourceLimits)
|
LinuxResources: *options.Specgen.ResourceLimits,
|
||||||
|
UpdateHealthCheckConfig: *options.ChangedHealthCheckConfiguration,
|
||||||
|
}
|
||||||
|
requestData, err := jsoniter.MarshalToString(updateEntities)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
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)
|
response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/containers/%s/update", params, nil, options.NameOrID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
|
|
@ -36,6 +36,7 @@ type ContainerStatsReport struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ContainerUpdateOptions struct {
|
type ContainerUpdateOptions struct {
|
||||||
NameOrID string
|
NameOrID string
|
||||||
Specgen *specgen.SpecGenerator
|
Specgen *specgen.SpecGenerator
|
||||||
|
ChangedHealthCheckConfiguration *define.UpdateHealthCheckConfig
|
||||||
}
|
}
|
||||||
|
|
|
@ -1806,13 +1806,14 @@ func (ic *ContainerEngine) ContainerUpdate(ctx context.Context, updateOptions *e
|
||||||
if len(containers) != 1 {
|
if len(containers) != 1 {
|
||||||
return "", fmt.Errorf("container not found")
|
return "", fmt.Errorf("container not found")
|
||||||
}
|
}
|
||||||
|
container := containers[0].Container
|
||||||
|
|
||||||
var restartPolicy *string
|
var restartPolicy *string
|
||||||
if updateOptions.Specgen.RestartPolicy != "" {
|
if updateOptions.Specgen.RestartPolicy != "" {
|
||||||
restartPolicy = &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 "", err
|
||||||
}
|
}
|
||||||
return containers[0].ID(), nil
|
return containers[0].ID(), nil
|
||||||
|
|
|
@ -354,7 +354,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
|
||||||
if c.NoHealthCheck {
|
if c.NoHealthCheck {
|
||||||
return errors.New("cannot specify both --no-healthcheck and --health-cmd")
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -383,7 +383,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
|
||||||
// The hardcoded "1s" will be discarded, as the startup
|
// The hardcoded "1s" will be discarded, as the startup
|
||||||
// healthcheck does not have a period. So just hardcode
|
// healthcheck does not have a period. So just hardcode
|
||||||
// something that parses correctly.
|
// 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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -948,7 +948,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions
|
||||||
return nil
|
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{}
|
cmdArr := []string{}
|
||||||
isArr := true
|
isArr := true
|
||||||
err := json.Unmarshal([]byte(inCmd), &cmdArr) // array unmarshalling
|
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")
|
return nil, errors.New("healthcheck-start-period must be 0 seconds or greater")
|
||||||
}
|
}
|
||||||
hc.StartPeriod = startPeriodDuration
|
hc.StartPeriod = startPeriodDuration
|
||||||
|
|
||||||
return &hc, nil
|
return &hc, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -161,4 +161,152 @@ device-write-iops = /dev/zero:4000 | - | -
|
||||||
run_podman rm -f -t0 testctr
|
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
|
# vim: filetype=sh
|
||||||
|
|
Loading…
Reference in New Issue