Files
docker-cli/components/engine/plugin/v2/plugin_linux.go
Brian Goff c932e5d2a5 Use runtime spec modifier for metrics plugin hook
Currently the metrics plugin uses a really hackish host mount with
propagated mounts to get the metrics socket into a plugin after the
plugin is alreay running.
This approach ends up leaking mounts which requires setting the plugin
manager root to private, which causes some other issues.

With this change, plugin subsystems can register a set of modifiers to
apply to the plugin's runtime spec before the plugin is ever started.
This will help to generalize some of the customization work that needs
to happen for various plugin subsystems (and future ones).

Specifically it lets the metrics plugin subsystem append a mount to the
runtime spec to mount the metrics socket in the plugin's mount namespace
rather than the host's and prevetns any leaking due to this mount.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Upstream-commit: 426e610e43179d58b29c496bc79a53f410a4b1e1
Component: engine
2018-02-07 15:48:26 -05:00

136 lines
3.9 KiB
Go

package v2
import (
"os"
"path/filepath"
"runtime"
"strings"
"github.com/docker/docker/api/types"
"github.com/docker/docker/oci"
"github.com/docker/docker/pkg/system"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
)
// InitSpec creates an OCI spec from the plugin's config.
func (p *Plugin) InitSpec(execRoot string) (*specs.Spec, error) {
s := oci.DefaultSpec()
s.Root = &specs.Root{
Path: p.Rootfs,
Readonly: false, // TODO: all plugins should be readonly? settable in config?
}
userMounts := make(map[string]struct{}, len(p.PluginObj.Settings.Mounts))
for _, m := range p.PluginObj.Settings.Mounts {
userMounts[m.Destination] = struct{}{}
}
execRoot = filepath.Join(execRoot, p.PluginObj.ID)
if err := os.MkdirAll(execRoot, 0700); err != nil {
return nil, errors.WithStack(err)
}
mounts := append(p.PluginObj.Config.Mounts, types.PluginMount{
Source: &execRoot,
Destination: defaultPluginRuntimeDestination,
Type: "bind",
Options: []string{"rbind", "rshared"},
})
if p.PluginObj.Config.Network.Type != "" {
// TODO: if net == bridge, use libnetwork controller to create a new plugin-specific bridge, bind mount /etc/hosts and /etc/resolv.conf look at the docker code (allocateNetwork, initialize)
if p.PluginObj.Config.Network.Type == "host" {
oci.RemoveNamespace(&s, specs.LinuxNamespaceType("network"))
}
etcHosts := "/etc/hosts"
resolvConf := "/etc/resolv.conf"
mounts = append(mounts,
types.PluginMount{
Source: &etcHosts,
Destination: etcHosts,
Type: "bind",
Options: []string{"rbind", "ro"},
},
types.PluginMount{
Source: &resolvConf,
Destination: resolvConf,
Type: "bind",
Options: []string{"rbind", "ro"},
})
}
if p.PluginObj.Config.PidHost {
oci.RemoveNamespace(&s, specs.LinuxNamespaceType("pid"))
}
if p.PluginObj.Config.IpcHost {
oci.RemoveNamespace(&s, specs.LinuxNamespaceType("ipc"))
}
for _, mnt := range mounts {
m := specs.Mount{
Destination: mnt.Destination,
Type: mnt.Type,
Options: mnt.Options,
}
if mnt.Source == nil {
return nil, errors.New("mount source is not specified")
}
m.Source = *mnt.Source
s.Mounts = append(s.Mounts, m)
}
for i, m := range s.Mounts {
if strings.HasPrefix(m.Destination, "/dev/") {
if _, ok := userMounts[m.Destination]; ok {
s.Mounts = append(s.Mounts[:i], s.Mounts[i+1:]...)
}
}
}
if p.PluginObj.Config.PropagatedMount != "" {
p.PropagatedMount = filepath.Join(p.Rootfs, p.PluginObj.Config.PropagatedMount)
s.Linux.RootfsPropagation = "rshared"
}
if p.PluginObj.Config.Linux.AllowAllDevices {
s.Linux.Resources.Devices = []specs.LinuxDeviceCgroup{{Allow: true, Access: "rwm"}}
}
for _, dev := range p.PluginObj.Settings.Devices {
path := *dev.Path
d, dPermissions, err := oci.DevicesFromPath(path, path, "rwm")
if err != nil {
return nil, errors.WithStack(err)
}
s.Linux.Devices = append(s.Linux.Devices, d...)
s.Linux.Resources.Devices = append(s.Linux.Resources.Devices, dPermissions...)
}
envs := make([]string, 1, len(p.PluginObj.Settings.Env)+1)
envs[0] = "PATH=" + system.DefaultPathEnv(runtime.GOOS)
envs = append(envs, p.PluginObj.Settings.Env...)
args := append(p.PluginObj.Config.Entrypoint, p.PluginObj.Settings.Args...)
cwd := p.PluginObj.Config.WorkDir
if len(cwd) == 0 {
cwd = "/"
}
s.Process.Terminal = false
s.Process.Args = args
s.Process.Cwd = cwd
s.Process.Env = envs
caps := s.Process.Capabilities
caps.Bounding = append(caps.Bounding, p.PluginObj.Config.Linux.Capabilities...)
caps.Permitted = append(caps.Permitted, p.PluginObj.Config.Linux.Capabilities...)
caps.Inheritable = append(caps.Inheritable, p.PluginObj.Config.Linux.Capabilities...)
caps.Effective = append(caps.Effective, p.PluginObj.Config.Linux.Capabilities...)
if p.modifyRuntimeSpec != nil {
p.modifyRuntimeSpec(&s)
}
return &s, nil
}