This commit is contained in:
Aaron Ang 2025-06-18 08:35:54 -07:00 committed by GitHub
commit 3d9aa32f1e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 160 additions and 13 deletions

View File

@ -440,14 +440,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
) )
_ = cmd.RegisterFlagCompletionFunc(umaskFlagName, completion.AutocompleteNone) _ = cmd.RegisterFlagCompletionFunc(umaskFlagName, completion.AutocompleteNone)
ulimitFlagName := "ulimit"
createFlags.StringSliceVar(
&cf.Ulimit,
ulimitFlagName, cf.Ulimit,
"Ulimit options",
)
_ = cmd.RegisterFlagCompletionFunc(ulimitFlagName, completion.AutocompleteNone)
userFlagName := "user" userFlagName := "user"
createFlags.StringVarP( createFlags.StringVarP(
&cf.User, &cf.User,
@ -559,6 +551,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
) )
_ = cmd.RegisterFlagCompletionFunc(unsetenvFlagName, completion.AutocompleteNone) _ = cmd.RegisterFlagCompletionFunc(unsetenvFlagName, completion.AutocompleteNone)
ulimitFlagName := "ulimit"
createFlags.StringSliceVar(
&cf.Ulimit,
ulimitFlagName, cf.Ulimit,
"Ulimit options",
)
_ = cmd.RegisterFlagCompletionFunc(ulimitFlagName, completion.AutocompleteNone)
healthCmdFlagName := "health-cmd" healthCmdFlagName := "health-cmd"
createFlags.StringVar( createFlags.StringVar(
&cf.HealthCmd, &cf.HealthCmd,

View File

@ -179,6 +179,18 @@ func update(cmd *cobra.Command, args []string) error {
opts.UnsetEnv = env opts.UnsetEnv = env
} }
if cmd.Flags().Changed("ulimit") {
ulimits, err := cmd.Flags().GetStringSlice("ulimit")
if err != nil {
return err
}
rlimits, err := specgenutil.GenRlimits(ulimits)
if err != nil {
return err
}
opts.Rlimits = rlimits
}
rep, err := registry.ContainerEngine().ContainerUpdate(context.Background(), opts) rep, err := registry.ContainerEngine().ContainerUpdate(context.Background(), opts)
if err != nil { if err != nil {
return err return err

View File

@ -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.
#### **--ulimit**=*option* #### **--ulimit**=*option*

View File

@ -92,6 +92,8 @@ Changing this setting resets the timer, depending on the state of the container.
@@option restart @@option restart
@@option ulimit
@@option unsetenv.update @@option unsetenv.update
@ -102,6 +104,11 @@ Update a container with a new cpu quota and period:
podman update --cpus=0.5 ctrID podman update --cpus=0.5 ctrID
``` ```
Update a container's ulimit:
```
podman update --ulimit nofile=1024:1024 myCtr
```
Update a container with multiple options at ones: Update a container with multiple options at ones:
``` ```
podman update --cpus 5 --cpuset-cpus 0 --cpu-shares 123 --cpuset-mems 0 \\ podman update --cpus 5 --cpuset-cpus 0 --cpu-shares 123 --cpuset-mems 0 \\

View File

@ -2810,6 +2810,7 @@ func (c *Container) update(updateOptions *entities.ContainerUpdateOptions) error
} }
oldRestart := c.config.RestartPolicy oldRestart := c.config.RestartPolicy
oldRetries := c.config.RestartRetries oldRetries := c.config.RestartRetries
oldRlimits := c.config.Spec.Process.Rlimits
if updateOptions.RestartPolicy != nil { if updateOptions.RestartPolicy != nil {
if err := define.ValidateRestartPolicy(*updateOptions.RestartPolicy); err != nil { if err := define.ValidateRestartPolicy(*updateOptions.RestartPolicy); err != nil {
@ -2857,16 +2858,21 @@ func (c *Container) update(updateOptions *entities.ContainerUpdateOptions) error
c.config.Spec.Process.Env = envLib.Slice(envMap) c.config.Spec.Process.Env = envLib.Slice(envMap)
} }
if updateOptions.Rlimits != nil {
c.config.Spec.Process.Rlimits = updateOptions.Rlimits
}
if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil {
// Assume DB write failed, revert to old resources block // Assume DB write failed, revert to old resources block
c.config.Spec.Linux.Resources = oldResources c.config.Spec.Linux.Resources = oldResources
c.config.RestartPolicy = oldRestart c.config.RestartPolicy = oldRestart
c.config.RestartRetries = oldRetries c.config.RestartRetries = oldRetries
c.config.Spec.Process.Rlimits = oldRlimits
return err return err
} }
if c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStatePaused) && if c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStatePaused) &&
(updateOptions.Resources != nil || updateOptions.Env != nil || updateOptions.UnsetEnv != nil) { (updateOptions.Resources != nil || updateOptions.Env != nil || updateOptions.UnsetEnv != nil || updateOptions.Rlimits != nil) {
// So `podman inspect` on running containers sources its OCI spec from disk. // So `podman inspect` on running containers sources its OCI spec from disk.
// To keep inspect accurate we need to update the on-disk OCI spec. // To keep inspect accurate we need to update the on-disk OCI spec.
onDiskSpec, err := c.specFromState() onDiskSpec, err := c.specFromState()
@ -2882,6 +2888,9 @@ func (c *Container) update(updateOptions *entities.ContainerUpdateOptions) error
if len(updateOptions.Env) != 0 || len(updateOptions.UnsetEnv) != 0 { if len(updateOptions.Env) != 0 || len(updateOptions.UnsetEnv) != 0 {
onDiskSpec.Process.Env = c.config.Spec.Process.Env onDiskSpec.Process.Env = c.config.Spec.Process.Env
} }
if updateOptions.Rlimits != nil {
onDiskSpec.Process.Rlimits = updateOptions.Rlimits
}
if err := c.saveSpec(onDiskSpec); err != nil { if err := c.saveSpec(onDiskSpec); err != nil {
logrus.Errorf("Unable to update container %s OCI spec - `podman inspect` may not be accurate until container is restarted: %v", c.ID(), err) logrus.Errorf("Unable to update container %s OCI spec - `podman inspect` may not be accurate until container is restarted: %v", c.ID(), err)
} }

View File

@ -822,11 +822,25 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) {
restartRetries = &localRetries restartRetries = &localRetries
} }
// Rlimits
var rlimits []spec.POSIXRlimit
if len(options.Ulimits) > 0 {
for _, ulimit := range options.Ulimits {
rlimit := spec.POSIXRlimit{
Type: ulimit.Name,
Hard: uint64(ulimit.Hard),
Soft: uint64(ulimit.Soft),
}
rlimits = append(rlimits, rlimit)
}
}
updateOptions := &entities.ContainerUpdateOptions{ updateOptions := &entities.ContainerUpdateOptions{
Resources: resources, Resources: resources,
ChangedHealthCheckConfiguration: &define.UpdateHealthCheckConfig{}, ChangedHealthCheckConfiguration: &define.UpdateHealthCheckConfig{},
RestartPolicy: restartPolicy, RestartPolicy: restartPolicy,
RestartRetries: restartRetries, RestartRetries: restartRetries,
Rlimits: rlimits,
} }
if err := ctr.Update(updateOptions); err != nil { if err := ctr.Update(updateOptions); err != nil {

View File

@ -462,6 +462,7 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) {
RestartRetries: restartRetries, RestartRetries: restartRetries,
Env: options.Env, Env: options.Env,
UnsetEnv: options.UnsetEnv, UnsetEnv: options.UnsetEnv,
Rlimits: options.Rlimits,
} }
err = ctr.Update(updateOptions) err = ctr.Update(updateOptions)

