Allow adding rules to cgroup devices.allow on container create/run

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>
This commit is contained in:
Kenfe-Mickael Laventure 2016-05-06 15:09:46 -07:00
parent fe5f49685d
commit 1756af6faf
10 changed files with 92 additions and 0 deletions

View file

@ -251,6 +251,7 @@ type Resources struct {
CpusetCpus string // CpusetCpus 0-2, 0,1
CpusetMems string // CpusetMems 0-2, 0,1
Devices []DeviceMapping // List of devices to map inside the container
DeviceCgroupRules []string // List of rule to be added to the device cgroup
DiskQuota int64 // Disk limit (in bytes)
KernelMemory int64 // Kernel memory limit (in bytes)
MemoryReservation int64 // Memory soft limit (in bytes)

View file

@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"path"
"regexp"
"strconv"
"strings"
"time"
@ -21,6 +22,10 @@ import (
"github.com/spf13/pflag"
)
var (
deviceCgroupRuleRegexp = regexp.MustCompile("^[acb] ([0-9]+|\\*):([0-9]+|\\*) [rwm]{1,3}$")
)
// containerOptions is a data object with all the options for creating a container
type containerOptions struct {
attach opts.ListOpts
@ -36,6 +41,7 @@ type containerOptions struct {
deviceWriteIOps opts.ThrottledeviceOpt
env opts.ListOpts
labels opts.ListOpts
deviceCgroupRules opts.ListOpts
devices opts.ListOpts
ulimits *opts.UlimitOpt
sysctls *opts.MapOpts
@ -127,6 +133,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
dns: opts.NewListOpts(opts.ValidateIPAddress),
dnsOptions: opts.NewListOpts(nil),
dnsSearch: opts.NewListOpts(opts.ValidateDNSSearch),
deviceCgroupRules: opts.NewListOpts(validateDeviceCgroupRule),
deviceReadBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
deviceReadIOps: opts.NewThrottledeviceOpt(opts.ValidateThrottleIOpsDevice),
deviceWriteBps: opts.NewThrottledeviceOpt(opts.ValidateThrottleBpsDevice),
@ -154,6 +161,7 @@ func addFlags(flags *pflag.FlagSet) *containerOptions {
// General purpose flags
flags.VarP(&copts.attach, "attach", "a", "Attach to STDIN, STDOUT or STDERR")
flags.Var(&copts.deviceCgroupRules, "device-cgroup-rule", "Add a rule to the cgroup allowed devices list")
flags.Var(&copts.devices, "device", "Add a host device to the container")
flags.VarP(&copts.env, "env", "e", "Set environment variables")
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
IOMaximumIOps: copts.ioMaxIOps,
IOMaximumBandwidth: uint64(maxIOBandwidth),
Ulimits: copts.ulimits.GetList(),
DeviceCgroupRules: copts.deviceCgroupRules.GetAll(),
Devices: deviceMappings,
}
@ -762,6 +771,17 @@ func parseDevice(device string) (container.DeviceMapping, error) {
return deviceMapping, nil
}
// validateDeviceCgroupRule validates a device cgroup rule string format
// It will make sure 'val' is in the form:
// 'type major:minor mode'
func validateDeviceCgroupRule(val string) (string, error) {
if deviceCgroupRuleRegexp.MatchString(val) {
return val, nil
}
return val, fmt.Errorf("invalid device cgroup format '%s'", val)
}
// validDeviceMode checks if the mode for device is valid or not.
// Valid mode is a composition of r (read), w (write), and m (mknod).
func validDeviceMode(mode string) bool {

View file

@ -1346,6 +1346,7 @@ _docker_container_run() {
--cpuset-mems
--cpu-shares -c
--device
--device-cgroup-rule
--device-read-bps
--device-read-iops
--device-write-bps

View file

@ -121,6 +121,7 @@ complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cap-drop -d
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cidfile -d 'Write the container ID to the file'
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l cpuset -d 'CPUs in which to allow execution (0-3, 0,1)'
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)'
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")'
complete -c docker -A -f -n '__fish_seen_subcommand_from create' -l dns -d 'Set custom DNS servers'
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)"
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
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l cpuset -d 'CPUs in which to allow execution (0-3, 0,1)'
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'
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)'
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")'
complete -c docker -A -f -n '__fish_seen_subcommand_from run' -l dns -d 'Set custom DNS servers'
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)"
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)"

View file

