diff --git a/README.md b/README.md index 6ade2040245..713d99b00de 100644 --- a/README.md +++ b/README.md @@ -311,6 +311,7 @@ Cgroup flags: - :whale: `--memory`: Memory limit - :whale: `--pids-limit`: Tune container pids limit - :nerd_face: `--cgroup-conf`: Configure cgroup v2 (key=value) +- :whale: `blkio-weight`: Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0) - :whale: `--cgroupns=(host|private)`: Cgroup namespace to use - Default: "private" on cgroup v2 hosts, "host" on cgroup v1 hosts - :whale: `--device`: Add a host device to the container diff --git a/cmd/nerdctl/run.go b/cmd/nerdctl/run.go index 4dc02ccc394..588ae50ae1c 100644 --- a/cmd/nerdctl/run.go +++ b/cmd/nerdctl/run.go @@ -135,6 +135,7 @@ func newRunCommand() *cobra.Command { }) runCommand.Flags().Int("pids-limit", -1, "Tune container pids limit (set -1 for unlimited)") runCommand.Flags().StringSlice("cgroup-conf", nil, "Configure cgroup v2 (key=value)") + runCommand.Flags().Uint16("blkio-weight", 0, "Block IO (relative weight), between 10 and 1000, or 0 to disable (default 0)") runCommand.Flags().String("cgroupns", defaults.CgroupnsMode(), `Cgroup namespace to use, the default depends on the cgroup version ("host"|"private")`) runCommand.RegisterFlagCompletionFunc("cgroupns", func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { return []string{"host", "private"}, cobra.ShellCompDirectiveNoFileComp diff --git a/cmd/nerdctl/run_cgroup_linux.go b/cmd/nerdctl/run_cgroup_linux.go index 891c711e9c7..a35bf8ce799 100644 --- a/cmd/nerdctl/run_cgroup_linux.go +++ b/cmd/nerdctl/run_cgroup_linux.go @@ -20,6 +20,8 @@ import ( "context" "errors" "fmt" + "io/fs" + "os" "path/filepath" "strings" @@ -133,6 +135,27 @@ func generateCgroupOpts(cmd *cobra.Command, id string) ([]oci.SpecOpts, error) { } opts = append(opts, withUnified(unifieds)) + blkioWeight, err := cmd.Flags().GetUint16("blkio-weight") + if err != nil { + return nil, err + } + if infoutil.CgroupsVersion() == "1" { + blkioController := "/sys/fs/cgroup/blkio" + blkioWeightPath := filepath.Join(blkioController, "blkio.weight") + if _, err := os.Stat(blkioWeightPath); errors.Is(err, fs.ErrNotExist) { + // if bfq io scheduler is used, the blkio.weight knob will be exposed as blkio.bfq.weight + blkioBfqWeightPath := filepath.Join(blkioController, "blkio.bfq.weight") + if _, err := os.Stat(blkioBfqWeightPath); errors.Is(err, fs.ErrNotExist) { + logrus.Warn("kernel support for cgroup blkio weight missing, weight discarded") + blkioWeight = 0 + } + } + } + if blkioWeight > 0 && blkioWeight < 10 || blkioWeight > 1000 { + return nil, errors.New("range of blkio weight is from 10 to 1000") + } + opts = append(opts, withBlkioWeight(blkioWeight)) + cgroupns, err := cmd.Flags().GetString("cgroupns") if err != nil { return nil, err @@ -224,3 +247,13 @@ func withUnified(unified map[string]string) oci.SpecOpts { return nil } } + +func withBlkioWeight(blkioWeight uint16) oci.SpecOpts { + return func(_ context.Context, _ oci.Client, _ *containers.Container, s *oci.Spec) error { + if blkioWeight == 0 { + return nil + } + s.Linux.Resources.BlockIO = &specs.LinuxBlockIO{Weight: &blkioWeight} + return nil + } +} diff --git a/cmd/nerdctl/run_test.go b/cmd/nerdctl/run_test.go index fdb27ac6d1a..1be597ef387 100644 --- a/cmd/nerdctl/run_test.go +++ b/cmd/nerdctl/run_test.go @@ -209,6 +209,17 @@ func TestRunCgroupConf(t *testing.T) { "sh", "-ec", "cd /sys/fs/cgroup && cat memory.high").AssertOutContains("33554432") } +func TestRunBlkioWeightCgroupV2(t *testing.T) { + if cgroups.Mode() != cgroups.Unified { + t.Skip("test requires cgroup v2") + } + base := testutil.NewBase(t) + + // when bfq io scheduler is used, the io.weight knob is exposed as io.bfq.weight + base.Cmd("run", "--rm", "--blkio-weight", "300", testutil.AlpineImage, + "sh", "-ec", "cd /sys/fs/cgroup && cat io.bfq.weight").AssertOutContains("300") +} + func TestRunAddHost(t *testing.T) { base := testutil.NewBase(t) base.Cmd("run", "--rm", "--add-host", "testing.example.com:10.0.0.1", testutil.AlpineImage, "sh", "-c", "cat /etc/hosts").AssertOutWithFunc(func(stdout string) error { diff --git a/pkg/composer/serviceparser/serviceparser.go b/pkg/composer/serviceparser/serviceparser.go index 4e4da5ffb2a..2d6e9e2c88e 100644 --- a/pkg/composer/serviceparser/serviceparser.go +++ b/pkg/composer/serviceparser/serviceparser.go @@ -37,6 +37,7 @@ func warnUnknownFields(svc compose.ServiceConfig) { if unknown := reflectutil.UnknownNonEmptyFields(&svc, "Name", "Build", + "BlkioConfig", "CapAdd", "CapDrop", "CPUS", @@ -81,6 +82,14 @@ func warnUnknownFields(svc compose.ServiceConfig) { logrus.Warnf("Ignoring: service %s: %+v", svc.Name, unknown) } + if svc.BlkioConfig != nil { + if unknown := reflectutil.UnknownNonEmptyFields(svc.BlkioConfig, + "Weight", + ); len(unknown) > 0 { + logrus.Warnf("Ignoring: service %s: blkio_config: %+v", svc.Name, unknown) + } + } + for depName, dep := range svc.DependsOn { if unknown := reflectutil.UnknownNonEmptyFields(&dep, "Condition", @@ -418,6 +427,10 @@ func newContainer(project *compose.Project, parsed *Service, i int) (*Container, "--pull=never", // because image will be ensured before running replicas with `nerdctl run`. } + if svc.BlkioConfig != nil && svc.BlkioConfig.Weight != 0 { + c.RunArgs = append(c.RunArgs, fmt.Sprintf("--blkio-weight=%d", svc.BlkioConfig.Weight)) + } + for _, v := range svc.CapAdd { c.RunArgs = append(c.RunArgs, fmt.Sprintf("--cap-add=%s", v)) }