View File

@ -81,6 +81,7 @@ type UpdateEntities struct {
define.UpdateContainerDevicesLimits define.UpdateContainerDevicesLimits
Env []string Env []string
UnsetEnv []string UnsetEnv []string
Rlimits []specs.POSIXRlimit
} }
type Info struct { type Info struct {

View File

@ -40,6 +40,9 @@ func Update(ctx context.Context, options *types.ContainerUpdateOptions) (string,
if options.DevicesLimits != nil { if options.DevicesLimits != nil {
updateEntities.UpdateContainerDevicesLimits = *options.DevicesLimits updateEntities.UpdateContainerDevicesLimits = *options.DevicesLimits
} }
if options.Rlimits != nil {
updateEntities.Rlimits = options.Rlimits
}
requestData, err := jsoniter.MarshalToString(updateEntities) requestData, err := jsoniter.MarshalToString(updateEntities)
if err != nil { if err != nil {

View File

@ -53,6 +53,7 @@ type ContainerUpdateOptions struct {
// - RestartRetries to change restart retries // - RestartRetries to change restart retries
// - Env to change the environment variables. // - Env to change the environment variables.
// - UntsetEnv to unset the environment variables. // - UntsetEnv to unset the environment variables.
// - Rlimits to change POSIX resource limits.
Specgen *specgen.SpecGenerator Specgen *specgen.SpecGenerator
Resources *specs.LinuxResources Resources *specs.LinuxResources
DevicesLimits *define.UpdateContainerDevicesLimits DevicesLimits *define.UpdateContainerDevicesLimits
@ -61,6 +62,7 @@ type ContainerUpdateOptions struct {
RestartRetries *uint RestartRetries *uint
Env []string Env []string
UnsetEnv []string UnsetEnv []string
Rlimits []specs.POSIXRlimit
} }
func (u *ContainerUpdateOptions) ProcessSpecgen() { func (u *ContainerUpdateOptions) ProcessSpecgen() {
@ -87,4 +89,8 @@ func (u *ContainerUpdateOptions) ProcessSpecgen() {
if u.RestartRetries == nil { if u.RestartRetries == nil {
u.RestartRetries = u.Specgen.RestartRetries u.RestartRetries = u.Specgen.RestartRetries
} }
if u.Rlimits == nil {
u.Rlimits = u.Specgen.Rlimits
}
} }

View File

@ -1811,15 +1811,15 @@ 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 ctr := containers[0]
updateOptions.Resources, err = specgenutil.UpdateMajorAndMinorNumbers(updateOptions.Resources, updateOptions.DevicesLimits) updateOptions.Resources, err = specgenutil.UpdateMajorAndMinorNumbers(updateOptions.Resources, updateOptions.DevicesLimits)
if err != nil { if err != nil {
return "", err return "", err
} }
if err = container.Update(updateOptions); err != nil { if err = ctr.Container.Update(updateOptions); err != nil {
return "", err return "", err
} }
return containers[0].ID(), nil return ctr.ID(), nil
} }

View File

@ -306,4 +306,48 @@ var _ = Describe("Podman update", func() {
Expect(env).ToNot(ContainSubstring("FOO")) Expect(env).ToNot(ContainSubstring("FOO"))
Expect(env).To(ContainSubstring("PATH=")) Expect(env).To(ContainSubstring("PATH="))
}) })
It("podman update sets ulimits", func() {
session := podmanTest.PodmanExitCleanly("run", "-dt", ALPINE)
ctrID := session.OutputToString()
// Get initial ulimit value
initialUlimit := podmanTest.PodmanExitCleanly("exec", ctrID, "sh", "-c", "ulimit -n")
Expect(initialUlimit.OutputToString()).ToNot(BeEmpty())
// Update with a single ulimit
podmanTest.PodmanExitCleanly("update", "--ulimit", "nofile=1024:1024", ctrID)
// Verify ulimit was updated
ulimit := podmanTest.PodmanExitCleanly("exec", ctrID, "sh", "-c", "ulimit -n")
Expect(ulimit.OutputToString()).To(Equal("1024"))
// Update with multiple ulimits
podmanTest.PodmanExitCleanly("update", "--ulimit", "nofile=2048:2048", "--ulimit", "nproc=512:512", ctrID)
// Verify nofile ulimit was updated
ulimit = podmanTest.PodmanExitCleanly("exec", ctrID, "sh", "-c", "ulimit -n")
Expect(ulimit.OutputToString()).To(Equal("2048"))
// Verify nproc ulimit was updated
ulimit = podmanTest.PodmanExitCleanly("exec", ctrID, "sh", "-c", "ulimit -u")
Expect(ulimit.OutputToString()).To(Equal("512"))
})
It("podman update with invalid ulimit fails", func() {
session := podmanTest.PodmanExitCleanly("run", "-dt", ALPINE)
ctrID := session.OutputToString()
// Try with invalid ulimit syntax (missing =)
update := podmanTest.Podman([]string{"update", "--ulimit", "nofile:1024:1024", ctrID})
update.WaitWithDefaultTimeout()
Expect(update).Should(Exit(125))
Expect(update.ErrorToString()).To(ContainSubstring("error"))
// Try with invalid ulimit value (soft > hard)
update = podmanTest.Podman([]string{"update", "--ulimit", "nofile=2048:1024", ctrID})
update.WaitWithDefaultTimeout()
Expect(update).Should(Exit(125))
Expect(update.ErrorToString()).To(ContainSubstring("error"))
})
}) })

