mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
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)
|
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
|
// TODO: this can be removed after lxc-conf is fully deprecated
|
||||||
mergeLxcConfIntoOptions(c.hostConfig, context)
|
mergeLxcConfIntoOptions(c.hostConfig, context)
|
||||||
|
|
||||||
|
@ -231,8 +245,8 @@ func populateCommand(c *Container, env []string) error {
|
||||||
User: c.Config.User,
|
User: c.Config.User,
|
||||||
Config: context,
|
Config: context,
|
||||||
Resources: resources,
|
Resources: resources,
|
||||||
AllowedDevices: devices.DefaultAllowedDevices,
|
AllowedDevices: allowedDevices,
|
||||||
AutoCreatedDevices: devices.DefaultAutoCreatedDevices,
|
AutoCreatedDevices: autoCreatedDevices,
|
||||||
}
|
}
|
||||||
c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
|
||||||
c.command.Env = env
|
c.command.Env = env
|
||||||
|
|
|
@ -946,6 +946,7 @@ removed before the image is removed.
|
||||||
-u, --user="" Username or UID
|
-u, --user="" Username or UID
|
||||||
-v, --volume=[] Bind mount a volume (e.g., from the host: -v /host:/container, from docker: -v /container)
|
-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)
|
--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
|
-w, --workdir="" Working directory inside the container
|
||||||
|
|
||||||
The `docker run` command first `creates` a writeable container layer over the
|
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
|
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.
|
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:**
|
**A complete example:**
|
||||||
|
|
||||||
$ sudo docker run -d --name static static-web-files sh
|
$ sudo docker run -d --name static static-web-files sh
|
||||||
|
|
|
@ -919,6 +919,22 @@ func TestRunUnprivilegedWithChroot(t *testing.T) {
|
||||||
logDone("run - unprivileged with chroot")
|
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) {
|
func TestModeHostname(t *testing.T) {
|
||||||
cmd := exec.Command(dockerBinary, "run", "-h=testhostname", "busybox", "cat", "/etc/hostname")
|
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"
|
return len(parts) > 1 && parts[0] == "container"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type DeviceMapping struct {
|
||||||
|
PathOnHost string
|
||||||
|
PathInContainer string
|
||||||
|
CgroupPermissions string
|
||||||
|
}
|
||||||
|
|
||||||
type HostConfig struct {
|
type HostConfig struct {
|
||||||
Binds []string
|
Binds []string
|
||||||
ContainerIDFile string
|
ContainerIDFile string
|
||||||
|
@ -30,6 +36,7 @@ type HostConfig struct {
|
||||||
Dns []string
|
Dns []string
|
||||||
DnsSearch []string
|
DnsSearch []string
|
||||||
VolumesFrom []string
|
VolumesFrom []string
|
||||||
|
Devices []DeviceMapping
|
||||||
NetworkMode NetworkMode
|
NetworkMode NetworkMode
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +49,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
|
||||||
}
|
}
|
||||||
job.GetenvJson("LxcConf", &hostConfig.LxcConf)
|
job.GetenvJson("LxcConf", &hostConfig.LxcConf)
|
||||||
job.GetenvJson("PortBindings", &hostConfig.PortBindings)
|
job.GetenvJson("PortBindings", &hostConfig.PortBindings)
|
||||||
|
job.GetenvJson("Devices", &hostConfig.Devices)
|
||||||
if Binds := job.GetenvList("Binds"); Binds != nil {
|
if Binds := job.GetenvList("Binds"); Binds != nil {
|
||||||
hostConfig.Binds = Binds
|
hostConfig.Binds = Binds
|
||||||
}
|
}
|
||||||
|
|
|
@ -41,6 +41,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
|
||||||
flVolumes = opts.NewListOpts(opts.ValidatePath)
|
flVolumes = opts.NewListOpts(opts.ValidatePath)
|
||||||
flLinks = opts.NewListOpts(opts.ValidateLink)
|
flLinks = opts.NewListOpts(opts.ValidateLink)
|
||||||
flEnv = opts.NewListOpts(opts.ValidateEnv)
|
flEnv = opts.NewListOpts(opts.ValidateEnv)
|
||||||
|
flDevices = opts.NewListOpts(opts.ValidatePath)
|
||||||
|
|
||||||
flPublish opts.ListOpts
|
flPublish opts.ListOpts
|
||||||
flExpose 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(&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(&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(&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(&flEnv, []string{"e", "-env"}, "Set environment variables")
|
||||||
cmd.Var(&flEnvFile, []string{"-env-file"}, "Read in a line delimited file of 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
|
// collect all the environment variables for the container
|
||||||
envVariables := []string{}
|
envVariables := []string{}
|
||||||
for _, ef := range flEnvFile.GetAll() {
|
for _, ef := range flEnvFile.GetAll() {
|
||||||
|
@ -245,6 +257,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
|
||||||
DnsSearch: flDnsSearch.GetAll(),
|
DnsSearch: flDnsSearch.GetAll(),
|
||||||
VolumesFrom: flVolumesFrom.GetAll(),
|
VolumesFrom: flVolumesFrom.GetAll(),
|
||||||
NetworkMode: netMode,
|
NetworkMode: netMode,
|
||||||
|
Devices: deviceMappings,
|
||||||
}
|
}
|
||||||
|
|
||||||
if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
|
if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
|
||||||
|
@ -303,3 +316,33 @@ func parseNetMode(netMode string) (NetworkMode, error) {
|
||||||
}
|
}
|
||||||
return NetworkMode(netMode), nil
|
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…
Add table
Reference in a new issue