Add --device flag to allow additional host devices in container
We add a --device flag which can be used like: docker run --device /dev/sda:/dev/xvda:rwm ubuntu /bin/bash To allow the container to have read write permissions to access the host's /dev/sda via a node named /dev/xvda in the container. Note: Much of this code was written by Dinesh Subhraveti dineshs@altiscale.com (github: dineshs-altiscale) and so he deserves a ton of credit. Docker-DCO-1.1-Signed-off-by: Timothy <timothyhobbs@seznam.cz> (github: timthelion)
This commit is contained in:
parent
840ed5ace2
commit
e855c4b921
5 changed files with 98 additions and 2 deletions
|
@ -209,6 +209,20 @@ func populateCommand(c *Container, env []string) error {
|
|||
return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode)
|
||||
}
|
||||
|
||||
// Build lists of devices allowed and created within the container.
|
||||
userSpecifiedDevices := make([]*devices.Device, len(c.hostConfig.Devices))
|
||||
for i, deviceMapping := range c.hostConfig.Devices {
|
||||
device, err := devices.GetDevice(deviceMapping.PathOnHost, deviceMapping.CgroupPermissions)
|
||||
device.Path = deviceMapping.PathInContainer
|
||||
if err != nil {
|
||||
return fmt.Errorf("error gathering device information while adding custom device %s", err)
|
||||
}
|
||||
userSpecifiedDevices[i] = device
|
||||
}
|
||||
allowedDevices := append(devices.DefaultAllowedDevices, userSpecifiedDevices...)
|
||||
|
||||
autoCreatedDevices := append(devices.DefaultAutoCreatedDevices, userSpecifiedDevices...)
|
||||
|
||||
// TODO: this can be removed after lxc-conf is fully deprecated
|
||||
mergeLxcConfIntoOptions(c.hostConfig, context)
|
||||
|
||||
|
@ -231,8 +245,8 @@ func populateCommand(c *Container, env []string) error {
|
|||
User: c.Config.User,
|
||||
Config: context,
|
||||
Resources: resources,
|
||||
AllowedDevices: devices.DefaultAllowedDevices,
|
||||
AutoCreatedDevices: devices.DefaultAutoCreatedDevices,
|
||||
AllowedDevices: allowedDevices,
|
||||
AutoCreatedDevices: autoCreatedDevices,
|
||||
}
|
||||
c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||
c.command.Env = env
|
||||
|
|
|
@ -946,6 +946,7 @@ removed before the image is removed.
|
|||
-u, --user="" Username or UID
|
||||
-v, --volume=[] Bind mount a volume (e.g., from the host: -v /host:/container, from docker: -v /container)
|
||||
--volumes-from=[] Mount volumes from the specified container(s)
|
||||
--device=[] Add a host device to the container (e.g. --device=/dev/sdc[:/dev/xvdc[:rwm]])
|
||||
-w, --workdir="" Working directory inside the container
|
||||
|
||||
The `docker run` command first `creates` a writeable container layer over the
|
||||
|
@ -1122,6 +1123,20 @@ logs could be retrieved using `docker logs`. This is
|
|||
useful if you need to pipe a file or something else into a container and
|
||||
retrieve the container's ID once the container has finished running.
|
||||
|
||||
$ sudo docker run --device=/dev/sdc:/dev/xvdc --device=/dev/sdd --device=/dev/zero:/dev/nulo -i -t ubuntu ls -l /dev/{xvdc,sdd,nulo}
|
||||
brw-rw---- 1 root disk 8, 2 Feb 9 16:05 /dev/xvdc
|
||||
brw-rw---- 1 root disk 8, 3 Feb 9 16:05 /dev/sdd
|
||||
crw-rw-rw- 1 root root 1, 5 Feb 9 16:05 /dev/nulo
|
||||
|
||||
It is often necessary to directly expose devices to a container. ``--device``
|
||||
option enables that. For example, a specific block storage device or loop
|
||||
device or audio device can be added to an otherwise unprivileged container
|
||||
(without the ``--privileged`` flag) and have the application directly access it.
|
||||
|
||||
** Security note: **
|
||||
|
||||
``--device`` cannot be safely used with ephemeral devices. Block devices that may be removed should not be added to untrusted containers with ``--device``!
|
||||
|
||||
**A complete example:**
|
||||
|
||||
$ sudo docker run -d --name static static-web-files sh
|
||||
|
|
|
@ -919,6 +919,22 @@ func TestRunUnprivilegedWithChroot(t *testing.T) {
|
|||
logDone("run - unprivileged with chroot")
|
||||
}
|
||||
|
||||
func TestAddingOptionalDevices(t *testing.T) {
|
||||
cmd := exec.Command(dockerBinary, "run", "--device", "/dev/zero:/dev/nulo", "busybox", "sh", "-c", "ls /dev/nulo")
|
||||
|
||||
out, _, err := runCommandWithOutput(cmd)
|
||||
if err != nil {
|
||||
t.Fatal(err, out)
|
||||
}
|
||||
|
||||
if actual := strings.Trim(out, "\r\n"); actual != "/dev/nulo" {
|
||||
t.Fatalf("expected output /dev/nulo, received %s", actual)
|
||||
}
|
||||
deleteAllContainers()
|
||||
|
||||
logDone("run - test --device argument")
|
||||
}
|
||||
|
||||
func TestModeHostname(t *testing.T) {
|
||||
cmd := exec.Command(dockerBinary, "run", "-h=testhostname", "busybox", "cat", "/etc/hostname")
|
||||
|
||||
|
|
|
@ -19,6 +19,12 @@ func (n NetworkMode) IsContainer() bool {
|
|||
return len(parts) > 1 && parts[0] == "container"
|
||||
}
|
||||
|
||||
type DeviceMapping struct {
|
||||
PathOnHost string
|
||||
PathInContainer string
|
||||
CgroupPermissions string
|
||||
}
|
||||
|
||||
type HostConfig struct {
|
||||
Binds []string
|
||||
ContainerIDFile string
|
||||
|
@ -30,6 +36,7 @@ type HostConfig struct {
|
|||
Dns []string
|
||||
DnsSearch []string
|
||||
VolumesFrom []string
|
||||
Devices []DeviceMapping
|
||||
NetworkMode NetworkMode
|
||||
}
|
||||
|
||||
|
@ -42,6 +49,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
|
|||
}
|
||||
job.GetenvJson("LxcConf", &hostConfig.LxcConf)
|
||||
job.GetenvJson("PortBindings", &hostConfig.PortBindings)
|
||||
job.GetenvJson("Devices", &hostConfig.Devices)
|
||||
if Binds := job.GetenvList("Binds"); Binds != nil {
|
||||
hostConfig.Binds = Binds
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
|
|||
flVolumes = opts.NewListOpts(opts.ValidatePath)
|
||||
flLinks = opts.NewListOpts(opts.ValidateLink)
|
||||
flEnv = opts.NewListOpts(opts.ValidateEnv)
|
||||
flDevices = opts.NewListOpts(opts.ValidatePath)
|
||||
|
||||
flPublish opts.ListOpts
|
||||
flExpose opts.ListOpts
|
||||
|
@ -74,6 +75,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
|
|||
cmd.Var(&flAttach, []string{"a", "-attach"}, "Attach to STDIN, STDOUT or STDERR.")
|
||||
cmd.Var(&flVolumes, []string{"v", "-volume"}, "Bind mount a volume (e.g., from the host: -v /host:/container, from Docker: -v /container)")
|
||||
cmd.Var(&flLinks, []string{"#link", "-link"}, "Add link to another container in the form of name:alias")
|
||||
cmd.Var(&flDevices, []string{"-device"}, "Add a host device to the container (e.g. --device=/dev/sdc:/dev/xvdc)")
|
||||
cmd.Var(&flEnv, []string{"e", "-env"}, "Set environment variables")
|
||||
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a line delimited file of environment variables")
|
||||
|
||||
|
@ -191,6 +193,16 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
|
|||
}
|
||||
}
|
||||
|
||||
// parse device mappings
|
||||
deviceMappings := []DeviceMapping{}
|
||||
for _, device := range flDevices.GetAll() {
|
||||
deviceMapping, err := ParseDevice(device)
|
||||
if err != nil {
|
||||
return nil, nil, cmd, err
|
||||
}
|
||||
deviceMappings = append(deviceMappings, deviceMapping)
|
||||
}
|
||||
|
||||
// collect all the environment variables for the container
|
||||
envVariables := []string{}
|
||||
for _, ef := range flEnvFile.GetAll() {
|
||||
|
@ -245,6 +257,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
|
|||
DnsSearch: flDnsSearch.GetAll(),
|
||||
VolumesFrom: flVolumesFrom.GetAll(),
|
||||
NetworkMode: netMode,
|
||||
Devices: deviceMappings,
|
||||
}
|
||||
|
||||
if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
|
||||
|
@ -303,3 +316,33 @@ func parseNetMode(netMode string) (NetworkMode, error) {
|
|||
}
|
||||
return NetworkMode(netMode), nil
|
||||
}
|
||||
|
||||
func ParseDevice(device string) (DeviceMapping, error) {
|
||||
src := ""
|
||||
dst := ""
|
||||
permissions := "rwm"
|
||||
arr := strings.Split(device, ":")
|
||||
switch len(arr) {
|
||||
case 3:
|
||||
permissions = arr[2]
|
||||
fallthrough
|
||||
case 2:
|
||||
dst = arr[1]
|
||||
fallthrough
|
||||
case 1:
|
||||
src = arr[0]
|
||||
default:
|
||||
return DeviceMapping{}, fmt.Errorf("Invalid device specification: %s", device)
|
||||
}
|
||||
|
||||
if dst == "" {
|
||||
dst = src
|
||||
}
|
||||
|
||||
deviceMapping := DeviceMapping{
|
||||
PathOnHost: src,
|
||||
PathInContainer: dst,
|
||||
CgroupPermissions: permissions,
|
||||
}
|
||||
return deviceMapping, nil
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue