Skip to content

Commit beda41d

Browse files
committed
Add ulimits to podman update
1 parent c8272b2 commit beda41d

File tree

15 files changed

+297
-14
lines changed

15 files changed

+297
-14
lines changed

cmd/podman/common/create.go

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -440,14 +440,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
440440
)
441441
_ = cmd.RegisterFlagCompletionFunc(umaskFlagName, completion.AutocompleteNone)
442442

443-
ulimitFlagName := "ulimit"
444-
createFlags.StringSliceVar(
445-
&cf.Ulimit,
446-
ulimitFlagName, cf.Ulimit,
447-
"Ulimit options",
448-
)
449-
_ = cmd.RegisterFlagCompletionFunc(ulimitFlagName, completion.AutocompleteNone)
450-
451443
userFlagName := "user"
452444
createFlags.StringVarP(
453445
&cf.User,
@@ -559,6 +551,14 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions,
559551
)
560552
_ = cmd.RegisterFlagCompletionFunc(unsetenvFlagName, completion.AutocompleteNone)
561553

554+
ulimitFlagName := "ulimit"
555+
createFlags.StringSliceVar(
556+
&cf.Ulimit,
557+
ulimitFlagName, cf.Ulimit,
558+
"Ulimit options",
559+
)
560+
_ = cmd.RegisterFlagCompletionFunc(ulimitFlagName, completion.AutocompleteNone)
561+
562562
healthCmdFlagName := "health-cmd"
563563
createFlags.StringVar(
564564
&cf.HealthCmd,

cmd/podman/containers/update.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,18 @@ func update(cmd *cobra.Command, args []string) error {
190190
opts.UnsetEnv = env
191191
}
192192

193+
if cmd.Flags().Changed("ulimit") {
194+
ulimits, err := cmd.Flags().GetStringSlice("ulimit")
195+
if err != nil {
196+
return err
197+
}
198+
rlimits, err := specgenutil.GenRlimits(ulimits)
199+
if err != nil {
200+
return err
201+
}
202+
opts.Rlimits = rlimits
203+
}
204+
193205
rep, err := registry.ContainerEngine().ContainerUpdate(context.Background(), opts)
194206
if err != nil {
195207
return err

docs/source/markdown/options/ulimit.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
####> This option file is used in:
2-
####> podman create, run
2+
####> podman create, run, update
33
####> If file is edited, make sure the changes
44
####> are applicable to all of those.
55
#### **--ulimit**=*option*

docs/source/markdown/podman-update.1.md.in

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@ Changing this setting resets the timer, depending on the state of the container.
9494

9595
@@option restart
9696

97+
@@option ulimit
98+
99+
Note: This limit only takes effect on the next container restart.
100+
97101
@@option unsetenv.update
98102

99103

@@ -109,6 +113,11 @@ Update the latest container with a new cpu value:
109113
podman update --latest --cpus=1
110114
```
111115

116+
Update a container's ulimit:
117+
```
118+
podman update --ulimit nofile=1024:1024 ctrID
119+
```
120+
112121
Update a container with multiple options at ones:
113122
```
114123
podman update --cpus 5 --cpuset-cpus 0 --cpu-shares 123 --cpuset-mems 0 \\

libpod/container_internal.go

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2810,6 +2810,7 @@ func (c *Container) update(updateOptions *entities.ContainerUpdateOptions) error
28102810
}
28112811
oldRestart := c.config.RestartPolicy
28122812
oldRetries := c.config.RestartRetries
2813+
oldRlimits := c.config.Spec.Process.Rlimits
28132814

28142815
if updateOptions.RestartPolicy != nil {
28152816
if err := define.ValidateRestartPolicy(*updateOptions.RestartPolicy); err != nil {
@@ -2857,16 +2858,21 @@ func (c *Container) update(updateOptions *entities.ContainerUpdateOptions) error
28572858
c.config.Spec.Process.Env = envLib.Slice(envMap)
28582859
}
28592860

2861+
if updateOptions.Rlimits != nil {
2862+
c.config.Spec.Process.Rlimits = updateOptions.Rlimits
2863+
}
2864+
28602865
if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil {
28612866
// Assume DB write failed, revert to old resources block
28622867
c.config.Spec.Linux.Resources = oldResources
28632868
c.config.RestartPolicy = oldRestart
28642869
c.config.RestartRetries = oldRetries
2870+
c.config.Spec.Process.Rlimits = oldRlimits
28652871
return err
28662872
}
28672873

28682874
if c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStatePaused) &&
2869-
(updateOptions.Resources != nil || updateOptions.Env != nil || updateOptions.UnsetEnv != nil) {
2875+
(updateOptions.Resources != nil || updateOptions.Env != nil || updateOptions.UnsetEnv != nil || updateOptions.Rlimits != nil) {
28702876
// So `podman inspect` on running containers sources its OCI spec from disk.
28712877
// To keep inspect accurate we need to update the on-disk OCI spec.
28722878
onDiskSpec, err := c.specFromState()
@@ -2882,13 +2888,24 @@ func (c *Container) update(updateOptions *entities.ContainerUpdateOptions) error
28822888
if len(updateOptions.Env) != 0 || len(updateOptions.UnsetEnv) != 0 {
28832889
onDiskSpec.Process.Env = c.config.Spec.Process.Env
28842890
}
2891+
if updateOptions.Rlimits != nil {
2892+
onDiskSpec.Process.Rlimits = updateOptions.Rlimits
2893+
}
28852894
if err := c.saveSpec(onDiskSpec); err != nil {
28862895
logrus.Errorf("Unable to update container %s OCI spec - `podman inspect` may not be accurate until container is restarted: %v", c.ID(), err)
28872896
}
28882897

28892898
if err := c.ociRuntime.UpdateContainer(c, updateOptions.Resources); err != nil {
28902899
return err
28912900
}
2901+
2902+
// Apply rlimits to running container processes using prlimit
2903+
if updateOptions.Rlimits != nil {
2904+
if err := c.applyRlimitsToRunningContainer(updateOptions.Rlimits); err != nil {
2905+
logrus.Errorf("Failed to apply rlimits to running container %s: %v", c.ID(), err)
2906+
// Don't return error here as the rlimits are saved to config and will take effect on restart
2907+
}
2908+
}
28922909
}
28932910

28942911
logrus.Debugf("updated container %s", c.ID())
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
//go:build linux
2+
3+
package libpod
4+
5+
import (
6+
"fmt"
7+
"os"
8+
"path/filepath"
9+
"strconv"
10+
"strings"
11+
"syscall"
12+
13+
"github.com/containers/podman/v5/libpod/define"
14+
spec "github.com/opencontainers/runtime-spec/specs-go"
15+
"github.com/sirupsen/logrus"
16+
"golang.org/x/sys/unix"
17+
)
18+
19+
// applyRlimitsToRunningContainer applies rlimits to all processes in the container's cgroup
20+
// using the prlimit syscall. This is Linux-specific functionality.
21+
func (c *Container) applyRlimitsToRunningContainer(rlimits []spec.POSIXRlimit) error {
22+
// Only apply to running containers
23+
if !c.ensureState(define.ContainerStateRunning, define.ContainerStatePaused) {
24+
return nil
25+
}
26+
27+
// Get the container's cgroup path
28+
cgroupPath, err := c.cGroupPath()
29+
if err != nil {
30+
return fmt.Errorf("getting cgroup path for container %s: %w", c.ID(), err)
31+
}
32+
33+
// Read all PIDs from the cgroup.procs file
34+
pids, err := c.getAllPIDsFromCgroup(cgroupPath)
35+
if err != nil {
36+
return fmt.Errorf("getting PIDs from cgroup %s: %w", cgroupPath, err)
37+
}
38+
39+
// Map rlimit type strings to syscall resource numbers
40+
rlimitMap := map[string]int{
41+
"as": syscall.RLIMIT_AS,
42+
"core": syscall.RLIMIT_CORE,
43+
"cpu": syscall.RLIMIT_CPU,
44+
"data": syscall.RLIMIT_DATA,
45+
"fsize": syscall.RLIMIT_FSIZE,
46+
"nofile": syscall.RLIMIT_NOFILE,
47+
"stack": syscall.RLIMIT_STACK,
48+
"nproc": 6, // RLIMIT_NPROC = 6 on Linux (not available in syscall package on all platforms)
49+
}
50+
51+
// Apply each rlimit to each PID
52+
for _, rlimit := range rlimits {
53+
resource, exists := rlimitMap[rlimit.Type]
54+
if !exists {
55+
logrus.Warnf("Unknown rlimit type %s, skipping", rlimit.Type)
56+
continue
57+
}
58+
59+
// Create the new rlimit structure
60+
newLimit := &syscall.Rlimit{
61+
Cur: rlimit.Soft,
62+
Max: rlimit.Hard,
63+
}
64+
65+
// Apply to all PIDs in the container
66+
for _, pid := range pids {
67+
err := c.applyRlimitToPID(pid, resource, newLimit)
68+
if err != nil {
69+
// ESRCH means the process has exited, which is fine
70+
if err == unix.ESRCH {
71+
logrus.Debugf("Process %d has exited, skipping rlimit application", pid)
72+
continue
73+
}
74+
// For other errors, log but continue with other processes
75+
logrus.Warnf("Failed to apply rlimit %s to PID %d: %v", rlimit.Type, pid, err)
76+
}
77+
}
78+
}
79+
80+
return nil
81+
}
82+
83+
// applyRlimitToPID applies a single rlimit to a single PID using the prlimit syscall.
84+
// This is Linux-specific functionality.
85+
func (c *Container) applyRlimitToPID(pid, resource int, newLimit *syscall.Rlimit) error {
86+
// Convert syscall.Rlimit to unix.Rlimit
87+
unixLimit := &unix.Rlimit{
88+
Cur: newLimit.Cur,
89+
Max: newLimit.Max,
90+
}
91+
// Use the unix package's Prlimit function
92+
// Note: This function is only available on Linux
93+
return unix.Prlimit(pid, resource, unixLimit, nil)
94+
}
95+
96+
// getAllPIDsFromCgroup reads all PIDs from a cgroup's cgroup.procs file.
97+
// This is Linux-specific functionality.
98+
func (c *Container) getAllPIDsFromCgroup(cgroupPath string) ([]int, error) {
99+
procsFile := filepath.Join(cgroupPath, "cgroup.procs")
100+
data, err := os.ReadFile(procsFile)
101+
if err != nil {
102+
return nil, err
103+
}
104+
105+
var pids []int
106+
lines := strings.Split(string(data), "\n")
107+
for _, line := range lines {
108+
line = strings.TrimSpace(line)
109+
if line == "" {
110+
continue
111+
}
112+
pid, err := strconv.Atoi(line)
113+
if err != nil {
114+
logrus.Warnf("Invalid PID in cgroup.procs: %s", line)
115+
continue
116+
}
117+
pids = append(pids, pid)
118+
}
119+
120+
return pids, nil
121+
}

pkg/api/handlers/compat/containers.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -822,11 +822,25 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) {
822822
restartRetries = &localRetries
823823
}
824824

825+
// Rlimits
826+
var rlimits []spec.POSIXRlimit
827+
if len(options.Ulimits) > 0 {
828+
for _, ulimit := range options.Ulimits {
829+
rlimit := spec.POSIXRlimit{
830+
Type: ulimit.Name,
831+
Hard: uint64(ulimit.Hard),
832+
Soft: uint64(ulimit.Soft),
833+
}
834+
rlimits = append(rlimits, rlimit)
835+
}
836+
}
837+
825838
updateOptions := &entities.ContainerUpdateOptions{
826839
Resources: resources,
827840
ChangedHealthCheckConfiguration: &define.UpdateHealthCheckConfig{},
828841
RestartPolicy: restartPolicy,
829842
RestartRetries: restartRetries,
843+
Rlimits: rlimits,
830844
}
831845

832846
if err := ctr.Update(updateOptions); err != nil {

pkg/api/handlers/libpod/containers.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,7 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) {
462462
RestartRetries: restartRetries,
463463
Env: options.Env,
464464
UnsetEnv: options.UnsetEnv,
465+
Rlimits: options.Rlimits,
465466
}
466467

467468
err = ctr.Update(updateOptions)

pkg/api/handlers/types.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ type UpdateEntities struct {
8181
define.UpdateContainerDevicesLimits
8282
Env []string
8383
UnsetEnv []string
84+
Rlimits []specs.POSIXRlimit
8485
}
8586

8687
type Info struct {

pkg/bindings/containers/update.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ func Update(ctx context.Context, options *types.ContainerUpdateOptions) (string,
4040
if options.DevicesLimits != nil {
4141
updateEntities.UpdateContainerDevicesLimits = *options.DevicesLimits
4242
}
43+
if options.Rlimits != nil {
44+
updateEntities.Rlimits = options.Rlimits
45+
}
4346

4447
requestData, err := jsoniter.MarshalToString(updateEntities)
4548
if err != nil {

0 commit comments

Comments
 (0)