Skip to content

Commit 33f1d28

Browse files
committed
feat: add cpu-rt-period and cpu-rt-runtime options in nerdctl create/run
Signed-off-by: Swagat Bora <[email protected]>
1 parent 9d0a766 commit 33f1d28

File tree

10 files changed

+212
-31
lines changed

10 files changed

+212
-31
lines changed

cmd/nerdctl/container/container_create.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,14 @@ func createOptions(cmd *cobra.Command) (types.ContainerCreateOptions, error) {
159159
if err != nil {
160160
return opt, err
161161
}
162+
opt.CPURealtimePeriod, err = cmd.Flags().GetUint64("cpu-rt-period")
163+
if err != nil {
164+
return opt, err
165+
}
166+
opt.CPURealtimeRuntime, err = cmd.Flags().GetUint64("cpu-rt-runtime")
167+
if err != nil {
168+
return opt, err
169+
}
162170
opt.Memory, err = cmd.Flags().GetString("memory")
163171
if err != nil {
164172
return opt, err

cmd/nerdctl/container/container_run.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ func setCreateFlags(cmd *cobra.Command) {
166166
cmd.Flags().Uint64("cpu-shares", 0, "CPU shares (relative weight)")
167167
cmd.Flags().Int64("cpu-quota", -1, "Limit CPU CFS (Completely Fair Scheduler) quota")
168168
cmd.Flags().Uint64("cpu-period", 0, "Limit CPU CFS (Completely Fair Scheduler) period")
169+
cmd.Flags().Uint64("cpu-rt-period", 0, "Limit CPU real-time period in microseconds")
170+
cmd.Flags().Uint64("cpu-rt-runtime", 0, "Limit CPU real-time runtime in microseconds")
169171
// device is defined as StringSlice, not StringArray, to allow specifying "--device=DEV1,DEV2" (compatible with Podman)
170172
cmd.Flags().StringSlice("device", nil, "Add a host device to the container")
171173
// ulimit is defined as StringSlice, not StringArray, to allow specifying "--ulimit=ULIMIT1,ULIMIT2" (compatible with Podman)

cmd/nerdctl/container/container_run_cgroup_linux_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -668,3 +668,39 @@ func TestRunBlkioSettingCgroupV2(t *testing.T) {
668668

669669
testCase.Run(t)
670670
}
671+
672+
func TestRunCPURealTimeSettingCgroupV1(t *testing.T) {
673+
nerdtest.Setup()
674+
675+
testCase := &test.Case{
676+
Description: "cpu-rt-runtime-and-period",
677+
Require: require.All(
678+
require.Not(nerdtest.CGroupV2),
679+
nerdtest.Rootful,
680+
),
681+
Command: func(data test.Data, helpers test.Helpers) test.TestableCommand {
682+
return helpers.Command("create", "--name", data.Identifier(),
683+
"--cpu-rt-runtime", "950000",
684+
"--cpu-rt-period", "1000000",
685+
testutil.AlpineImage, "sleep", "infinity")
686+
},
687+
Cleanup: func(data test.Data, helpers test.Helpers) {
688+
helpers.Anyhow("rm", "-f", data.Identifier())
689+
},
690+
Expected: func(data test.Data, helpers test.Helpers) *test.Expected {
691+
return &test.Expected{
692+
ExitCode: 0,
693+
Output: expect.All(
694+
func(stdout string, info string, t *testing.T) {
695+
rtRuntime := helpers.Capture("inspect", "--format", "{{.HostConfig.CPURealtimeRuntime}}", data.Identifier())
696+
rtPeriod := helpers.Capture("inspect", "--format", "{{.HostConfig.CPURealtimePeriod}}", data.Identifier())
697+
assert.Assert(t, strings.Contains(rtRuntime, "950000"))
698+
assert.Assert(t, strings.Contains(rtPeriod, "1000000"))
699+
},
700+
),
701+
}
702+
},
703+
}
704+
705+
testCase.Run(t)
706+
}

docs/command-reference.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,8 @@ Resource flags:
203203
- :whale: `--cpu-shares`: CPU shares (relative weight)
204204
- :whale: `--cpuset-cpus`: CPUs in which to allow execution (0-3, 0,1)
205205
- :whale: `--cpuset-mems`: Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems
206+
- :whale: `--cpu-rt-period`: Limit CPU real-time period in microseconds. Only supported with cgroup v1.
207+
- :whale: `--cpu-rt-runtime`: Limit CPU real-time runtime in microseconds. Only supported with cgroup v1.
206208
- :whale: `--memory`: Memory limit
207209
- :whale: `--memory-reservation`: Memory soft limit
208210
- :whale: `--memory-swap`: Swap limit equal to memory plus swap: '-1' to enable unlimited swap
@@ -419,10 +421,8 @@ IPFS flags:
419421
- :nerd_face: `--ipfs-address`: Multiaddr of IPFS API (default uses `$IPFS_PATH` env variable if defined or local directory `~/.ipfs`)
420422

421423
Unimplemented `docker run` flags:
422-
`--cpu-rt-*`, `--device-cgroup-rule`,
423-
`--disable-content-trust`, `--expose`, `--health-*`, `--isolation`, `--no-healthcheck`,
424-
`--link*`, `--publish-all`, `--storage-opt`,
425-
`--userns`, `--volume-driver`
424+
`--device-cgroup-rule`, `--disable-content-trust`, `--expose`, `--health-*`, `--isolation`, `--no-healthcheck`,
425+
`--link*`, `--publish-all`, `--storage-opt`, `--userns`, `--volume-driver`
426426

427427
### :whale: :blue_square: nerdctl exec
428428

pkg/api/types/container_types.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,10 @@ type ContainerCreateOptions struct {
114114
CPUSetCPUs string
115115
// CPUSetMems specifies the memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
116116
CPUSetMems string
117+
// Limit CPU real-time period in microseconds
118+
CPURealtimePeriod uint64
119+
// Limit CPU real-time runtime in microseconds
120+
CPURealtimeRuntime uint64
117121
// Memory specifies the memory limit
118122
Memory string
119123
// MemoryReservationChanged specifies whether the memory soft limit has been changed

pkg/cmd/container/run_cgroup_linux.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,19 @@ func generateCgroupOpts(id string, options types.ContainerCreateOptions, interna
102102
opts = append(opts, oci.WithCPUsMems(options.CPUSetMems))
103103
}
104104

105+
if options.CPURealtimePeriod != 0 || options.CPURealtimeRuntime != 0 {
106+
if !infoutil.CPURealtime(options.GOptions.CgroupManager) {
107+
// CPU realtime scheduling is not supported in cgroup V2
108+
return nil, errors.New("kernel does not support CPU real-time scheduler")
109+
}
110+
111+
if options.CPURealtimePeriod != 0 && options.CPURealtimeRuntime != 0 &&
112+
options.CPURealtimeRuntime > options.CPURealtimePeriod {
113+
return nil, errors.New("cpu real-time runtime cannot be higher than cpu real-time period")
114+
}
115+
}
116+
opts = append(opts, oci.WithCPURT(int64(options.CPURealtimeRuntime), options.CPURealtimePeriod))
117+
105118
var mem64 int64
106119
if options.Memory != "" {
107120
mem64, err = units.RAMInBytes(options.Memory)

pkg/infoutil/infoutil.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -284,3 +284,8 @@ func BlockIOReadIOpsDevice(cgroupManager string) bool {
284284
func BlockIOWriteIOpsDevice(cgroupManager string) bool {
285285
return getMobySysInfo(cgroupManager).BlkioWriteIOpsDevice
286286
}
287+
288+
// CPURealtime returns whether CPU realtime period is supported or not
289+
func CPURealtime(cgroupManager string) bool {
290+
return getMobySysInfo(cgroupManager).CPURealtime
291+
}

pkg/inspecttypes/dockercompat/dockercompat.go

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -167,17 +167,20 @@ type HostConfig struct {
167167
Tmpfs map[string]string `json:"Tmpfs,omitempty"` // List of tmpfs (mounts) used for the container
168168
UTSMode string // UTS namespace to use for the container
169169
// UsernsMode UsernsMode // The user namespace to use for the container
170-
ShmSize int64 // Size of /dev/shm in bytes. The size must be greater than 0.
171-
Sysctls map[string]string // List of Namespaced sysctls used for the container
172-
Runtime string // Runtime to use with this container
173-
CPUSetMems string `json:"CpusetMems"` // CpusetMems 0-2, 0,1
174-
CPUSetCPUs string `json:"CpusetCpus"` // CpusetCpus 0-2, 0,1
175-
CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota
176-
CPUShares uint64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers)
177-
Memory int64 // Memory limit (in bytes)
178-
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap
179-
OomKillDisable bool // specifies whether to disable OOM Killer
180-
Devices []DeviceMapping // List of devices to map inside the container
170+
ShmSize int64 // Size of /dev/shm in bytes. The size must be greater than 0.
171+
Sysctls map[string]string // List of Namespaced sysctls used for the container
172+
Runtime string // Runtime to use with this container
173+
CPUSetMems string `json:"CpusetMems"` // CpusetMems 0-2, 0,1
174+
CPUSetCPUs string `json:"CpusetCpus"` // CpusetCpus 0-2, 0,1
175+
CPUQuota int64 `json:"CpuQuota"` // CPU CFS (Completely Fair Scheduler) quota
176+
CPUShares uint64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers)
177+
CPUPeriod uint64 `json:"CpuPeriod"` // Limits the CPU CFS (Completely Fair Scheduler) period
178+
CPURealtimePeriod uint64 `json:"CpuRealtimePeriod"` // Limits the CPU real-time period in microseconds
179+
CPURealtimeRuntime int64 `json:"CpuRealtimeRuntime"` // Limits the CPU real-time runtime in microseconds
180+
Memory int64 // Memory limit (in bytes)
181+
MemorySwap int64 // Total memory usage (memory + swap); set `-1` to enable unlimited swap
182+
OomKillDisable bool // specifies whether to disable OOM Killer
183+
Devices []DeviceMapping // List of devices to map inside the container
181184
LinuxBlkioSettings
182185
}
183186

@@ -264,11 +267,14 @@ type DeviceMapping struct {
264267
CgroupPermissions string
265268
}
266269

267-
type cpuSettings struct {
268-
cpuSetCpus string
269-
cpuSetMems string
270-
cpuShares uint64
271-
cpuQuota int64
270+
type CPUSettings struct {
271+
CPUSetCpus string
272+
CPUSetMems string
273+
CPUShares uint64
274+
CPUQuota int64
275+
CPUPeriod uint64
276+
CPURealtimePeriod uint64
277+
CPURealtimeRuntime int64
272278
}
273279

274280
// DefaultNetworkSettings is from https://github.com/moby/moby/blob/v20.10.1/api/types/types.go#L405-L414
@@ -478,12 +484,15 @@ func ContainerFromNative(n *native.Container) (*Container, error) {
478484

479485
cpuSetting, err := cpuSettingsFromNative(n.Spec.(*specs.Spec))
480486
if err != nil {
481-
return nil, fmt.Errorf("failed to Decode cpuSettings: %v", err)
487+
return nil, fmt.Errorf("failed to Decode cpuSetting: %v", err)
482488
}
483-
c.HostConfig.CPUSetCPUs = cpuSetting.cpuSetCpus
484-
c.HostConfig.CPUSetMems = cpuSetting.cpuSetMems
485-
c.HostConfig.CPUQuota = cpuSetting.cpuQuota
486-
c.HostConfig.CPUShares = cpuSetting.cpuShares
489+
c.HostConfig.CPUSetCPUs = cpuSetting.CPUSetCpus
490+
c.HostConfig.CPUSetMems = cpuSetting.CPUSetMems
491+
c.HostConfig.CPUQuota = cpuSetting.CPUQuota
492+
c.HostConfig.CPUShares = cpuSetting.CPUShares
493+
c.HostConfig.CPUPeriod = cpuSetting.CPUPeriod
494+
c.HostConfig.CPURealtimePeriod = cpuSetting.CPURealtimePeriod
495+
c.HostConfig.CPURealtimeRuntime = cpuSetting.CPURealtimeRuntime
487496

488497
cgroupNamespace, err := getCgroupnsFromNative(n.Spec.(*specs.Spec))
489498
if err != nil {
@@ -728,23 +737,32 @@ func networkSettingsFromNative(n *native.NetNS, sp *specs.Spec) (*NetworkSetting
728737
return res, nil
729738
}
730739

731-
func cpuSettingsFromNative(sp *specs.Spec) (*cpuSettings, error) {
732-
res := &cpuSettings{}
740+
func cpuSettingsFromNative(sp *specs.Spec) (*CPUSettings, error) {
741+
res := &CPUSettings{}
733742
if sp.Linux != nil && sp.Linux.Resources != nil && sp.Linux.Resources.CPU != nil {
734743
if sp.Linux.Resources.CPU.Cpus != "" {
735-
res.cpuSetCpus = sp.Linux.Resources.CPU.Cpus
744+
res.CPUSetCpus = sp.Linux.Resources.CPU.Cpus
736745
}
737746

738747
if sp.Linux.Resources.CPU.Mems != "" {
739-
res.cpuSetMems = sp.Linux.Resources.CPU.Mems
748+
res.CPUSetMems = sp.Linux.Resources.CPU.Mems
740749
}
741750

742751
if sp.Linux.Resources.CPU.Shares != nil && *sp.Linux.Resources.CPU.Shares > 0 {
743-
res.cpuShares = *sp.Linux.Resources.CPU.Shares
752+
res.CPUShares = *sp.Linux.Resources.CPU.Shares
744753
}
745754

746755
if sp.Linux.Resources.CPU.Quota != nil && *sp.Linux.Resources.CPU.Quota > 0 {
747-
res.cpuQuota = *sp.Linux.Resources.CPU.Quota
756+
res.CPUQuota = *sp.Linux.Resources.CPU.Quota
757+
}
758+
if sp.Linux.Resources.CPU.Period != nil && *sp.Linux.Resources.CPU.Period > 0 {
759+
res.CPUPeriod = *sp.Linux.Resources.CPU.Period
760+
}
761+
if sp.Linux.Resources.CPU.RealtimePeriod != nil && *sp.Linux.Resources.CPU.RealtimePeriod > 0 {
762+
res.CPURealtimePeriod = *sp.Linux.Resources.CPU.RealtimePeriod
763+
}
764+
if sp.Linux.Resources.CPU.RealtimeRuntime != nil && *sp.Linux.Resources.CPU.RealtimeRuntime > 0 {
765+
res.CPURealtimeRuntime = *sp.Linux.Resources.CPU.RealtimeRuntime
748766
}
749767
}
750768

pkg/inspecttypes/dockercompat/dockercompat_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -417,3 +417,97 @@ func TestNetworkSettingsFromNative(t *testing.T) {
417417
})
418418
}
419419
}
420+
421+
func TestCpuSettingsFromNative(t *testing.T) {
422+
// Helper function to create uint64 pointer
423+
uint64Ptr := func(i uint64) *uint64 {
424+
return &i
425+
}
426+
427+
int64Ptr := func(i int64) *int64 {
428+
return &i
429+
}
430+
431+
testcases := []struct {
432+
name string
433+
spec *specs.Spec
434+
expected *CPUSettings
435+
}{
436+
{
437+
name: "Test with empty spec",
438+
spec: &specs.Spec{},
439+
expected: &CPUSettings{},
440+
},
441+
{
442+
name: "Full CPU Settings",
443+
spec: &specs.Spec{
444+
Linux: &specs.Linux{
445+
Resources: &specs.LinuxResources{
446+
CPU: &specs.LinuxCPU{
447+
Cpus: "0-3",
448+
Mems: "0-1",
449+
Shares: uint64Ptr(1024),
450+
Quota: int64Ptr(100000),
451+
Period: uint64Ptr(100000),
452+
RealtimePeriod: uint64Ptr(1000000),
453+
RealtimeRuntime: int64Ptr(950000),
454+
},
455+
},
456+
},
457+
},
458+
expected: &CPUSettings{
459+
CPUSetCpus: "0-3",
460+
CPUSetMems: "0-1",
461+
CPUShares: 1024,
462+
CPUQuota: 100000,
463+
CPUPeriod: 100000,
464+
CPURealtimePeriod: 1000000,
465+
CPURealtimeRuntime: 950000,
466+
},
467+
},
468+
{
469+
name: "Partial CPU Settings",
470+
spec: &specs.Spec{
471+
Linux: &specs.Linux{
472+
Resources: &specs.LinuxResources{
473+
CPU: &specs.LinuxCPU{
474+
Cpus: "0,1",
475+
Shares: uint64Ptr(512),
476+
},
477+
},
478+
},
479+
},
480+
expected: &CPUSettings{
481+
CPUSetCpus: "0,1",
482+
CPUShares: 512,
483+
},
484+
},
485+
{
486+
name: "Zero Values Should Be Ignored",
487+
spec: &specs.Spec{
488+
Linux: &specs.Linux{
489+
Resources: &specs.LinuxResources{
490+
CPU: &specs.LinuxCPU{
491+
Shares: uint64Ptr(0),
492+
Quota: int64Ptr(0),
493+
Period: uint64Ptr(0),
494+
RealtimePeriod: uint64Ptr(0),
495+
RealtimeRuntime: int64Ptr(0),
496+
},
497+
},
498+
},
499+
},
500+
expected: &CPUSettings{},
501+
},
502+
}
503+
504+
for _, tc := range testcases {
505+
t.Run(tc.name, func(t *testing.T) {
506+
result, err := cpuSettingsFromNative(tc.spec)
507+
if err != nil {
508+
t.Fatalf("unexpected error: %v", err)
509+
}
510+
assert.DeepEqual(t, result, tc.expected)
511+
})
512+
}
513+
}

pkg/inspecttypes/dockercompat/info.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type Info struct {
3737
CPUCfsQuota bool `json:"CpuCfsQuota"`
3838
CPUShares bool
3939
CPUSet bool
40+
CPURealtime bool
4041
PidsLimit bool
4142
IPv4Forwarding bool
4243
BridgeNfIptables bool

0 commit comments

Comments
 (0)