View File

@ -349,4 +349,54 @@ function nrand() {
run_podman rm -t 0 -f "$cid" run_podman rm -t 0 -f "$cid"
} }
# bats test_tags=ci:parallel
@test "podman update - set ulimits" {
local ctrname="c-h-$(safename)"
run_podman run -d --name $ctrname $IMAGE sleep 600
# Get initial nofile ulimit
run_podman exec $ctrname sh -c "ulimit -n"
initial_nofile="$output"
# Update with single ulimit
run_podman update --ulimit nofile=1024:1024 $ctrname
# Verify the nofile ulimit was updated
run_podman exec $ctrname sh -c "ulimit -n"
assert "$output" == "1024" "nofile ulimit updated to 1024"
assert "$output" != "$initial_nofile" "nofile ulimit should have changed"
# Update with multiple ulimits
run_podman update --ulimit nofile=2048:2048 --ulimit nproc=512:512 $ctrname
# Verify the nofile ulimit was updated again
run_podman exec $ctrname sh -c "ulimit -n"
assert "$output" == "2048" "nofile ulimit updated to 2048"
# Verify the nproc ulimit was updated
run_podman exec $ctrname sh -c "ulimit -u"
assert "$output" == "512" "nproc ulimit updated to 512"
# Test persists across container restart
run_podman restart $ctrname
# Verify the ulimits persist after restart
run_podman exec $ctrname sh -c "ulimit -n"
assert "$output" == "2048" "nofile ulimit persists after restart"
run_podman exec $ctrname sh -c "ulimit -u"
assert "$output" == "512" "nproc ulimit persists after restart"
# Error cases
run_podman 125 update --ulimit nofile:1024:1024 $ctrname
assert "$output" =~ "error" "Invalid ulimit syntax should fail"
run_podman 125 update --ulimit nofile=2048:1024 $ctrname
assert "$output" =~ "error" "Invalid ulimit values should fail"
# Clean up
run_podman rm -t 0 -f $ctrname
}
# vim: filetype=sh # vim: filetype=sh