Add podman run --timeout option

This option allows users to specify the maximum amount of time to run
before conmon sends the kill signal to the container.

Fixes: https://github.com/containers/podman/issues/6412

Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
This commit is contained in:
Daniel J Walsh
2021-04-22 15:38:36 -04:00
parent ba60821f0a
commit 3538815c5b
17 changed files with 92 additions and 6 deletions

View File

@ -651,7 +651,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) {
createFlags.UintVar( createFlags.UintVar(
&cf.StopTimeout, &cf.StopTimeout,
stopTimeoutFlagName, containerConfig.Engine.StopTimeout, stopTimeoutFlagName, containerConfig.Engine.StopTimeout,
"Timeout (in seconds) to stop a container. Default is 10", "Timeout (in seconds) that containers stopped by user command have to exit. If exceeded, the container will be forcibly stopped via SIGKILL.",
) )
_ = cmd.RegisterFlagCompletionFunc(stopTimeoutFlagName, completion.AutocompleteNone) _ = cmd.RegisterFlagCompletionFunc(stopTimeoutFlagName, completion.AutocompleteNone)
@ -697,6 +697,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *ContainerCLIOpts) {
) )
_ = cmd.RegisterFlagCompletionFunc(systemdFlagName, AutocompleteSystemdFlag) _ = cmd.RegisterFlagCompletionFunc(systemdFlagName, AutocompleteSystemdFlag)
timeoutFlagName := "timeout"
createFlags.UintVar(
&cf.Timeout,
timeoutFlagName, 0,
"Maximum length of time a container is allowed to run. The container will be killed automatically after the time expires.",
)
_ = cmd.RegisterFlagCompletionFunc(timeoutFlagName, completion.AutocompleteNone)
tmpfsFlagName := "tmpfs" tmpfsFlagName := "tmpfs"
createFlags.StringArrayVar( createFlags.StringArrayVar(
&cf.TmpFS, &cf.TmpFS,

View File

@ -108,6 +108,7 @@ type ContainerCLIOpts struct {
SubGIDName string SubGIDName string
Sysctl []string Sysctl []string
Systemd string Systemd string
Timeout uint
TmpFS []string TmpFS []string
TTY bool TTY bool
Timezone string Timezone string

View File

@ -641,6 +641,7 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *ContainerCLIOpts, args []string
} }
s.Remove = c.Rm s.Remove = c.Rm
s.StopTimeout = &c.StopTimeout s.StopTimeout = &c.StopTimeout
s.Timeout = c.Timeout
s.Timezone = c.Timezone s.Timezone = c.Timezone
s.Umask = c.Umask s.Umask = c.Umask
s.Secrets = c.Secrets s.Secrets = c.Secrets

View File

@ -72,7 +72,8 @@ func stopFlags(cmd *cobra.Command) {
_ = flags.MarkHidden("cidfile") _ = flags.MarkHidden("cidfile")
_ = flags.MarkHidden("ignore") _ = flags.MarkHidden("ignore")
} }
flags.SetNormalizeFunc(utils.AliasFlags)
flags.SetNormalizeFunc(utils.TimeoutAliasFlags)
} }
func init() { func init() {

View File

@ -74,7 +74,7 @@ func init() {
flags.StringVar(&format, formatFlagName, "", "Print the created units in specified format (json)") flags.StringVar(&format, formatFlagName, "", "Print the created units in specified format (json)")
_ = systemdCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil)) _ = systemdCmd.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(nil))
flags.SetNormalizeFunc(utils.AliasFlags) flags.SetNormalizeFunc(utils.TimeoutAliasFlags)
} }
func systemd(cmd *cobra.Command, args []string) error { func systemd(cmd *cobra.Command, args []string) error {

View File

@ -17,8 +17,6 @@ func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
name = "health-timeout" name = "health-timeout"
case "net": case "net":
name = "network" name = "network"
case "timeout":
name = "time"
case "namespace": case "namespace":
name = "ns" name = "ns"
case "storage": case "storage":
@ -34,3 +32,12 @@ func AliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
} }
return pflag.NormalizedName(name) return pflag.NormalizedName(name)
} }
// TimeoutAliasFlags is a function to handle backwards compatibility with old timeout flags
func TimeoutAliasFlags(f *pflag.FlagSet, name string) pflag.NormalizedName {
switch name {
case "timeout":
name = "time"
}
return pflag.NormalizedName(name)
}