@ -546,6 +546,7 @@ __docker_container_subcommand() {
"($help)--cidfile=[Write the container ID to the file]:CID file:_files"
"($help)--cpus=[Number of CPUs (default 0.000)]:cpus: "
"($help)*--device=[Add a host device to the container]:device:_files"
"($help)*--device-cgroup-rule=[Add a rule to the cgroup allowed devices list]:device:cgroup: "
"($help)*--device-read-bps=[Limit the read rate (bytes per second) from a device]:device:IO rate: "
"($help)*--device-read-iops=[Limit the read rate (IO 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: "

View file

@ -6,6 +6,7 @@ import (
"os"
"os/exec"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
@ -27,6 +28,10 @@ import (
specs "github.com/opencontainers/runtime-spec/specs-go"
)
var (
deviceCgroupRuleRegex = regexp.MustCompile("^([acb]) ([0-9]+|\\*):([0-9]+|\\*) ([rwm]{1,3})$")
)
func setResources(s *specs.Spec, r containertypes.Resources) error {
weightDevices, err := getBlkioWeightDevices(r)
if err != nil {
@ -106,6 +111,41 @@ func setDevices(s *specs.Spec, c *container.Container) error {
devs = append(devs, d...)
devPermissions = append(devPermissions, dPermissions...)
}
for _, deviceCgroupRule := range c.HostConfig.DeviceCgroupRules {
ss := deviceCgroupRuleRegex.FindAllStringSubmatch(deviceCgroupRule, -1)
if len(ss[0]) != 5 {
return fmt.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule)
}
matches := ss[0]
dPermissions := specs.DeviceCgroup{
Allow: true,
Type: &matches[1],
Access: &matches[4],
}
if matches[2] == "*" {
major := int64(-1)
dPermissions.Major = &major
} else {
major, err := strconv.ParseInt(matches[2], 10, 64)
if err != nil {
return fmt.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule)
}
dPermissions.Major = &major
}
if matches[3] == "*" {
minor := int64(-1)
dPermissions.Minor = &minor
} else {
minor, err := strconv.ParseInt(matches[3], 10, 64)
if err != nil {
return fmt.Errorf("invalid minor value in device cgroup rule format: '%s'", deviceCgroupRule)
}
dPermissions.Minor = &minor
}
devPermissions = append(devPermissions, dPermissions)
}
}
s.Linux.Devices = append(s.Linux.Devices, devs...)

View file

@ -44,6 +44,7 @@ Options:
--cpuset-cpus string CPUs in which to allow execution (0-3, 0,1)
--cpuset-mems string MEMs in which to allow execution (0-3, 0,1)
--device value Add a host device to the container (default [])
--device-cgroup-rule value Add a rule to the cgroup allowed devices list
--device-read-bps value Limit read rate (bytes per second) from a device (default [])
--device-read-iops value Limit read rate (IO per second) from a device (default [])
--device-write-bps value Limit write rate (bytes per second) to a device (default [])

View file

@ -48,6 +48,7 @@ Options:
-d, --detach Run container in background and print container ID
--detach-keys string Override the key sequence for detaching a container
--device value Add a host device to the container (default [])
--device-cgroup-rule value Add a rule to the cgroup allowed devices list
--device-read-bps value Limit read rate (bytes per second) from a device (default [])
--device-read-iops value Limit read rate (IO per second) from a device (default [])
--device-write-bps value Limit write rate (bytes per second) to a device (default [])

View file

@ -4444,3 +4444,17 @@ func (s *DockerSuite) TestRunHostnameInHostMode(c *check.C) {
out, _ := dockerCmd(c, "run", "--net=host", "--hostname=foobar", "busybox", "sh", "-c", `echo $HOSTNAME && hostname`)
c.Assert(strings.TrimSpace(out), checker.Equals, expectedOutput)
}
func (s *DockerSuite) TestRunAddDeviceCgroupRule(c *check.C) {
testRequires(c, DaemonIsLinux)
deviceRule := "c 7:128 rwm"
out, _ := dockerCmd(c, "run", "--rm", "busybox", "cat", "/sys/fs/cgroup/devices/devices.list")
if strings.Contains(out, deviceRule) {
c.Fatalf("%s shouldn't been in the device.list", deviceRule)
}
out, _ = dockerCmd(c, "run", "--rm", fmt.Sprintf("--device-cgroup-rule=%s", deviceRule), "busybox", "grep", deviceRule, "/sys/fs/cgroup/devices/devices.list")
c.Assert(strings.TrimSpace(out), checker.Equals, deviceRule)
}

View file

@ -27,6 +27,7 @@ docker-run - Run a command in a new container
[**-d**|**--detach**]
[**--detach-keys**[=*[]*]]
[**--device**[=*[]*]]
[**--device-cgroup-rule**[=*[]*]]
[**--device-read-bps**[=*[]*]]
[**--device-read-iops**[=*[]*]]
[**--device-write-bps**[=*[]*]]
@ -246,6 +247,16 @@ See **config-json(5)** for documentation on using a configuration file.
**--device**=[]
Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc:rwm)
**--device-cgroup-rule**=[]
Add a rule to the cgroup allowed devices list.
The rule is expected to be in the format specified in the Linux kernel documentation (Documentation/cgroup-v1/devices.txt):
- type: `a` (all), `c` (char) or `b` (block)
- major and minor: either a number or `*` for all
- permission: a composition of `r` (read), `w` (write) and `m` (mknod)
Example: `c 1:3 mr`: allow for character device with major `1` and minor `3` to be created (`m`) and read (`r`)
**--device-read-bps**=[]
Limit read rate from a device (e.g. --device-read-bps=/dev/sda:1mb)