Pārlūkot izejas kodu

Merge pull request #14466 from Mashimiao/add-support-blkio_throtte_bps

Add support for blkio read/write bps device
Sebastiaan van Stijn 9 gadi atpakaļ
vecāks
revīzija
cb6a1a6042

+ 2 - 0
contrib/completion/bash/docker

@@ -1372,6 +1372,8 @@ _docker_run() {
 		--cpuset-mems
 		--cpu-shares
 		--device
+		--device-read-bps
+		--device-write-bps
 		--dns
 		--dns-opt
 		--dns-search

+ 2 - 0
contrib/completion/zsh/_docker

@@ -468,6 +468,8 @@ __docker_subcommand() {
         "($help)*--cap-drop=[Drop Linux capabilities]:capability: "
         "($help)--cidfile=[Write the container ID to the file]:CID file:_files"
         "($help)*--device=[Add a host device to the container]:device:_files"
+        "($help)*--device-read-bps=[Limit the read rate (bytes per second) from a device]:device:IO rate: "
+        "($help)*--device-write-bps=[Limit the write rate (bytes per second) to a device]:device:IO rate: "
         "($help)*--dns=[Set custom DNS servers]:DNS server: "
         "($help)*--dns-opt=[Set custom DNS options]:DNS option: "
         "($help)*--dns-search=[Set custom DNS search domains]:DNS domains: "

+ 22 - 10
daemon/container_operations_unix.go

@@ -163,6 +163,16 @@ func (daemon *Daemon) populateCommand(c *container.Container, env []string) erro
 		return err
 	}
 
+	readBpsDevice, err := getBlkioReadBpsDevices(c.HostConfig)
+	if err != nil {
+		return err
+	}
+
+	writeBpsDevice, err := getBlkioWriteBpsDevices(c.HostConfig)
+	if err != nil {
+		return err
+	}
+
 	for _, limit := range ulimits {
 		rl, err := limit.GetRlimit()
 		if err != nil {
@@ -178,16 +188,18 @@ func (daemon *Daemon) populateCommand(c *container.Container, env []string) erro
 			CPUShares:         c.HostConfig.CPUShares,
 			BlkioWeight:       c.HostConfig.BlkioWeight,
 		},
-		MemorySwap:        c.HostConfig.MemorySwap,
-		KernelMemory:      c.HostConfig.KernelMemory,
-		CpusetCpus:        c.HostConfig.CpusetCpus,
-		CpusetMems:        c.HostConfig.CpusetMems,
-		CPUPeriod:         c.HostConfig.CPUPeriod,
-		CPUQuota:          c.HostConfig.CPUQuota,
-		Rlimits:           rlimits,
-		BlkioWeightDevice: weightDevices,
-		OomKillDisable:    c.HostConfig.OomKillDisable,
-		MemorySwappiness:  *c.HostConfig.MemorySwappiness,
+		MemorySwap:                  c.HostConfig.MemorySwap,
+		KernelMemory:                c.HostConfig.KernelMemory,
+		CpusetCpus:                  c.HostConfig.CpusetCpus,
+		CpusetMems:                  c.HostConfig.CpusetMems,
+		CPUPeriod:                   c.HostConfig.CPUPeriod,
+		CPUQuota:                    c.HostConfig.CPUQuota,
+		Rlimits:                     rlimits,
+		BlkioWeightDevice:           weightDevices,
+		BlkioThrottleReadBpsDevice:  readBpsDevice,
+		BlkioThrottleWriteBpsDevice: writeBpsDevice,
+		OomKillDisable:              c.HostConfig.OomKillDisable,
+		MemorySwappiness:            *c.HostConfig.MemorySwappiness,
 	}
 
 	processConfig := execdriver.ProcessConfig{

+ 40 - 0
daemon/daemon_unix.go

@@ -83,6 +83,36 @@ func parseSecurityOpt(container *container.Container, config *runconfig.HostConf
 	return err
 }
 
+func getBlkioReadBpsDevices(config *runconfig.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
+	var BlkioReadBpsDevice []*blkiodev.ThrottleDevice
+	var stat syscall.Stat_t
+
+	for _, bpsDevice := range config.BlkioDeviceReadBps {
+		if err := syscall.Stat(bpsDevice.Path, &stat); err != nil {
+			return nil, err
+		}
+		ReadBpsDevice := blkiodev.NewThrottleDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), bpsDevice.Rate)
+		BlkioReadBpsDevice = append(BlkioReadBpsDevice, ReadBpsDevice)
+	}
+
+	return BlkioReadBpsDevice, nil
+}
+
+func getBlkioWriteBpsDevices(config *runconfig.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
+	var BlkioWriteBpsDevice []*blkiodev.ThrottleDevice
+	var stat syscall.Stat_t
+
+	for _, bpsDevice := range config.BlkioDeviceWriteBps {
+		if err := syscall.Stat(bpsDevice.Path, &stat); err != nil {
+			return nil, err
+		}
+		WriteBpsDevice := blkiodev.NewThrottleDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), bpsDevice.Rate)
+		BlkioWriteBpsDevice = append(BlkioWriteBpsDevice, WriteBpsDevice)
+	}
+
+	return BlkioWriteBpsDevice, nil
+}
+
 func checkKernelVersion(k, major, minor int) bool {
 	if v, err := kernel.GetKernelVersion(); err != nil {
 		logrus.Warnf("%s", err)
@@ -259,6 +289,16 @@ func verifyPlatformContainerSettings(daemon *Daemon, hostConfig *runconfig.HostC
 		logrus.Warnf("Your kernel does not support Block I/O weight_device. Weight-device discarded.")
 		hostConfig.BlkioWeightDevice = []*pblkiodev.WeightDevice{}
 	}
+	if len(hostConfig.BlkioDeviceReadBps) > 0 && !sysInfo.BlkioReadBpsDevice {
+		warnings = append(warnings, "Your kernel does not support Block read limit in bytes per second.")
+		logrus.Warnf("Your kernel does not support Block I/O read limit in bytes per second. --device-read-bps discarded.")
+		hostConfig.BlkioDeviceReadBps = []*pblkiodev.ThrottleDevice{}
+	}
+	if len(hostConfig.BlkioDeviceWriteBps) > 0 && !sysInfo.BlkioWriteBpsDevice {
+		warnings = append(warnings, "Your kernel does not support Block write limit in bytes per second.")
+		logrus.Warnf("Your kernel does not support Block I/O write limit in bytes per second. --device-write-bps discarded.")
+		hostConfig.BlkioDeviceWriteBps = []*pblkiodev.ThrottleDevice{}
+	}
 	if hostConfig.OomKillDisable && !sysInfo.OomKillDisable {
 		hostConfig.OomKillDisable = false
 		return warnings, fmt.Errorf("Your kernel does not support oom kill disable.")

+ 8 - 0
daemon/daemon_windows.go

@@ -39,6 +39,14 @@ func parseSecurityOpt(container *container.Container, config *runconfig.HostConf
 	return nil
 }
 
+func getBlkioReadBpsDevices(config *runconfig.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
+	return nil, nil
+}
+
+func getBlkioWriteBpsDevices(config *runconfig.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
+	return nil, nil
+}
+
 func setupInitLayer(initLayer string, rootUID, rootGID int) error {
 	return nil
 }

+ 14 - 10
daemon/execdriver/driver_unix.go

@@ -38,16 +38,18 @@ type Resources struct {
 
 	// Fields below here are platform specific
 
-	BlkioWeightDevice []*blkiodev.WeightDevice `json:"blkio_weight_device"`
-	MemorySwap        int64                    `json:"memory_swap"`
-	KernelMemory      int64                    `json:"kernel_memory"`
-	CPUQuota          int64                    `json:"cpu_quota"`
-	CpusetCpus        string                   `json:"cpuset_cpus"`
-	CpusetMems        string                   `json:"cpuset_mems"`
-	CPUPeriod         int64                    `json:"cpu_period"`
-	Rlimits           []*ulimit.Rlimit         `json:"rlimits"`
-	OomKillDisable    bool                     `json:"oom_kill_disable"`
-	MemorySwappiness  int64                    `json:"memory_swappiness"`
+	BlkioWeightDevice           []*blkiodev.WeightDevice   `json:"blkio_weight_device"`
+	BlkioThrottleReadBpsDevice  []*blkiodev.ThrottleDevice `json:"blkio_throttle_read_bps_device"`
+	BlkioThrottleWriteBpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_write_bps_device"`
+	MemorySwap                  int64                      `json:"memory_swap"`
+	KernelMemory                int64                      `json:"kernel_memory"`
+	CPUQuota                    int64                      `json:"cpu_quota"`
+	CpusetCpus                  string                     `json:"cpuset_cpus"`
+	CpusetMems                  string                     `json:"cpuset_mems"`
+	CPUPeriod                   int64                      `json:"cpu_period"`
+	Rlimits                     []*ulimit.Rlimit           `json:"rlimits"`
+	OomKillDisable              bool                       `json:"oom_kill_disable"`
+	MemorySwappiness            int64                      `json:"memory_swappiness"`
 }
 
 // ProcessConfig is the platform specific structure that describes a process
@@ -170,6 +172,8 @@ func SetupCgroups(container *configs.Config, c *Command) error {
 		container.Cgroups.CpuQuota = c.Resources.CPUQuota
 		container.Cgroups.BlkioWeight = c.Resources.BlkioWeight
 		container.Cgroups.BlkioWeightDevice = c.Resources.BlkioWeightDevice
+		container.Cgroups.BlkioThrottleReadBpsDevice = c.Resources.BlkioThrottleReadBpsDevice
+		container.Cgroups.BlkioThrottleWriteBpsDevice = c.Resources.BlkioThrottleWriteBpsDevice
 		container.Cgroups.OomKillDisable = c.Resources.OomKillDisable
 		container.Cgroups.MemorySwappiness = c.Resources.MemorySwappiness
 	}

+ 8 - 0
docs/reference/api/docker_remote_api_v1.22.md

@@ -188,6 +188,8 @@ Create a container
              "CpusetMems": "0,1",
              "BlkioWeight": 300,
              "BlkioWeightDevice": [{}],
+             "BlkioDeviceReadBps": [{}],
+             "BlkioDeviceWriteBps": [{}],
              "MemorySwappiness": 60,
              "OomKillDisable": false,
              "OomScoreAdj": 500,
@@ -245,6 +247,10 @@ Json Parameters:
 -   **CpusetMems** - Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.
 -   **BlkioWeight** - Block IO weight (relative weight) accepts a weight value between 10 and 1000.
 -   **BlkioWeightDevice** - Block IO weight (relative device weight) in the form of:        `"BlkioWeightDevice": [{"Path": "device_path", "Weight": weight}]`
+-   **BlkioDeviceReadBps** - Limit read rate from a device in form of:	`"BlkioDeviceReadBps": [{"Path": "device_path", "Rate": rate}]`, for example:
+	`"BlkioDeviceReadBps": [{"Path": "/dev/sda", "Rate": "1024"}]"`
+-   **BlkioDeviceWriteBps** - Limit write rate to a device in the form of:	`"BlkioDeviceWriteBps": [{"Path": "deivce_path", "Rate": rate}]`, for example:
+	`"BlkioDeviceWriteBps": [{"Path": "/dev/sda", "Rate": "1024"}]"`
 -   **MemorySwappiness** - Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.
 -   **OomKillDisable** - Boolean value, whether to disable OOM Killer for the container or not.
 -   **OomScoreAdj** - An integer value containing the score given to the container in order to tune OOM killer preferences.
@@ -398,6 +404,8 @@ Return low-level information on the container `id`
 			"Binds": null,
 			"BlkioWeight": 0,
 			"BlkioWeightDevice": [{}],
+			"BlkioDeviceReadBps": [{}],
+			"BlkioDeviceWriteBps": [{}],
 			"CapAdd": null,
 			"CapDrop": null,
 			"ContainerIDFile": "",

+ 2 - 0
docs/reference/commandline/create.md

@@ -30,6 +30,8 @@ Creates a new container.
       --cpuset-cpus=""              CPUs in which to allow execution (0-3, 0,1)
       --cpuset-mems=""              Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
       --device=[]                   Add a host device to the container
+      --device-read-bps=[]          Limit read rate (bytes per second) from a device (e.g., --device-read-bps=/dev/sda:1mb)
+      --device-write-bps=[]         Limit write rate (bytes per second) to a device (e.g., --device-write-bps=/dev/sda:1mb)
       --disable-content-trust=true  Skip image verification
       --dns=[]                      Set custom DNS servers
       --dns-opt=[]                  Set custom DNS options

+ 2 - 0
docs/reference/commandline/run.md

@@ -29,6 +29,8 @@ parent = "smn_cli"
       --cpuset-mems=""              Memory nodes (MEMs) in which to allow execution (0-3, 0,1)
       -d, --detach=false            Run container in background and print container ID
       --device=[]                   Add a host device to the container
+      --device-read-bps=[]          Limit read rate (bytes per second) from a device (e.g., --device-read-bps=/dev/sda:1mb)
+      --device-write-bps=[]         Limit write rate (bytes per second) to a device (e.g., --device-write-bps=/dev/sda:1mb)
       --disable-content-trust=true  Skip image verification
       --dns=[]                      Set custom DNS servers
       --dns-opt=[]                  Set custom DNS options

+ 16 - 0
docs/reference/run.md

@@ -624,6 +624,10 @@ container:
 | `--cpu-quota=0`            | Limit the CPU CFS (Completely Fair Scheduler) quota                                         |
 | `--blkio-weight=0`         | Block IO weight (relative weight) accepts a weight value between 10 and 1000.               |
 | `--blkio-weight-device=""` | Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)                                                |
+| `--device-read-bps="" `    | Limit read rate from a device (format: `<device-path>:<number>[<unit>]`.                    |
+|                            | Number is a positive integer. Unit can be one of kb, mb, or gb.                             |
+| `--device-write-bps="" `   | Limit write rate to a device (format: `<device-path>:<number>[<unit>]`.                     |
+|                            | Number is a positive integer. Unit can be one of kb, mb, or gb.                             |
 | `--oom-kill-disable=false` | Whether to disable OOM Killer for the container or not.                                     |
 | `--memory-swappiness=""  ` | Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100.        |
 | `--shm-size=""  `          | Size of `/dev/shm`. The format is `<number><unit>`. `number` must be greater than `0`.      |
@@ -978,6 +982,18 @@ $ docker run -it \
     --blkio-weight-device "/dev/sda:200" \
     ubuntu
 
+The `--device-read-bps` flag can limit read rate from a device.
+For example, the command creates a container and limits theread rate to `1mb` per second from `/dev/sda`:
+
+    $ docker run -ti --device-read-bps /dev/sda:1mb ubuntu
+
+The `--device-write-bps` flag can limit write rate to a device.
+For example, the command creates a container and limits write rate to `1mb` per second to `/dev/sda`:
+
+    $ docker run -ti --device-write-bps /dev/sda:1mb ubuntu
+
+Both flags take limits in the `<device-path>:<limit>[unit]` format. Both read and write rates must be a positive integer. You can specify the rate in `kb` (kilobytes), `mb` (megabytes), or `gb` (gigabytes).
+
 ## Additional groups
     --group-add: Add Linux capabilities
 

+ 12 - 0
integration-cli/docker_cli_run_unix_test.go

@@ -246,6 +246,18 @@ func (s *DockerSuite) TestRunWithBlkioInvalidWeightDevice(c *check.C) {
 	c.Assert(err, check.NotNil, check.Commentf(out))
 }
 
+func (s *DockerSuite) TestRunWithBlkioInvalidDeivceReadBps(c *check.C) {
+	testRequires(c, blkioWeight)
+	out, _, err := dockerCmdWithError("run", "--device-read-bps", "/dev/sda:500", "busybox", "true")
+	c.Assert(err, check.NotNil, check.Commentf(out))
+}
+
+func (s *DockerSuite) TestRunWithBlkioInvalidDeviceWriteBps(c *check.C) {
+	testRequires(c, blkioWeight)
+	out, _, err := dockerCmdWithError("run", "--device-write-bps", "/dev/sda:500", "busybox", "true")
+	c.Assert(err, check.NotNil, check.Commentf(out))
+}
+
 func (s *DockerSuite) TestRunOOMExitCode(c *check.C) {
 	testRequires(c, oomControl)
 	errChan := make(chan error)

+ 8 - 0
man/docker-create.1.md

@@ -20,6 +20,8 @@ docker-create - Create a new container
 [**--cpuset-cpus**[=*CPUSET-CPUS*]]
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
 [**--device**[=*[]*]]
+[**--device-read-bps**[=*[]*]]
+[**--device-write-bps**[=*[]*]]
 [**--dns**[=*[]*]]
 [**--dns-search**[=*[]*]]
 [**--dns-opt**[=*[]*]]
@@ -125,6 +127,12 @@ two memory nodes.
 **--device**=[]
    Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
 
+**--device-read-bps**=[]
+    Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)
+
+**--device-write-bps**=[]
+    Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)
+
 **--dns**=[]
    Set custom DNS servers
 

+ 8 - 0
man/docker-run.1.md

@@ -21,6 +21,8 @@ docker-run - Run a command in a new container
 [**--cpuset-mems**[=*CPUSET-MEMS*]]
 [**-d**|**--detach**[=*false*]]
 [**--device**[=*[]*]]
+[**--device-read-bps**[=*[]*]]
+[**--device-write-bps**[=*[]*]]
 [**--dns**[=*[]*]]
 [**--dns-opt**[=*[]*]]
 [**--dns-search**[=*[]*]]
@@ -192,6 +194,12 @@ stopping the process by pressing the keys CTRL-P CTRL-Q.
 **--device**=[]
    Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
 
+**--device-read-bps**=[]
+   Limit read rate from a device (e.g. --device-read-bps=/dev/sda:1mb)
+
+**--device-write-bps**=[]
+   Limit write rate to a device (e.g. --device-write-bps=/dev/sda:1mb)
+
 **--dns-search**=[]
    Set custom DNS search domains (Use --dns-search=. if you don't wish to set the search domain)
 

+ 27 - 0
opts/opts.go

@@ -11,6 +11,7 @@ import (
 
 	"github.com/docker/docker/pkg/blkiodev"
 	"github.com/docker/docker/pkg/parsers"
+	"github.com/docker/docker/pkg/units"
 )
 
 var (
@@ -173,6 +174,9 @@ type ValidatorFctType func(val string) (string, error)
 // ValidatorWeightFctType defines a validator function that returns a validated struct and/or an error.
 type ValidatorWeightFctType func(val string) (*blkiodev.WeightDevice, error)
 
+// ValidatorThrottleFctType defines a validator function that returns a validated struct and/or an error.
+type ValidatorThrottleFctType func(val string) (*blkiodev.ThrottleDevice, error)
+
 // ValidatorFctListType defines a validator function that returns a validated list of string and/or an error
 type ValidatorFctListType func(val string) ([]string, error)
 
@@ -210,6 +214,29 @@ func ValidateWeightDevice(val string) (*blkiodev.WeightDevice, error) {
 	}, nil
 }
 
+// ValidateThrottleBpsDevice validates that the specified string has a valid device-rate format.
+func ValidateThrottleBpsDevice(val string) (*blkiodev.ThrottleDevice, error) {
+	split := strings.SplitN(val, ":", 2)
+	if len(split) != 2 {
+		return nil, fmt.Errorf("bad format: %s", val)
+	}
+	if !strings.HasPrefix(split[0], "/dev/") {
+		return nil, fmt.Errorf("bad format for device path: %s", val)
+	}
+	rate, err := units.RAMInBytes(split[1])
+	if err != nil {
+		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
+	}
+	if rate < 0 {
+		return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
+	}
+
+	return &blkiodev.ThrottleDevice{
+		Path: split[0],
+		Rate: uint64(rate),
+	}, nil
+}
+
 // ValidateLink validates that the specified string has a valid link format (containerName:alias).
 func ValidateLink(val string) (string, error) {
 	if _, _, err := parsers.ParseLink(val); err != nil {

+ 56 - 0
opts/throttledevice.go

@@ -0,0 +1,56 @@
+package opts
+
+import (
+	"fmt"
+
+	"github.com/docker/docker/pkg/blkiodev"
+)
+
+// ThrottledeviceOpt defines a map of ThrottleDevices
+type ThrottledeviceOpt struct {
+	values    []*blkiodev.ThrottleDevice
+	validator ValidatorThrottleFctType
+}
+
+// NewThrottledeviceOpt creates a new ThrottledeviceOpt
+func NewThrottledeviceOpt(validator ValidatorThrottleFctType) ThrottledeviceOpt {
+	values := []*blkiodev.ThrottleDevice{}
+	return ThrottledeviceOpt{
+		values:    values,
+		validator: validator,
+	}
+}
+
+// Set validates a ThrottleDevice and sets its name as a key in ThrottledeviceOpt
+func (opt *ThrottledeviceOpt) Set(val string) error {
+	var value *blkiodev.ThrottleDevice
+	if opt.validator != nil {
+		v, err := opt.validator(val)
+		if err != nil {
+			return err
+		}
+		value = v
+	}
+	(opt.values) = append((opt.values), value)
+	return nil
+}
+
+// String returns ThrottledeviceOpt values as a string.
+func (opt *ThrottledeviceOpt) String() string {
+	var out []string
+	for _, v := range opt.values {
+		out = append(out, v.String())
+	}
+
+	return fmt.Sprintf("%v", out)
+}
+
+// GetList returns a slice of pointers to ThrottleDevices.
+func (opt *ThrottledeviceOpt) GetList() []*blkiodev.ThrottleDevice {
+	var throttledevice []*blkiodev.ThrottleDevice
+	for _, v := range opt.values {
+		throttledevice = append(throttledevice, v)
+	}
+
+	return throttledevice
+}

+ 10 - 0
pkg/blkiodev/blkiodev.go

@@ -13,3 +13,13 @@ type WeightDevice struct {
 func (w *WeightDevice) String() string {
 	return fmt.Sprintf("%s:%d", w.Path, w.Weight)
 }
+
+// ThrottleDevice is a structure that hold device:rate_per_second pair
+type ThrottleDevice struct {
+	Path string
+	Rate uint64
+}
+
+func (t *ThrottleDevice) String() string {
+	return fmt.Sprintf("%s:%d", t.Path, t.Rate)
+}

+ 6 - 0
pkg/sysinfo/sysinfo.go

@@ -63,6 +63,12 @@ type cgroupBlkioInfo struct {
 
 	// Whether Block IO weight_device is supported or not
 	BlkioWeightDevice bool
+
+	// Whether Block IO read limit in bytes per second is supported or not
+	BlkioReadBpsDevice bool
+
+	// Whether Block IO write limit in bytes per second is supported or not
+	BlkioWriteBpsDevice bool
 }
 
 type cgroupCpusetInfo struct {

+ 14 - 2
pkg/sysinfo/sysinfo_linux.go

@@ -126,9 +126,21 @@ func checkCgroupBlkioInfo(quiet bool) cgroupBlkioInfo {
 	if !quiet && !weightDevice {
 		logrus.Warn("Your kernel does not support cgroup blkio weight_device")
 	}
+
+	readBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.read_bps_device")
+	if !quiet && !readBpsDevice {
+		logrus.Warn("Your kernel does not support cgroup blkio throttle.read_bps_device")
+	}
+
+	writeBpsDevice := cgroupEnabled(mountPoint, "blkio.throttle.write_bps_device")
+	if !quiet && !writeBpsDevice {
+		logrus.Warn("Your kernel does not support cgroup blkio throttle.write_bps_device")
+	}
 	return cgroupBlkioInfo{
-		BlkioWeight:       weight,
-		BlkioWeightDevice: weightDevice,
+		BlkioWeight:         weight,
+		BlkioWeightDevice:   weightDevice,
+		BlkioReadBpsDevice:  readBpsDevice,
+		BlkioWriteBpsDevice: writeBpsDevice,
 	}
 }
 

+ 16 - 14
runconfig/hostconfig.go

@@ -171,20 +171,22 @@ type Resources struct {
 	CPUShares int64 `json:"CpuShares"` // CPU shares (relative weight vs. other containers)
 
 	// Applicable to UNIX platforms
-	CgroupParent      string // Parent cgroup.
-	BlkioWeight       uint16 // Block IO weight (relative weight vs. other containers)
-	BlkioWeightDevice []*blkiodev.WeightDevice
-	CPUPeriod         int64            `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period
-	CPUQuota          int64            `json:"CpuQuota"`  // CPU CFS (Completely Fair Scheduler) quota
-	CpusetCpus        string           // CpusetCpus 0-2, 0,1
-	CpusetMems        string           // CpusetMems 0-2, 0,1
-	Devices           []DeviceMapping  // List of devices to map inside the container
-	KernelMemory      int64            // Kernel memory limit (in bytes)
-	Memory            int64            // Memory limit (in bytes)
-	MemoryReservation int64            // Memory soft limit (in bytes)
-	MemorySwap        int64            // Total memory usage (memory + swap); set `-1` to disable swap
-	MemorySwappiness  *int64           // Tuning container memory swappiness behaviour
-	Ulimits           []*ulimit.Ulimit // List of ulimits to be set in the container
+	CgroupParent        string // Parent cgroup.
+	BlkioWeight         uint16 // Block IO weight (relative weight vs. other containers)
+	BlkioWeightDevice   []*blkiodev.WeightDevice
+	BlkioDeviceReadBps  []*blkiodev.ThrottleDevice
+	BlkioDeviceWriteBps []*blkiodev.ThrottleDevice
+	CPUPeriod           int64            `json:"CpuPeriod"` // CPU CFS (Completely Fair Scheduler) period
+	CPUQuota            int64            `json:"CpuQuota"`  // CPU CFS (Completely Fair Scheduler) quota
+	CpusetCpus          string           // CpusetCpus 0-2, 0,1
+	CpusetMems          string           // CpusetMems 0-2, 0,1
+	Devices             []DeviceMapping  // List of devices to map inside the container
+	KernelMemory        int64            // Kernel memory limit (in bytes)
+	Memory              int64            // Memory limit (in bytes)
+	MemoryReservation   int64            // Memory soft limit (in bytes)
+	MemorySwap          int64            // Total memory usage (memory + swap); set `-1` to disable swap
+	MemorySwappiness    *int64           // Tuning container memory swappiness behaviour
+	Ulimits             []*ulimit.Ulimit // List of ulimits to be set in the container
 }
 
 // HostConfig the non-portable Config structure of a container.

+ 21 - 15
runconfig/parse.go

@@ -53,6 +53,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 		flVolumes           = opts.NewListOpts(nil)
 		flTmpfs             = opts.NewListOpts(nil)
 		flBlkioWeightDevice = opts.NewWeightdeviceOpt(opts.ValidateWeightDevice)
+		flDeviceReadBps     = opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice)
+		flDeviceWriteBps    = opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice)
 		flLinks             = opts.NewListOpts(opts.ValidateLink)
 		flEnv               = opts.NewListOpts(opts.ValidateEnv)
 		flLabels            = opts.NewListOpts(opts.ValidateEnv)
@@ -113,6 +115,8 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 
 	cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR")
 	cmd.Var(&flBlkioWeightDevice, []string{"-blkio-weight-device"}, "Block IO weight (relative device weight)")
+	cmd.Var(&flDeviceReadBps, []string{"-device-read-bps"}, "Limit read rate (bytes per second) from a device")
+	cmd.Var(&flDeviceWriteBps, []string{"-device-write-bps"}, "Limit write rate (bytes per second) to a device")
 	cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume")
 	cmd.Var(&flTmpfs, []string{"-tmpfs"}, "Mount a tmpfs directory")
 	cmd.Var(&flLinks, []string{"-link"}, "Add link to another container")
@@ -338,21 +342,23 @@ func Parse(cmd *flag.FlagSet, args []string) (*Config, *HostConfig, *flag.FlagSe
 	}
 
 	resources := Resources{
-		CgroupParent:      *flCgroupParent,
-		Memory:            flMemory,
-		MemoryReservation: MemoryReservation,
-		MemorySwap:        memorySwap,
-		MemorySwappiness:  flSwappiness,
-		KernelMemory:      KernelMemory,
-		CPUShares:         *flCPUShares,
-		CPUPeriod:         *flCPUPeriod,
-		CpusetCpus:        *flCpusetCpus,
-		CpusetMems:        *flCpusetMems,
-		CPUQuota:          *flCPUQuota,
-		BlkioWeight:       *flBlkioWeight,
-		BlkioWeightDevice: flBlkioWeightDevice.GetList(),
-		Ulimits:           flUlimits.GetList(),
-		Devices:           deviceMappings,
+		CgroupParent:        *flCgroupParent,
+		Memory:              flMemory,
+		MemoryReservation:   MemoryReservation,
+		MemorySwap:          memorySwap,
+		MemorySwappiness:    flSwappiness,
+		KernelMemory:        KernelMemory,
+		CPUShares:           *flCPUShares,
+		CPUPeriod:           *flCPUPeriod,
+		CpusetCpus:          *flCpusetCpus,
+		CpusetMems:          *flCpusetMems,
+		CPUQuota:            *flCPUQuota,
+		BlkioWeight:         *flBlkioWeight,
+		BlkioWeightDevice:   flBlkioWeightDevice.GetList(),
+		BlkioDeviceReadBps:  flDeviceReadBps.GetList(),
+		BlkioDeviceWriteBps: flDeviceWriteBps.GetList(),
+		Ulimits:             flUlimits.GetList(),
+		Devices:             deviceMappings,
 	}
 
 	config := &Config{