View File

@ -951,6 +951,12 @@ The `container_manage_cgroup` boolean must be enabled for this to be allowed on
`setsebool -P container_manage_cgroup true` `setsebool -P container_manage_cgroup true`
#### **\-\-timeout**=*seconds*
Maximimum time a container is allowed to run before conmon sends it the kill
signal. By default containers will run until they exit or are stopped by
`podman stop`.
#### **\-\-tmpfs**=*fs* #### **\-\-tmpfs**=*fs*
Create a tmpfs mount Create a tmpfs mount

View File

@ -1024,6 +1024,12 @@ The **container_manage_cgroup** boolean must be enabled for this to be allowed o
setsebool -P container_manage_cgroup true setsebool -P container_manage_cgroup true
``` ```
#### **\-\-timeout**=*seconds*
Maximimum time a container is allowed to run before conmon sends it the kill
signal. By default containers will run until they exit or are stopped by
`podman stop`.
#### **\-\-tmpfs**=*fs* #### **\-\-tmpfs**=*fs*
Create a tmpfs mount. Create a tmpfs mount.

View File

@ -298,6 +298,8 @@ type ContainerMiscConfig struct {
StopSignal uint `json:"stopSignal,omitempty"` StopSignal uint `json:"stopSignal,omitempty"`
// StopTimeout is the signal that will be used to stop the container // StopTimeout is the signal that will be used to stop the container
StopTimeout uint `json:"stopTimeout,omitempty"` StopTimeout uint `json:"stopTimeout,omitempty"`
// Timeout is maximimum time a container will run before getting the kill signal
Timeout uint `json:"timeout,omitempty"`
// Time container was created // Time container was created
CreatedTime time.Time `json:"createdTime"` CreatedTime time.Time `json:"createdTime"`
// CgroupManager is the cgroup manager used to create this container. // CgroupManager is the cgroup manager used to create this container.

View File

@ -304,6 +304,8 @@ func (c *Container) generateInspectContainerConfig(spec *spec.Spec) *define.Insp
ctrConfig.WorkingDir = spec.Process.Cwd ctrConfig.WorkingDir = spec.Process.Cwd
} }
ctrConfig.StopTimeout = c.config.StopTimeout
ctrConfig.Timeout = c.config.Timeout
ctrConfig.OpenStdin = c.config.Stdin ctrConfig.OpenStdin = c.config.Stdin
ctrConfig.Image = c.config.RootfsImageName ctrConfig.Image = c.config.RootfsImageName
ctrConfig.SystemdMode = c.config.Systemd ctrConfig.SystemdMode = c.config.Systemd

View File

@ -64,6 +64,10 @@ type InspectContainerConfig struct {
Umask string `json:"Umask,omitempty"` Umask string `json:"Umask,omitempty"`
// Secrets are the secrets mounted in the container // Secrets are the secrets mounted in the container
Secrets []*InspectSecret `json:"Secrets,omitempty"` Secrets []*InspectSecret `json:"Secrets,omitempty"`
// Timeout is time before container is killed by conmon
Timeout uint `json:"Timeout"`
// StopTimeout is time before container is stoped when calling stop
StopTimeout uint `json:"StopTimeout"`
} }
// InspectRestartPolicy holds information about the container's restart policy. // InspectRestartPolicy holds information about the container's restart policy.

View File

@ -1024,6 +1024,10 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
args = append(args, "-i") args = append(args, "-i")
} }
if ctr.config.Timeout > 0 {
args = append(args, fmt.Sprintf("--timeout=%d", ctr.config.Timeout))
}
if !r.enableKeyring { if !r.enableKeyring {
args = append(args, "--no-new-keyring") args = append(args, "--no-new-keyring")
} }

