This introduce a new `--device-cgroup-rule` flag that allow a user to
add one or more entry to the container cgroup device `devices.allow`
Signed-off-by: Kenfe-Mickael Laventure <mickael.laventure@gmail.com>
| ... | ... |
@@ -251,6 +251,7 @@ type Resources struct {
|
| 251 | 251 |
CpusetCpus string // CpusetCpus 0-2, 0,1 |
| 252 | 252 |
CpusetMems string // CpusetMems 0-2, 0,1 |
| 253 | 253 |
Devices []DeviceMapping // List of devices to map inside the container |
| 254 |
+ DeviceCgroupRules []string // List of rule to be added to the device cgroup |
|
| 254 | 255 |
DiskQuota int64 // Disk limit (in bytes) |
| 255 | 256 |
KernelMemory int64 // Kernel memory limit (in bytes) |
| 256 | 257 |
MemoryReservation int64 // Memory soft limit (in bytes) |
| ... | ... |
@@ -6,6 +6,7 @@ import ( |
| 6 | 6 |
"fmt" |
| 7 | 7 |
"io/ioutil" |
| 8 | 8 |
"path" |
| 9 |
+ "regexp" |
|
| 9 | 10 |
"strconv" |
| 10 | 11 |
"strings" |
| 11 | 12 |
"time" |
| ... | ... |
@@ -21,6 +22,10 @@ import ( |
| 21 | 21 |
"github.com/spf13/pflag" |
| 22 | 22 |
) |
| 23 | 23 |
|
| 24 |
+var ( |
|
| 25 |
+ deviceCgroupRuleRegexp = regexp.MustCompile("^[acb] ([0-9]+|\\*):([0-9]+|\\*) [rwm]{1,3}$")
|
|
| 26 |
+) |
|
| 27 |
+ |
|
| 24 | 28 |
// containerOptions is a data object with all the options for creating a container |
| 25 | 29 |
type containerOptions struct {
|
| 26 | 30 |
attach opts.ListOpts |
| ... | ... |
@@ -36,6 +41,7 @@ type containerOptions struct {
|
| 36 | 36 |
deviceWriteIOps opts.ThrottledeviceOpt |
| 37 | 37 |
env opts.ListOpts |
| 38 | 38 |
labels opts.ListOpts |
| 39 |
+ deviceCgroupRules opts.ListOpts |
|
| 39 | 40 |
devices opts.ListOpts |
| 40 | 41 |
ulimits *opts.UlimitOpt |
| 41 | 42 |
sysctls *opts.MapOpts |
| ... | ... |
@@ -127,6 +133,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
| 127 | 127 |
dns: opts.NewListOpts(opts.ValidateIPAddress), |
| 128 | 128 |
dnsOptions: opts.NewListOpts(nil), |
| 129 | 129 |
dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch), |
| 130 |
+ deviceCgroupRules: opts.NewListOpts(validateDeviceCgroupRule), |
|
| 130 | 131 |
deviceReadBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), |
| 131 | 132 |
deviceReadIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice), |
| 132 | 133 |
deviceWriteBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice), |
| ... | ... |
@@ -154,6 +161,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
|
| 154 | 154 |
|
| 155 | 155 |
// General purpose flags |
| 156 | 156 |
flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR") |
| 157 |
+ flags.Var(&copts.deviceCgroupRules, "device-cgroup-rule", "Add a rule to the cgroup allowed devices list") |
|
| 157 | 158 |
flags.Var(&copts.devices, "device", "Add a host device to the container") |
| 158 | 159 |
flags.VarP(&copts.env, "env", "e", "Set environment variables") |
| 159 | 160 |
flags.Var(&copts.envFile, "env-file", "Read in a file of environment variables") |
| ... | ... |
@@ -548,6 +556,7 @@ func parse(flags *pflag.FlagSet, copts *containerOptions) (*container.Config, *c |
| 548 | 548 |
IOMaximumIOps: copts.ioMaxIOps, |
| 549 | 549 |
IOMaximumBandwidth: uint64(maxIOBandwidth), |
| 550 | 550 |
Ulimits: copts.ulimits.GetList(), |
| 551 |
+ DeviceCgroupRules: copts.deviceCgroupRules.GetAll(), |
|
| 551 | 552 |
Devices: deviceMappings, |
| 552 | 553 |
} |
| 553 | 554 |
|
| ... | ... |
@@ -762,6 +771,17 @@ func parseDevice(device string) (container.DeviceMapping, error) {
|
| 762 | 762 |
return deviceMapping, nil |
| 763 | 763 |
} |
| 764 | 764 |
|
| 765 |
+// validateDeviceCgroupRule validates a device cgroup rule string format |
|
| 766 |
+// It will make sure 'val' is in the form: |
|
| 767 |
+// 'type major:minor mode' |
|
| 768 |
+func validateDeviceCgroupRule(val string) (string, error) {
|
|
| 769 |
+ if deviceCgroupRuleRegexp.MatchString(val) {
|
|
| 770 |
+ return val, nil |
|
| 771 |
+ } |
|
| 772 |
+ |
|
| 773 |
+ return val, fmt.Errorf("invalid device cgroup format '%s'", val)
|
|
| 774 |
+} |
|
| 775 |
+ |
|
| 765 | 776 |
// validDeviceMode checks if the mode for device is valid or not. |
| 766 | 777 |
// Valid mode is a composition of r (read), w (write), and m (mknod). |
| 767 | 778 |
func validDeviceMode(mode string) bool {
|
| ... | ... |
@@ -121,6 +121,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cap-drop -d |
| 121 | 121 |
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cidfile -d 'Write the container ID to the file' |
| 122 | 122 |
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cpuset -d 'CPUs in which to allow execution (0-3, 0,1)' |
| 123 | 123 |
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l device -d 'Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)' |
| 124 |
+complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l device-cgroup-rule -d 'Add a rule to the cgroup allowed devices list (e.g. --device-cgroup-rule="c 13:37 rwm")' |
|
| 124 | 125 |
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l dns -d 'Set custom DNS servers' |
| 125 | 126 |
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l dns-opt -d "Set custom DNS options (Use --dns-opt='' if you don't wish to set options)" |
| 126 | 127 |
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l dns-search -d "Set custom DNS search domains (Use --dns-search=. if you don't wish to set the search domain)" |
| ... | ... |
@@ -312,6 +313,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cidfile -d 'Wri |
| 312 | 312 |
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cpuset -d 'CPUs in which to allow execution (0-3, 0,1)' |
| 313 | 313 |
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -s d -l detach -d 'Detached mode: run the container in the background and print the new container ID' |
| 314 | 314 |
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l device -d 'Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)' |
| 315 |
+complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l device-cgroup-rule -d 'Add a rule to the cgroup allowed devices list (e.g. --device-cgroup-rule="c 13:37 rwm")' |
|
| 315 | 316 |
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns -d 'Set custom DNS servers' |
| 316 | 317 |
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns-opt -d "Set custom DNS options (Use --dns-opt='' if you don't wish to set options)" |
| 317 | 318 |
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns-search -d "Set custom DNS search domains (Use --dns-search=. if you don't wish to set the search domain)" |
| ... | ... |
@@ -546,6 +546,7 @@ __docker_container_subcommand() {
|
| 546 | 546 |
"($help)--cidfile=[Write the container ID to the file]:CID file:_files" |
| 547 | 547 |
"($help)--cpus=[Number of CPUs (default 0.000)]:cpus: " |
| 548 | 548 |
"($help)*--device=[Add a host device to the container]:device:_files" |
| 549 |
+ "($help)*--device-cgroup-rule=[Add a rule to the cgroup allowed devices list]:device:cgroup: " |
|
| 549 | 550 |
"($help)*--device-read-bps=[Limit the read rate (bytes per second) from a device]:device:IO rate: " |
| 550 | 551 |
"($help)*--device-read-iops=[Limit the read rate (IO per second) from a device]:device:IO rate: " |
| 551 | 552 |
"($help)*--device-write-bps=[Limit the write rate (bytes per second) to a device]:device:IO rate: " |
| ... | ... |
@@ -6,6 +6,7 @@ import ( |
| 6 | 6 |
"os" |
| 7 | 7 |
"os/exec" |
| 8 | 8 |
"path/filepath" |
| 9 |
+ "regexp" |
|
| 9 | 10 |
"sort" |
| 10 | 11 |
"strconv" |
| 11 | 12 |
"strings" |
| ... | ... |
@@ -27,6 +28,10 @@ import ( |
| 27 | 27 |
specs "github.com/opencontainers/runtime-spec/specs-go" |
| 28 | 28 |
) |
| 29 | 29 |
|
| 30 |
+var ( |
|
| 31 |
+ deviceCgroupRuleRegex = regexp.MustCompile("^([acb]) ([0-9]+|\\*):([0-9]+|\\*) ([rwm]{1,3})$")
|
|
| 32 |
+) |
|
| 33 |
+ |
|
| 30 | 34 |
func setResources(s *specs.Spec, r containertypes.Resources) error {
|
| 31 | 35 |
weightDevices, err := getBlkioWeightDevices(r) |
| 32 | 36 |
if err != nil {
|
| ... | ... |
@@ -106,6 +111,41 @@ func setDevices(s *specs.Spec, c *container.Container) error {
|
| 106 | 106 |
devs = append(devs, d...) |
| 107 | 107 |
devPermissions = append(devPermissions, dPermissions...) |
| 108 | 108 |
} |
| 109 |
+ |
|
| 110 |
+ for _, deviceCgroupRule := range c.HostConfig.DeviceCgroupRules {
|
|
| 111 |
+ ss := deviceCgroupRuleRegex.FindAllStringSubmatch(deviceCgroupRule, -1) |
|
| 112 |
+ if len(ss[0]) != 5 {
|
|
| 113 |
+ return fmt.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule)
|
|
| 114 |
+ } |
|
| 115 |
+ matches := ss[0] |
|
| 116 |
+ |
|
| 117 |
+ dPermissions := specs.DeviceCgroup{
|
|
| 118 |
+ Allow: true, |
|
| 119 |
+ Type: &matches[1], |
|
| 120 |
+ Access: &matches[4], |
|
| 121 |
+ } |
|
| 122 |
+ if matches[2] == "*" {
|
|
| 123 |
+ major := int64(-1) |
|
| 124 |
+ dPermissions.Major = &major |
|
| 125 |
+ } else {
|
|
| 126 |
+ major, err := strconv.ParseInt(matches[2], 10, 64) |
|
| 127 |
+ if err != nil {
|
|
| 128 |
+ return fmt.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule)
|
|
| 129 |
+ } |
|
| 130 |
+ dPermissions.Major = &major |
|
| 131 |
+ } |
|
| 132 |
+ if matches[3] == "*" {
|
|
| 133 |
+ minor := int64(-1) |
|
| 134 |
+ dPermissions.Minor = &minor |
|
| 135 |
+ } else {
|
|
| 136 |
+ minor, err := strconv.ParseInt(matches[3], 10, 64) |
|
| 137 |
+ if err != nil {
|
|
| 138 |
+ return fmt.Errorf("invalid minor value in device cgroup rule format: '%s'", deviceCgroupRule)
|
|
| 139 |
+ } |
|
| 140 |
+ dPermissions.Minor = &minor |
|
| 141 |
+ } |
|
| 142 |
+ devPermissions = append(devPermissions, dPermissions) |
|
| 143 |
+ } |
|
| 109 | 144 |
} |
| 110 | 145 |
|
| 111 | 146 |
s.Linux.Devices = append(s.Linux.Devices, devs...) |
| ... | ... |
@@ -44,6 +44,7 @@ Options: |
| 44 | 44 |
--cpuset-cpus string CPUs in which to allow execution (0-3, 0,1) |
| 45 | 45 |
--cpuset-mems string MEMs in which to allow execution (0-3, 0,1) |
| 46 | 46 |
--device value Add a host device to the container (default []) |
| 47 |
+ --device-cgroup-rule value Add a rule to the cgroup allowed devices list |
|
| 47 | 48 |
--device-read-bps value Limit read rate (bytes per second) from a device (default []) |
| 48 | 49 |
--device-read-iops value Limit read rate (IO per second) from a device (default []) |
| 49 | 50 |
--device-write-bps value Limit write rate (bytes per second) to a device (default []) |
| ... | ... |
@@ -48,6 +48,7 @@ Options: |
| 48 | 48 |
-d, --detach Run container in background and print container ID |
| 49 | 49 |
--detach-keys string Override the key sequence for detaching a container |
| 50 | 50 |
--device value Add a host device to the container (default []) |
| 51 |
+ --device-cgroup-rule value Add a rule to the cgroup allowed devices list |
|
| 51 | 52 |
--device-read-bps value Limit read rate (bytes per second) from a device (default []) |
| 52 | 53 |
--device-read-iops value Limit read rate (IO per second) from a device (default []) |
| 53 | 54 |
--device-write-bps value Limit write rate (bytes per second) to a device (default []) |
| ... | ... |
@@ -4444,3 +4444,17 @@ func (s *DockerSuite) TestRunHostnameInHostMode(c *check.C) {
|
| 4444 | 4444 |
out, _ := dockerCmd(c, "run", "--net=host", "--hostname=foobar", "busybox", "sh", "-c", `echo $HOSTNAME && hostname`) |
| 4445 | 4445 |
c.Assert(strings.TrimSpace(out), checker.Equals, expectedOutput) |
| 4446 | 4446 |
} |
| 4447 |
+ |
|
| 4448 |
+func (s *DockerSuite) TestRunAddDeviceCgroupRule(c *check.C) {
|
|
| 4449 |
+ testRequires(c, DaemonIsLinux) |
|
| 4450 |
+ |
|
| 4451 |
+ deviceRule := "c 7:128 rwm" |
|
| 4452 |
+ |
|
| 4453 |
+ out, _ := dockerCmd(c, "run", "--rm", "busybox", "cat", "/sys/fs/cgroup/devices/devices.list") |
|
| 4454 |
+ if strings.Contains(out, deviceRule) {
|
|
| 4455 |
+ c.Fatalf("%s shouldn't been in the device.list", deviceRule)
|
|
| 4456 |
+ } |
|
| 4457 |
+ |
|
| 4458 |
+ out, _ = dockerCmd(c, "run", "--rm", fmt.Sprintf("--device-cgroup-rule=%s", deviceRule), "busybox", "grep", deviceRule, "/sys/fs/cgroup/devices/devices.list")
|
|
| 4459 |
+ c.Assert(strings.TrimSpace(out), checker.Equals, deviceRule) |
|
| 4460 |
+} |
| ... | ... |
@@ -27,6 +27,7 @@ docker-run - Run a command in a new container |
| 27 | 27 |
[**-d**|**--detach**] |
| 28 | 28 |
[**--detach-keys**[=*[]*]] |
| 29 | 29 |
[**--device**[=*[]*]] |
| 30 |
+[**--device-cgroup-rule**[=*[]*]] |
|
| 30 | 31 |
[**--device-read-bps**[=*[]*]] |
| 31 | 32 |
[**--device-read-iops**[=*[]*]] |
| 32 | 33 |
[**--device-write-bps**[=*[]*]] |
| ... | ... |
@@ -246,6 +247,16 @@ See **config-json(5)** for documentation on using a configuration file. |
| 246 | 246 |
**--device**=[] |
| 247 | 247 |
Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm) |
| 248 | 248 |
|
| 249 |
+**--device-cgroup-rule**=[] |
|
| 250 |
+ Add a rule to the cgroup allowed devices list. |
|
| 251 |
+ |
|
| 252 |
+ The rule is expected to be in the format specified in the Linux kernel documentation (Documentation/cgroup-v1/devices.txt): |
|
| 253 |
+ - type: `a` (all), `c` (char) or `b` (block) |
|
| 254 |
+ - major and minor: either a number or `*` for all |
|
| 255 |
+ - permission: a composition of `r` (read), `w` (write) and `m` (mknod) |
|
| 256 |
+ |
|
| 257 |
+ Example: `c 1:3 mr`: allow for character device with major `1` and minor `3` to be created (`m`) and read (`r`) |
|
| 258 |
+ |
|
| 249 | 259 |
**--device-read-bps**=[] |
| 250 | 260 |
Limit read rate from a device (e.g. --device-read-bps=/dev/sda:1mb) |
| 251 | 261 |
|