View File

@ -758,6 +758,19 @@ func WithStopTimeout(timeout uint) CtrCreateOption {
} }
} }
// WithTimeout sets the maximum time a container is allowed to run"
func WithTimeout(timeout uint) CtrCreateOption {
return func(ctr *Container) error {
if ctr.valid {
return define.ErrCtrFinalized
}
ctr.config.Timeout = timeout
return nil
}
}
// WithIDMappings sets the idmappings for the container // WithIDMappings sets the idmappings for the container
func WithIDMappings(idmappings storage.IDMappingOptions) CtrCreateOption { func WithIDMappings(idmappings storage.IDMappingOptions) CtrCreateOption {
return func(ctr *Container) error { return func(ctr *Container) error {

View File

@ -292,6 +292,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen.
if s.StopTimeout != nil { if s.StopTimeout != nil {
options = append(options, libpod.WithStopTimeout(*s.StopTimeout)) options = append(options, libpod.WithStopTimeout(*s.StopTimeout))
} }
if s.Timeout != 0 {
options = append(options, libpod.WithTimeout(s.Timeout))
}
if s.LogConfiguration != nil { if s.LogConfiguration != nil {
if len(s.LogConfiguration.Path) > 0 { if len(s.LogConfiguration.Path) > 0 {
options = append(options, libpod.WithLogPath(s.LogConfiguration.Path)) options = append(options, libpod.WithLogPath(s.LogConfiguration.Path))

View File

@ -83,6 +83,11 @@ type ContainerBasicConfig struct {
// instead. // instead.
// Optional. // Optional.
StopTimeout *uint `json:"stop_timeout,omitempty"` StopTimeout *uint `json:"stop_timeout,omitempty"`
// Timeout is a maximum time in seconds the container will run before
// main process is sent SIGKILL.
// If 0 is used, signal will not be sent. Container can run indefinitely
// Optional.
Timeout uint `json:"timeout,omitempty"`
// LogConfiguration describes the logging for a container including // LogConfiguration describes the logging for a container including
// driver, path, and options. // driver, path, and options.
// Optional // Optional

View File

@ -242,7 +242,7 @@ var _ = Describe("Podman generate systemd", func() {
n.WaitWithDefaultTimeout() n.WaitWithDefaultTimeout()
Expect(n.ExitCode()).To(Equal(0)) Expect(n.ExitCode()).To(Equal(0))
session := podmanTest.Podman([]string{"generate", "systemd", "--timeout", "42", "--name", "--new", "foo"}) session := podmanTest.Podman([]string{"generate", "systemd", "--time", "42", "--name", "--new", "foo"})
session.WaitWithDefaultTimeout() session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0)) Expect(session.ExitCode()).To(Equal(0))

View File

@ -668,4 +668,27 @@ json-file | f
is "$output" ".*HOME=/.*" is "$output" ".*HOME=/.*"
} }
@test "podman run --timeout - basic test" {
cid=timeouttest
t0=$SECONDS
run_podman 255 run --name $cid --timeout 10 $IMAGE sleep 60
t1=$SECONDS
# Confirm that container is stopped. Podman-remote unfortunately
# cannot tell the difference between "stopped" and "exited", and
# spits them out interchangeably, so we need to recognize either.
run_podman inspect --format '{{.State.Status}} {{.State.ExitCode}}' $cid
is "$output" "\\(stopped\|exited\\) \-1" \
"Status and exit code of stopped container"
# This operation should take
# exactly 10 seconds. Give it some leeway.
delta_t=$(( $t1 - $t0 ))
[ $delta_t -gt 8 ] ||\
die "podman stop: ran too quickly! ($delta_t seconds; expected >= 10)"
[ $delta_t -le 14 ] ||\
die "podman stop: took too long ($delta_t seconds; expected ~10)"
run_podman rm $cid
}
# vim: filetype=sh # vim: filetype=sh