Merge pull request #20662 from tonistiigi/containerd-integration

Containerd integration
This commit is contained in:
Jess Frazelle 2016-03-18 17:21:18 -07:00
commit e5a3f86e44
203 changed files with 11699 additions and 5488 deletions

View file

@ -249,6 +249,24 @@ RUN set -x \
&& go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \
&& rm -rf "$GOPATH"
# Install runc
ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
&& git checkout -q "$RUNC_COMMIT" \
&& make BUILDTAGS="seccomp apparmor selinux" && make install
# Install containerd
ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
&& cd "$GOPATH/src/github.com/docker/containerd" \
&& git checkout -q "$CONTAINERD_COMMIT" \
&& make && make install
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]

View file

@ -186,6 +186,24 @@ RUN set -x \
&& go build -v -o /usr/local/bin/tomlv github.com/BurntSushi/toml/cmd/tomlv \
&& rm -rf "$GOPATH"
# Install runc
ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
&& git checkout -q "$RUNC_COMMIT" \
&& make BUILDTAGS="seccomp apparmor selinux" && make install
# Install containerd
ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
&& cd "$GOPATH/src/github.com/docker/containerd" \
&& git checkout -q "$CONTAINERD_COMMIT" \
&& make && make install
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]

View file

@ -205,6 +205,24 @@ RUN set -x \
&& go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \
&& rm -rf "$GOPATH"
# Install runc
ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
&& git checkout -q "$RUNC_COMMIT" \
&& make BUILDTAGS="seccomp apparmor selinux" && make install
# Install containerd
ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
&& cd "$GOPATH/src/github.com/docker/containerd" \
&& git checkout -q "$CONTAINERD_COMMIT" \
&& make && make install
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]

View file

@ -73,6 +73,24 @@ VOLUME /var/lib/docker
WORKDIR /go/src/github.com/docker/docker
ENV DOCKER_BUILDTAGS apparmor seccomp selinux
# Install runc
ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
&& git checkout -q "$RUNC_COMMIT" \
&& make BUILDTAGS="seccomp apparmor selinux" && make install
# Install containerd
ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
&& cd "$GOPATH/src/github.com/docker/containerd" \
&& git checkout -q "$CONTAINERD_COMMIT" \
&& make && make install
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]

View file

@ -197,6 +197,24 @@ RUN set -x \
&& go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \
&& rm -rf "$GOPATH"
# Install runc
ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
&& git checkout -q "$RUNC_COMMIT" \
&& make BUILDTAGS="seccomp apparmor selinux" && make install
# Install containerd
ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
&& cd "$GOPATH/src/github.com/docker/containerd" \
&& git checkout -q "$CONTAINERD_COMMIT" \
&& make && make install
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]

View file

@ -176,6 +176,24 @@ RUN set -x \
&& go build -v -o /usr/local/bin/rsrc github.com/akavel/rsrc \
&& rm -rf "$GOPATH"
# Install runc
ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
&& git checkout -q "$RUNC_COMMIT" \
&& make BUILDTAGS="seccomp apparmor selinux" && make install
# Install containerd
ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
&& cd "$GOPATH/src/github.com/docker/containerd" \
&& git checkout -q "$CONTAINERD_COMMIT" \
&& make && make install
# Wrap all commands in the "docker-in-docker" script to allow nested containers
ENTRYPOINT ["hack/dind"]

View file

@ -29,6 +29,24 @@ RUN apt-get update && apt-get install -y --no-install-recommends \
aufs-tools \
&& rm -rf /var/lib/apt/lists/*
# Install runc
ENV RUNC_COMMIT bbde9c426ff363d813b8722f0744115c13b408b6
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/opencontainers/runc.git "$GOPATH/src/github.com/opencontainers/runc" \
&& cd "$GOPATH/src/github.com/opencontainers/runc" \
&& git checkout -q "$RUNC_COMMIT" \
&& make BUILDTAGS="seccomp apparmor selinux" && make install
# Install containerd
ENV CONTAINERD_COMMIT 7146b01a3d7aaa146414cdfb0a6c96cfba5d9091
RUN set -x \
&& export GOPATH="$(mktemp -d)" \
&& git clone git://github.com/docker/containerd.git "$GOPATH/src/github.com/docker/containerd" \
&& cd "$GOPATH/src/github.com/docker/containerd" \
&& git checkout -q "$CONTAINERD_COMMIT" \
&& make && make install
ENV AUTO_GOPATH 1
WORKDIR /usr/src/docker
COPY . /usr/src/docker

View file

@ -14,7 +14,6 @@ import (
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/stringid"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/engine-api/types"
"github.com/docker/libnetwork/resolvconf/dns"
@ -256,16 +255,6 @@ func (cli *DockerCli) CmdRun(args ...string) error {
// Attached mode
if *flAutoRemove {
// Warn user if they detached us
js, err := cli.client.ContainerInspect(context.Background(), createResponse.ID)
if err != nil {
return runStartContainerErr(err)
}
if js.State.Running == true || js.State.Paused == true {
fmt.Fprintf(cli.out, "Detached from %s, awaiting its termination in order to uphold \"--rm\".\n",
stringid.TruncateID(createResponse.ID))
}
// Autoremove: wait for the container to finish, retrieve
// the exit code and remove the container
if status, err = cli.client.ContainerWait(context.Background(), createResponse.ID); err != nil {

View file

@ -112,7 +112,9 @@ func (s *containerRouter) postContainerExecStart(ctx context.Context, w http.Res
if execStartCheck.Detach {
return err
}
stdout.Write([]byte(err.Error()))
logrus.Errorf("Error running exec in container: %v\n", err)
return err
}
return nil
}

View file

@ -17,7 +17,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/exec"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/daemon/logger/jsonfilelog"
"github.com/docker/docker/daemon/network"
@ -27,6 +26,7 @@ import (
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/signal"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/restartmanager"
"github.com/docker/docker/runconfig"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/docker/volume"
@ -74,13 +74,12 @@ type CommonContainer struct {
HasBeenManuallyStopped bool // used for unless-stopped restart policy
MountPoints map[string]*volume.MountPoint
HostConfig *containertypes.HostConfig `json:"-"` // do not serialize the host config in the json, otherwise we'll make the container unportable
Command *execdriver.Command `json:"-"`
monitor *containerMonitor
ExecCommands *exec.Store `json:"-"`
ExecCommands *exec.Store `json:"-"`
// logDriver for closing
LogDriver logger.Logger `json:"-"`
LogCopier *logger.Copier `json:"-"`
attachContext *attachContext
LogDriver logger.Logger `json:"-"`
LogCopier *logger.Copier `json:"-"`
restartManager restartmanager.RestartManager
attachContext *attachContext
}
// NewBaseContainer creates a new container with its
@ -276,19 +275,9 @@ func (container *Container) GetRootResourcePath(path string) (string, error) {
// ExitOnNext signals to the monitor that it should not restart the container
// after we send the kill signal.
func (container *Container) ExitOnNext() {
container.monitor.ExitOnNext()
}
// Resize changes the TTY of the process running inside the container
// to the given height and width. The container must be running.
func (container *Container) Resize(h, w int) error {
if container.Command.ProcessConfig.Terminal == nil {
return fmt.Errorf("Container %s does not have a terminal ready", container.ID)
if container.restartManager != nil {
container.restartManager.Cancel()
}
if err := container.Command.ProcessConfig.Terminal.Resize(h, w); err != nil {
return err
}
return nil
}
// HostConfigPath returns the path to the container's JSON hostconfig
@ -897,19 +886,33 @@ func (container *Container) BuildCreateEndpointOptions(n libnetwork.Network, epC
// UpdateMonitor updates monitor configure for running container
func (container *Container) UpdateMonitor(restartPolicy containertypes.RestartPolicy) {
monitor := container.monitor
// No need to update monitor if container hasn't got one
// monitor will be generated correctly according to container
if monitor == nil {
return
type policySetter interface {
SetPolicy(containertypes.RestartPolicy)
}
monitor.mux.Lock()
// to check whether restart policy has changed.
if restartPolicy.Name != "" && !monitor.restartPolicy.IsSame(&restartPolicy) {
monitor.restartPolicy = restartPolicy
if rm, ok := container.RestartManager(false).(policySetter); ok {
rm.SetPolicy(restartPolicy)
}
monitor.mux.Unlock()
}
// FullHostname returns hostname and optional domain appended to it.
func (container *Container) FullHostname() string {
fullHostname := container.Config.Hostname
if container.Config.Domainname != "" {
fullHostname = fmt.Sprintf("%s.%s", fullHostname, container.Config.Domainname)
}
return fullHostname
}
// RestartManager returns the current restartmanager instace connected to container.
func (container *Container) RestartManager(reset bool) restartmanager.RestartManager {
if reset {
container.RestartCount = 0
}
if container.restartManager == nil {
container.restartManager = restartmanager.New(container.HostConfig.RestartPolicy)
}
return container.restartManager
}
type attachContext struct {

View file

@ -11,7 +11,6 @@ import (
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/chrootarchive"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/pkg/system"
@ -39,6 +38,15 @@ type Container struct {
NoNewPrivileges bool
}
// ExitStatus provides exit reasons for a container.
type ExitStatus struct {
// The exit code with which the container exited.
ExitCode int
// Whether the container encountered an OOM.
OOMKilled bool
}
// CreateDaemonEnvironment returns the list of all environment variables given the list of
// environment variables related to links.
// Sets PATH, HOSTNAME and if container.Config.Tty is set: TERM.
@ -57,7 +65,6 @@ func (container *Container) CreateDaemonEnvironment(linkedEnv []string) []string
// we need to replace the 'env' keys where they match and append anything
// else.
env = utils.ReplaceOrAppendEnvValues(env, container.Config.Env)
return env
}
@ -103,8 +110,8 @@ func appendNetworkMounts(container *Container, volumeMounts []volume.MountPoint)
}
// NetworkMounts returns the list of network mounts.
func (container *Container) NetworkMounts() []execdriver.Mount {
var mounts []execdriver.Mount
func (container *Container) NetworkMounts() []Mount {
var mounts []Mount
shared := container.HostConfig.NetworkMode.IsContainer()
if container.ResolvConfPath != "" {
if _, err := os.Stat(container.ResolvConfPath); err != nil {
@ -115,7 +122,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount {
if m, exists := container.MountPoints["/etc/resolv.conf"]; exists {
writable = m.RW
}
mounts = append(mounts, execdriver.Mount{
mounts = append(mounts, Mount{
Source: container.ResolvConfPath,
Destination: "/etc/resolv.conf",
Writable: writable,
@ -132,7 +139,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount {
if m, exists := container.MountPoints["/etc/hostname"]; exists {
writable = m.RW
}
mounts = append(mounts, execdriver.Mount{
mounts = append(mounts, Mount{
Source: container.HostnamePath,
Destination: "/etc/hostname",
Writable: writable,
@ -149,7 +156,7 @@ func (container *Container) NetworkMounts() []execdriver.Mount {
if m, exists := container.MountPoints["/etc/hosts"]; exists {
writable = m.RW
}
mounts = append(mounts, execdriver.Mount{
mounts = append(mounts, Mount{
Source: container.HostsPath,
Destination: "/etc/hosts",
Writable: writable,
@ -224,37 +231,26 @@ func (container *Container) UnmountIpcMounts(unmount func(pth string) error) {
}
// IpcMounts returns the list of IPC mounts
func (container *Container) IpcMounts() []execdriver.Mount {
var mounts []execdriver.Mount
func (container *Container) IpcMounts() []Mount {
var mounts []Mount
if !container.HasMountFor("/dev/shm") {
label.SetFileLabel(container.ShmPath, container.MountLabel)
mounts = append(mounts, execdriver.Mount{
mounts = append(mounts, Mount{
Source: container.ShmPath,
Destination: "/dev/shm",
Writable: true,
Propagation: volume.DefaultPropagationMode,
})
}
return mounts
}
func updateCommand(c *execdriver.Command, resources containertypes.Resources) {
c.Resources.BlkioWeight = resources.BlkioWeight
c.Resources.CPUShares = resources.CPUShares
c.Resources.CPUPeriod = resources.CPUPeriod
c.Resources.CPUQuota = resources.CPUQuota
c.Resources.CpusetCpus = resources.CpusetCpus
c.Resources.CpusetMems = resources.CpusetMems
c.Resources.Memory = resources.Memory
c.Resources.MemorySwap = resources.MemorySwap
c.Resources.MemoryReservation = resources.MemoryReservation
c.Resources.KernelMemory = resources.KernelMemory
return mounts
}
// UpdateContainer updates configuration of a container.
func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfig) error {
container.Lock()
defer container.Unlock()
// update resources of container
resources := hostConfig.Resources
@ -294,19 +290,8 @@ func (container *Container) UpdateContainer(hostConfig *containertypes.HostConfi
if hostConfig.RestartPolicy.Name != "" {
container.HostConfig.RestartPolicy = hostConfig.RestartPolicy
}
container.Unlock()
// If container is not running, update hostConfig struct is enough,
// resources will be updated when the container is started again.
// If container is running (including paused), we need to update
// the command so we can update configs to the real world.
if container.IsRunning() {
container.Lock()
updateCommand(container.Command, *cResources)
container.Unlock()
}
if err := container.ToDiskLocking(); err != nil {
if err := container.ToDisk(); err != nil {
logrus.Errorf("Error saving updated container: %v", err)
return err
}
@ -400,10 +385,10 @@ func copyOwnership(source, destination string) error {
}
// TmpfsMounts returns the list of tmpfs mounts
func (container *Container) TmpfsMounts() []execdriver.Mount {
var mounts []execdriver.Mount
func (container *Container) TmpfsMounts() []Mount {
var mounts []Mount
for dest, data := range container.HostConfig.Tmpfs {
mounts = append(mounts, execdriver.Mount{
mounts = append(mounts, Mount{
Source: "tmpfs",
Destination: dest,
Data: data,

View file

@ -7,7 +7,6 @@ import (
"os"
"path/filepath"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/volume"
containertypes "github.com/docker/engine-api/types/container"
)
@ -23,6 +22,12 @@ type Container struct {
// Fields below here are platform specific.
}
// ExitStatus provides exit reasons for a container.
type ExitStatus struct {
// The exit code with which the container exited.
ExitCode int
}
// CreateDaemonEnvironment creates a new environment variable slice for this container.
func (container *Container) CreateDaemonEnvironment(linkedEnv []string) []string {
// On Windows, nothing to link. Just return the container environment.
@ -35,7 +40,7 @@ func (container *Container) UnmountIpcMounts(unmount func(pth string) error) {
}
// IpcMounts returns the list of Ipc related mounts.
func (container *Container) IpcMounts() []execdriver.Mount {
func (container *Container) IpcMounts() []Mount {
return nil
}
@ -45,7 +50,7 @@ func (container *Container) UnmountVolumes(forceSyscall bool, volumeEventLog fun
}
// TmpfsMounts returns the list of tmpfs mounts
func (container *Container) TmpfsMounts() []execdriver.Mount {
func (container *Container) TmpfsMounts() []Mount {
return nil
}

View file

@ -5,7 +5,7 @@ import "sync"
// memoryStore implements a Store in memory.
type memoryStore struct {
s map[string]*Container
sync.Mutex
sync.RWMutex
}
// NewMemoryStore initializes a new memory store.
@ -25,9 +25,9 @@ func (c *memoryStore) Add(id string, cont *Container) {
// Get returns a container from the store by id.
func (c *memoryStore) Get(id string) *Container {
c.Lock()
c.RLock()
res := c.s[id]
c.Unlock()
c.RUnlock()
return res
}
@ -42,26 +42,26 @@ func (c *memoryStore) Delete(id string) {
// The containers are ordered by creation date.
func (c *memoryStore) List() []*Container {
containers := new(History)
c.Lock()
c.RLock()
for _, cont := range c.s {
containers.Add(cont)
}
c.Unlock()
c.RUnlock()
containers.sort()
return *containers
}
// Size returns the number of containers in the store.
func (c *memoryStore) Size() int {
c.Lock()
defer c.Unlock()
c.RLock()
defer c.RUnlock()
return len(c.s)
}
// First returns the first container found in the store by a given filter.
func (c *memoryStore) First(filter StoreFilter) *Container {
c.Lock()
defer c.Unlock()
c.RLock()
defer c.RUnlock()
for _, cont := range c.s {
if filter(cont) {
return cont
@ -72,9 +72,10 @@ func (c *memoryStore) First(filter StoreFilter) *Container {
// ApplyAll calls the reducer function with every container in the store.
// This operation is asyncronous in the memory store.
// NOTE: Modifications to the store MUST NOT be done by the StoreReducer.
func (c *memoryStore) ApplyAll(apply StoreReducer) {
c.Lock()
defer c.Unlock()
c.RLock()
defer c.RUnlock()
wg := new(sync.WaitGroup)
for _, cont := range c.s {

View file

@ -1,24 +1,13 @@
package container
import (
"fmt"
"io"
"os/exec"
"strings"
"sync"
"syscall"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/engine-api/types/container"
)
const (
defaultTimeIncrement = 100
loggerCloseTimeout = 10 * time.Second
loggerCloseTimeout = 10 * time.Second
)
// supervisor defines the interface that a supervisor must implement
@ -30,311 +19,13 @@ type supervisor interface {
// StartLogging starts the logging driver for the container
StartLogging(*Container) error
// Run starts a container
Run(c *Container, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (execdriver.ExitStatus, error)
Run(c *Container) error
// IsShuttingDown tells whether the supervisor is shutting down or not
IsShuttingDown() bool
}
// containerMonitor monitors the execution of a container's main process.
// If a restart policy is specified for the container the monitor will ensure that the
// process is restarted based on the rules of the policy. When the container is finally stopped
// the monitor will reset and cleanup any of the container resources such as networking allocations
// and the rootfs
type containerMonitor struct {
mux sync.Mutex
// supervisor keeps track of the container and the events it generates
supervisor supervisor
// container is the container being monitored
container *Container
// restartPolicy is the current policy being applied to the container monitor
restartPolicy container.RestartPolicy
// failureCount is the number of times the container has failed to
// start in a row
failureCount int
// shouldStop signals the monitor that the next time the container exits it is
// either because docker or the user asked for the container to be stopped
shouldStop bool
// startSignal is a channel that is closes after the container initially starts
startSignal chan struct{}
// stopChan is used to signal to the monitor whenever there is a wait for the
// next restart so that the timeIncrement is not honored and the user is not
// left waiting for nothing to happen during this time
stopChan chan struct{}
// timeIncrement is the amount of time to wait between restarts
// this is in milliseconds
timeIncrement int
// lastStartTime is the time which the monitor last exec'd the container's process
lastStartTime time.Time
}
// StartMonitor initializes a containerMonitor for this container with the provided supervisor and restart policy
// and starts the container's process.
func (container *Container) StartMonitor(s supervisor) error {
container.monitor = &containerMonitor{
supervisor: s,
container: container,
restartPolicy: container.HostConfig.RestartPolicy,
timeIncrement: defaultTimeIncrement,
stopChan: make(chan struct{}),
startSignal: make(chan struct{}),
}
return container.monitor.wait()
}
// wait starts the container and wait until
// we either receive an error from the initial start of the container's
// process or until the process is running in the container
func (m *containerMonitor) wait() error {
select {
case <-m.startSignal:
case err := <-promise.Go(m.start):
return err
}
return nil
}
// Stop signals to the container monitor that it should stop monitoring the container
// for exits the next time the process dies
func (m *containerMonitor) ExitOnNext() {
m.mux.Lock()
// we need to protect having a double close of the channel when stop is called
// twice or else we will get a panic
if !m.shouldStop {
m.shouldStop = true
close(m.stopChan)
}
m.mux.Unlock()
}
// Close closes the container's resources such as networking allocations and
// unmounts the container's root filesystem
func (m *containerMonitor) Close() error {
// Cleanup networking and mounts
m.supervisor.Cleanup(m.container)
if err := m.container.ToDisk(); err != nil {
logrus.Errorf("Error dumping container %s state to disk: %s", m.container.ID, err)
return err
}
return nil
}
// Start starts the containers process and monitors it according to the restart policy
func (m *containerMonitor) start() error {
var (
err error
exitStatus execdriver.ExitStatus
// this variable indicates where we in execution flow:
// before Run or after
afterRun bool
)
// ensure that when the monitor finally exits we release the networking and unmount the rootfs
defer func() {
if afterRun {
m.container.Lock()
defer m.container.Unlock()
m.container.SetStopped(&exitStatus)
}
m.Close()
}()
// reset stopped flag
if m.container.HasBeenManuallyStopped {
m.container.HasBeenManuallyStopped = false
}
// reset the restart count
m.container.RestartCount = -1
for {
m.container.RestartCount++
if err := m.supervisor.StartLogging(m.container); err != nil {
m.resetContainer(false)
return err
}
pipes := execdriver.NewPipes(m.container.Stdin(), m.container.Stdout(), m.container.Stderr(), m.container.Config.OpenStdin)
m.logEvent("start")
m.lastStartTime = time.Now()
if exitStatus, err = m.supervisor.Run(m.container, pipes, m.callback); err != nil {
// if we receive an internal error from the initial start of a container then lets
// return it instead of entering the restart loop
// set to 127 for container cmd not found/does not exist)
if strings.Contains(err.Error(), "executable file not found") ||
strings.Contains(err.Error(), "no such file or directory") ||
strings.Contains(err.Error(), "system cannot find the file specified") {
if m.container.RestartCount == 0 {
m.container.ExitCode = 127
m.resetContainer(false)
return fmt.Errorf("Container command not found or does not exist.")
}
}
// set to 126 for container cmd can't be invoked errors
if strings.Contains(err.Error(), syscall.EACCES.Error()) {
if m.container.RestartCount == 0 {
m.container.ExitCode = 126
m.resetContainer(false)
return fmt.Errorf("Container command could not be invoked.")
}
}
if m.container.RestartCount == 0 {
m.container.ExitCode = -1
m.resetContainer(false)
return fmt.Errorf("Cannot start container %s: %v", m.container.ID, err)
}
logrus.Errorf("Error running container: %s", err)
}
// here container.Lock is already lost
afterRun = true
m.resetMonitor(err == nil && exitStatus.ExitCode == 0)
if m.shouldRestart(exitStatus.ExitCode) {
m.container.SetRestartingLocking(&exitStatus)
m.logEvent("die")
m.resetContainer(true)
// sleep with a small time increment between each restart to help avoid issues cased by quickly
// restarting the container because of some types of errors ( networking cut out, etc... )
m.waitForNextRestart()
// we need to check this before reentering the loop because the waitForNextRestart could have
// been terminated by a request from a user
if m.shouldStop {
return err
}
continue
}
m.logEvent("die")
m.resetContainer(true)
return err
}
}
// resetMonitor resets the stateful fields on the containerMonitor based on the
// previous runs success or failure. Regardless of success, if the container had
// an execution time of more than 10s then reset the timer back to the default
func (m *containerMonitor) resetMonitor(successful bool) {
executionTime := time.Now().Sub(m.lastStartTime).Seconds()
if executionTime > 10 {
m.timeIncrement = defaultTimeIncrement
} else {
// otherwise we need to increment the amount of time we wait before restarting
// the process. We will build up by multiplying the increment by 2
m.timeIncrement *= 2
}
// the container exited successfully so we need to reset the failure counter
if successful {
m.failureCount = 0
} else {
m.failureCount++
}
}
// waitForNextRestart waits with the default time increment to restart the container unless
// a user or docker asks for the container to be stopped
func (m *containerMonitor) waitForNextRestart() {
select {
case <-time.After(time.Duration(m.timeIncrement) * time.Millisecond):
case <-m.stopChan:
}
}
// shouldRestart checks the restart policy and applies the rules to determine if
// the container's process should be restarted
func (m *containerMonitor) shouldRestart(exitCode int) bool {
m.mux.Lock()
defer m.mux.Unlock()
// do not restart if the user or docker has requested that this container be stopped
if m.shouldStop {
m.container.HasBeenManuallyStopped = !m.supervisor.IsShuttingDown()
return false
}
switch {
case m.restartPolicy.IsAlways(), m.restartPolicy.IsUnlessStopped():
return true
case m.restartPolicy.IsOnFailure():
// the default value of 0 for MaximumRetryCount means that we will not enforce a maximum count
if max := m.restartPolicy.MaximumRetryCount; max != 0 && m.failureCount > max {
logrus.Debugf("stopping restart of container %s because maximum failure could of %d has been reached",
stringid.TruncateID(m.container.ID), max)
return false
}
return exitCode != 0
}
return false
}
// callback ensures that the container's state is properly updated after we
// received ack from the execution drivers
func (m *containerMonitor) callback(processConfig *execdriver.ProcessConfig, pid int, chOOM <-chan struct{}) error {
go func() {
for range chOOM {
m.logEvent("oom")
}
}()
if processConfig.Tty {
// The callback is called after the process start()
// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave
// which we close here.
if c, ok := processConfig.Stdout.(io.Closer); ok {
c.Close()
}
}
m.container.SetRunning(pid)
// signal that the process has started
// close channel only if not closed
select {
case <-m.startSignal:
default:
close(m.startSignal)
}
if err := m.container.ToDiskLocking(); err != nil {
logrus.Errorf("Error saving container to disk: %v", err)
}
return nil
}
// resetContainer resets the container's IO and ensures that the command is able to be executed again
// by copying the data into a new struct
// if lock is true, then container locked during reset
func (m *containerMonitor) resetContainer(lock bool) {
container := m.container
// Reset puts a container into a state where it can be restarted again.
func (container *Container) Reset(lock bool) {
if lock {
container.Lock()
defer container.Unlock()
@ -344,12 +35,6 @@ func (m *containerMonitor) resetContainer(lock bool) {
logrus.Errorf("%s: %s", container.ID, err)
}
if container.Command != nil && container.Command.ProcessConfig.Terminal != nil {
if err := container.Command.ProcessConfig.Terminal.Close(); err != nil {
logrus.Errorf("%s: Error closing terminal: %s", container.ID, err)
}
}
// Re-create a brand new stdin pipe once the container exited
if container.Config.OpenStdin {
container.NewInputPipes()
@ -365,9 +50,6 @@ func (m *containerMonitor) resetContainer(lock bool) {
select {
case <-time.After(loggerCloseTimeout):
logrus.Warnf("Logger didn't exit in time: logs may be truncated")
container.LogCopier.Close()
// always waits for the LogCopier to finished before closing
<-exit
case <-exit:
}
}
@ -375,22 +57,4 @@ func (m *containerMonitor) resetContainer(lock bool) {
container.LogCopier = nil
container.LogDriver = nil
}
c := container.Command.ProcessConfig.Cmd
container.Command.ProcessConfig.Cmd = exec.Cmd{
Stdin: c.Stdin,
Stdout: c.Stdout,
Stderr: c.Stderr,
Path: c.Path,
Env: c.Env,
ExtraFiles: c.ExtraFiles,
Args: c.Args,
Dir: c.Dir,
SysProcAttr: c.SysProcAttr,
}
}
func (m *containerMonitor) logEvent(action string) {
m.supervisor.LogContainerEvent(m.container, action)
}

12
container/mounts_unix.go Normal file
View file

@ -0,0 +1,12 @@
// +build !windows
package container
// Mount contains information for a mount operation.
type Mount struct {
Source string `json:"source"`
Destination string `json:"destination"`
Writable bool `json:"writable"`
Data string `json:"data"`
Propagation string `json:"mountpropagation"`
}

View file

@ -0,0 +1,8 @@
package container
// Mount contains information for a mount operation.
type Mount struct {
Source string `json:"source"`
Destination string `json:"destination"`
Writable bool `json:"writable"`
}

View file

@ -5,7 +5,6 @@ import (
"sync"
"time"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/go-units"
)
@ -179,28 +178,31 @@ func (s *State) getExitCode() int {
}
// SetRunning sets the state of the container to "running".
func (s *State) SetRunning(pid int) {
func (s *State) SetRunning(pid int, initial bool) {
s.Error = ""
s.Running = true
s.Paused = false
s.Restarting = false
s.ExitCode = 0
s.Pid = pid
s.StartedAt = time.Now().UTC()
if initial {
s.StartedAt = time.Now().UTC()
}
close(s.waitChan) // fire waiters for start
s.waitChan = make(chan struct{})
}
// SetStoppedLocking locks the container state is sets it to "stopped".
func (s *State) SetStoppedLocking(exitStatus *execdriver.ExitStatus) {
func (s *State) SetStoppedLocking(exitStatus *ExitStatus) {
s.Lock()
s.SetStopped(exitStatus)
s.Unlock()
}
// SetStopped sets the container state to "stopped" without locking.
func (s *State) SetStopped(exitStatus *execdriver.ExitStatus) {
func (s *State) SetStopped(exitStatus *ExitStatus) {
s.Running = false
s.Paused = false
s.Restarting = false
s.Pid = 0
s.FinishedAt = time.Now().UTC()
@ -211,7 +213,7 @@ func (s *State) SetStopped(exitStatus *execdriver.ExitStatus) {
// SetRestartingLocking is when docker handles the auto restart of containers when they are
// in the middle of a stop and being restarted again
func (s *State) SetRestartingLocking(exitStatus *execdriver.ExitStatus) {
func (s *State) SetRestartingLocking(exitStatus *ExitStatus) {
s.Lock()
s.SetRestarting(exitStatus)
s.Unlock()
@ -219,7 +221,7 @@ func (s *State) SetRestartingLocking(exitStatus *execdriver.ExitStatus) {
// SetRestarting sets the container state to "restarting".
// It also sets the container PID to 0.
func (s *State) SetRestarting(exitStatus *execdriver.ExitStatus) {
func (s *State) SetRestarting(exitStatus *ExitStatus) {
// we should consider the container running when it is restarting because of
// all the checks in docker around rm/stop/etc
s.Running = true

View file

@ -4,8 +4,6 @@ import (
"sync/atomic"
"testing"
"time"
"github.com/docker/docker/daemon/execdriver"
)
func TestStateRunStop(t *testing.T) {
@ -19,7 +17,7 @@ func TestStateRunStop(t *testing.T) {
close(started)
}()
s.Lock()
s.SetRunning(i + 100)
s.SetRunning(i+100, false)
s.Unlock()
if !s.IsRunning() {
@ -52,7 +50,7 @@ func TestStateRunStop(t *testing.T) {
atomic.StoreInt64(&exit, int64(exitCode))
close(stopped)
}()
s.SetStoppedLocking(&execdriver.ExitStatus{ExitCode: i})
s.SetStoppedLocking(&ExitStatus{ExitCode: i})
if s.IsRunning() {
t.Fatal("State is running")
}
@ -93,7 +91,7 @@ func TestStateTimeoutWait(t *testing.T) {
}
s.Lock()
s.SetRunning(49)
s.SetRunning(49, false)
s.Unlock()
stopped := make(chan struct{})

View file

@ -2,11 +2,9 @@
package container
import "github.com/docker/docker/daemon/execdriver"
// setFromExitStatus is a platform specific helper function to set the state
// based on the ExitStatus structure.
func (s *State) setFromExitStatus(exitStatus *execdriver.ExitStatus) {
func (s *State) setFromExitStatus(exitStatus *ExitStatus) {
s.ExitCode = exitStatus.ExitCode
s.OOMKilled = exitStatus.OOMKilled
}

View file

@ -1,9 +1,7 @@
package container
import "github.com/docker/docker/daemon/execdriver"
// setFromExitStatus is a platform specific helper function to set the state
// based on the ExitStatus structure.
func (s *State) setFromExitStatus(exitStatus *execdriver.ExitStatus) {
func (s *State) setFromExitStatus(exitStatus *ExitStatus) {
s.ExitCode = exitStatus.ExitCode
}

View file

@ -1,9 +0,0 @@
This directory contains code pertaining to running containers and storing images
Code pertaining to running containers:
- execdriver
Code pertaining to storing images:
- graphdriver

View file

@ -0,0 +1,30 @@
// +build linux
package daemon
import (
"github.com/Sirupsen/logrus"
aaprofile "github.com/docker/docker/profiles/apparmor"
"github.com/opencontainers/runc/libcontainer/apparmor"
)
// Define constants for native driver
const (
defaultApparmorProfile = "docker-default"
)
func installDefaultAppArmorProfile() {
if apparmor.IsEnabled() {
if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
apparmorProfiles := []string{defaultApparmorProfile}
// Allow daemon to run if loading failed, but are active
// (possibly through another run, manually, or via system startup)
for _, policy := range apparmorProfiles {
if err := aaprofile.IsLoaded(policy); err != nil {
logrus.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
}
}
}
}
}

View file

@ -0,0 +1,6 @@
// +build !linux
package daemon
func installDefaultAppArmorProfile() {
}

View file

@ -1,6 +1,6 @@
// +build !windows
package execdriver
package caps
import (
"fmt"
@ -24,7 +24,7 @@ func init() {
}
capabilityList = append(capabilityList,
&CapabilityMapping{
Key: strings.ToUpper(cap.String()),
Key: "CAP_" + strings.ToUpper(cap.String()),
Value: cap,
},
)
@ -77,12 +77,16 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
allCaps = GetAllCapabilities()
)
// FIXME(tonistiigi): docker format is without CAP_ prefix, oci is with prefix
// Currently they are mixed in here. We should do conversion in one place.
// look for invalid cap in the drop list
for _, cap := range drops {
if strings.ToLower(cap) == "all" {
continue
}
if !stringutils.InSlice(allCaps, cap) {
if !stringutils.InSlice(allCaps, "CAP_"+cap) {
return nil, fmt.Errorf("Unknown capability drop: %q", cap)
}
}
@ -100,7 +104,7 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
}
// if we don't drop `all`, add back all the non-dropped caps
if !stringutils.InSlice(drops, cap) {
if !stringutils.InSlice(drops, cap[4:]) {
newCaps = append(newCaps, strings.ToUpper(cap))
}
}
@ -112,6 +116,8 @@ func TweakCapabilities(basics, adds, drops []string) ([]string, error) {
continue
}
cap = "CAP_" + cap
if !stringutils.InSlice(allCaps, cap) {
return nil, fmt.Errorf("Unknown capability to add: %q", cap)
}

View file

@ -115,7 +115,7 @@ func (config *Config) InstallCommonFlags(cmd *flag.FlagSet, usageFn func(string)
cmd.Var(opts.NewNamedListOptsRef("exec-opts", &config.ExecOptions, nil), []string{"-exec-opt"}, usageFn("Set exec driver options"))
cmd.StringVar(&config.Pidfile, []string{"p", "-pidfile"}, defaultPidFile, usageFn("Path to use for daemon PID file"))
cmd.StringVar(&config.Root, []string{"g", "-graph"}, defaultGraph, usageFn("Root of the Docker runtime"))
cmd.StringVar(&config.ExecRoot, []string{"-exec-root"}, "/var/run/docker", usageFn("Root of the Docker execdriver"))
cmd.StringVar(&config.ExecRoot, []string{"-exec-root"}, defaultExecRoot, usageFn("Root of the Docker execdriver"))
cmd.BoolVar(&config.AutoRestart, []string{"#r", "#-restart"}, true, usageFn("--restart on the daemon has been deprecated in favor of --restart policies on docker run"))
cmd.StringVar(&config.GraphDriver, []string{"s", "-storage-driver"}, "", usageFn("Storage driver to use"))
cmd.IntVar(&config.Mtu, []string{"#mtu", "-mtu"}, 0, usageFn("Set the containers network MTU"))

View file

@ -12,8 +12,9 @@ import (
)
var (
defaultPidFile = "/var/run/docker.pid"
defaultGraph = "/var/lib/docker"
defaultPidFile = "/var/run/docker.pid"
defaultGraph = "/var/lib/docker"
defaultExecRoot = "/var/run/docker"
)
// Config defines the configuration of a docker daemon.
@ -30,6 +31,7 @@ type Config struct {
RemappedRoot string `json:"userns-remap,omitempty"`
CgroupParent string `json:"cgroup-parent,omitempty"`
Ulimits map[string]*units.Ulimit `json:"default-ulimits,omitempty"`
ContainerdAddr string `json:"containerd,omitempty"`
}
// bridgeConfig stores all the bridge driver specific
@ -80,6 +82,7 @@ func (config *Config) InstallFlags(cmd *flag.FlagSet, usageFn func(string) strin
cmd.StringVar(&config.CorsHeaders, []string{"-api-cors-header"}, "", usageFn("Set CORS headers in the remote API"))
cmd.StringVar(&config.CgroupParent, []string{"-cgroup-parent"}, "", usageFn("Set parent cgroup for all containers"))
cmd.StringVar(&config.RemappedRoot, []string{"-userns-remap"}, "", usageFn("User/Group setting for user namespaces"))
cmd.StringVar(&config.ContainerdAddr, []string{"-containerd"}, "", usageFn("Path to containerD socket"))
config.attachExperimentalFlags(cmd, usageFn)
}

View file

@ -7,8 +7,9 @@ import (
)
var (
defaultPidFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker.pid"
defaultGraph = os.Getenv("programdata") + string(os.PathSeparator) + "docker"
defaultPidFile = os.Getenv("programdata") + string(os.PathSeparator) + "docker.pid"
defaultGraph = os.Getenv("programdata") + string(os.PathSeparator) + "docker"
defaultExecRoot = defaultGraph
)
// bridgeConfig stores all the bridge driver specific

View file

@ -48,11 +48,10 @@ func (daemon *Daemon) buildSandboxOptions(container *container.Container, n libn
sboxOptions = append(sboxOptions, libnetwork.OptionUseDefaultSandbox())
sboxOptions = append(sboxOptions, libnetwork.OptionOriginHostsPath("/etc/hosts"))
sboxOptions = append(sboxOptions, libnetwork.OptionOriginResolvConfPath("/etc/resolv.conf"))
} else if daemon.execDriver.SupportsHooks() {
// OptionUseExternalKey is mandatory for userns support.
// But optional for non-userns support
sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey())
}
// OptionUseExternalKey is mandatory for userns support.
// But optional for non-userns support
sboxOptions = append(sboxOptions, libnetwork.OptionUseExternalKey())
container.HostsPath, err = container.GetRootResourcePath("hosts")
if err != nil {

View file

@ -13,7 +13,6 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/links"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/idtools"
@ -22,13 +21,16 @@ import (
"github.com/docker/docker/runconfig"
containertypes "github.com/docker/engine-api/types/container"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/go-units"
"github.com/docker/libnetwork"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/devices"
"github.com/opencontainers/runc/libcontainer/label"
"github.com/opencontainers/specs/specs-go"
)
func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]string, error) {
var env []string
children := daemon.children(container)
@ -64,220 +66,6 @@ func (daemon *Daemon) setupLinkedContainers(container *container.Container) ([]s
return env, nil
}
func (daemon *Daemon) populateCommand(c *container.Container, env []string) error {
var en *execdriver.Network
if !c.Config.NetworkDisabled {
en = &execdriver.Network{}
if !daemon.execDriver.SupportsHooks() || c.HostConfig.NetworkMode.IsHost() {
en.NamespacePath = c.NetworkSettings.SandboxKey
}
if c.HostConfig.NetworkMode.IsContainer() {
nc, err := daemon.getNetworkedContainer(c.ID, c.HostConfig.NetworkMode.ConnectedContainer())
if err != nil {
return err
}
en.ContainerID = nc.ID
}
}
ipc := &execdriver.Ipc{}
var err error
c.ShmPath, err = c.ShmResourcePath()
if err != nil {
return err
}
if c.HostConfig.IpcMode.IsContainer() {
ic, err := daemon.getIpcContainer(c)
if err != nil {
return err
}
ipc.ContainerID = ic.ID
c.ShmPath = ic.ShmPath
} else {
ipc.HostIpc = c.HostConfig.IpcMode.IsHost()
if ipc.HostIpc {
if _, err := os.Stat("/dev/shm"); err != nil {
return fmt.Errorf("/dev/shm is not mounted, but must be for --ipc=host")
}
c.ShmPath = "/dev/shm"
}
}
pid := &execdriver.Pid{}
pid.HostPid = c.HostConfig.PidMode.IsHost()
uts := &execdriver.UTS{
HostUTS: c.HostConfig.UTSMode.IsHost(),
}
// Build lists of devices allowed and created within the container.
var userSpecifiedDevices []*configs.Device
for _, deviceMapping := range c.HostConfig.Devices {
devs, err := getDevicesFromPath(deviceMapping)
if err != nil {
return err
}
userSpecifiedDevices = append(userSpecifiedDevices, devs...)
}
allowedDevices := mergeDevices(configs.DefaultAllowedDevices, userSpecifiedDevices)
autoCreatedDevices := mergeDevices(configs.DefaultAutoCreatedDevices, userSpecifiedDevices)
var rlimits []*units.Rlimit
ulimits := c.HostConfig.Ulimits
// Merge ulimits with daemon defaults
ulIdx := make(map[string]*units.Ulimit)
for _, ul := range ulimits {
ulIdx[ul.Name] = ul
}
for name, ul := range daemon.configStore.Ulimits {
if _, exists := ulIdx[name]; !exists {
ulimits = append(ulimits, ul)
}
}
weightDevices, err := getBlkioWeightDevices(c.HostConfig)
if err != nil {
return err
}
readBpsDevice, err := getBlkioReadBpsDevices(c.HostConfig)
if err != nil {
return err
}
writeBpsDevice, err := getBlkioWriteBpsDevices(c.HostConfig)
if err != nil {
return err
}
readIOpsDevice, err := getBlkioReadIOpsDevices(c.HostConfig)
if err != nil {
return err
}
writeIOpsDevice, err := getBlkioWriteIOpsDevices(c.HostConfig)
if err != nil {
return err
}
for _, limit := range ulimits {
rl, err := limit.GetRlimit()
if err != nil {
return err
}
rlimits = append(rlimits, rl)
}
resources := &execdriver.Resources{
CommonResources: execdriver.CommonResources{
Memory: c.HostConfig.Memory,
MemoryReservation: c.HostConfig.MemoryReservation,
CPUShares: c.HostConfig.CPUShares,
BlkioWeight: c.HostConfig.BlkioWeight,
},
MemorySwap: c.HostConfig.MemorySwap,
KernelMemory: c.HostConfig.KernelMemory,
CpusetCpus: c.HostConfig.CpusetCpus,
CpusetMems: c.HostConfig.CpusetMems,
CPUPeriod: c.HostConfig.CPUPeriod,
CPUQuota: c.HostConfig.CPUQuota,
Rlimits: rlimits,
BlkioWeightDevice: weightDevices,
BlkioThrottleReadBpsDevice: readBpsDevice,
BlkioThrottleWriteBpsDevice: writeBpsDevice,
BlkioThrottleReadIOpsDevice: readIOpsDevice,
BlkioThrottleWriteIOpsDevice: writeIOpsDevice,
PidsLimit: c.HostConfig.PidsLimit,
MemorySwappiness: -1,
}
if c.HostConfig.OomKillDisable != nil {
resources.OomKillDisable = *c.HostConfig.OomKillDisable
}
if c.HostConfig.MemorySwappiness != nil {
resources.MemorySwappiness = *c.HostConfig.MemorySwappiness
}
processConfig := execdriver.ProcessConfig{
CommonProcessConfig: execdriver.CommonProcessConfig{
Entrypoint: c.Path,
Arguments: c.Args,
Tty: c.Config.Tty,
},
Privileged: c.HostConfig.Privileged,
User: c.Config.User,
}
processConfig.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
processConfig.Env = env
remappedRoot := &execdriver.User{}
if c.HostConfig.UsernsMode.IsPrivate() {
rootUID, rootGID := daemon.GetRemappedUIDGID()
if rootUID != 0 {
remappedRoot.UID = rootUID
remappedRoot.GID = rootGID
}
}
uidMap, gidMap := daemon.GetUIDGIDMaps()
if !daemon.seccompEnabled {
if c.SeccompProfile != "" && c.SeccompProfile != "unconfined" {
return fmt.Errorf("Seccomp is not enabled in your kernel, cannot run a custom seccomp profile.")
}
logrus.Warn("Seccomp is not enabled in your kernel, running container without default profile.")
c.SeccompProfile = "unconfined"
}
defaultCgroupParent := "/docker"
if daemon.configStore.CgroupParent != "" {
defaultCgroupParent = daemon.configStore.CgroupParent
} else if daemon.usingSystemd() {
defaultCgroupParent = "system.slice"
}
c.Command = &execdriver.Command{
CommonCommand: execdriver.CommonCommand{
ID: c.ID,
MountLabel: c.GetMountLabel(),
Network: en,
ProcessConfig: processConfig,
ProcessLabel: c.GetProcessLabel(),
Rootfs: c.BaseFS,
Resources: resources,
WorkingDir: c.Config.WorkingDir,
},
AllowedDevices: allowedDevices,
AppArmorProfile: c.AppArmorProfile,
AutoCreatedDevices: autoCreatedDevices,
CapAdd: c.HostConfig.CapAdd,
CapDrop: c.HostConfig.CapDrop,
CgroupParent: defaultCgroupParent,
GIDMapping: gidMap,
GroupAdd: c.HostConfig.GroupAdd,
Ipc: ipc,
OomScoreAdj: c.HostConfig.OomScoreAdj,
Pid: pid,
ReadonlyRootfs: c.HostConfig.ReadonlyRootfs,
RemappedRoot: remappedRoot,
SeccompProfile: c.SeccompProfile,
UIDMapping: uidMap,
UTS: uts,
NoNewPrivileges: c.NoNewPrivileges,
}
if c.HostConfig.CgroupParent != "" {
c.Command.CgroupParent = c.HostConfig.CgroupParent
}
return nil
}
// getSize returns the real size & virtual size of the container.
func (daemon *Daemon) getSize(container *container.Container) (int64, int64) {
var (
@ -395,28 +183,49 @@ func (daemon *Daemon) getIpcContainer(container *container.Container) (*containe
}
func (daemon *Daemon) setupIpcDirs(c *container.Container) error {
rootUID, rootGID := daemon.GetRemappedUIDGID()
if !c.HasMountFor("/dev/shm") {
shmPath, err := c.ShmResourcePath()
var err error
c.ShmPath, err = c.ShmResourcePath()
if err != nil {
return err
}
if c.HostConfig.IpcMode.IsContainer() {
ic, err := daemon.getIpcContainer(c)
if err != nil {
return err
}
c.ShmPath = ic.ShmPath
} else if c.HostConfig.IpcMode.IsHost() {
if _, err := os.Stat("/dev/shm"); err != nil {
return fmt.Errorf("/dev/shm is not mounted, but must be for --ipc=host")
}
c.ShmPath = "/dev/shm"
} else {
rootUID, rootGID := daemon.GetRemappedUIDGID()
if !c.HasMountFor("/dev/shm") {
shmPath, err := c.ShmResourcePath()
if err != nil {
return err
}
if err := idtools.MkdirAllAs(shmPath, 0700, rootUID, rootGID); err != nil {
return err
if err := idtools.MkdirAllAs(shmPath, 0700, rootUID, rootGID); err != nil {
return err
}
shmSize := container.DefaultSHMSize
if c.HostConfig.ShmSize != 0 {
shmSize = c.HostConfig.ShmSize
}
shmproperty := "mode=1777,size=" + strconv.FormatInt(shmSize, 10)
if err := syscall.Mount("shm", shmPath, "tmpfs", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), label.FormatMountLabel(shmproperty, c.GetMountLabel())); err != nil {
return fmt.Errorf("mounting shm tmpfs: %s", err)
}
if err := os.Chown(shmPath, rootUID, rootGID); err != nil {
return err
}
}
shmSize := container.DefaultSHMSize
if c.HostConfig.ShmSize != 0 {
shmSize = c.HostConfig.ShmSize
}
shmproperty := "mode=1777,size=" + strconv.FormatInt(shmSize, 10)
if err := syscall.Mount("shm", shmPath, "tmpfs", uintptr(syscall.MS_NOEXEC|syscall.MS_NOSUID|syscall.MS_NODEV), label.FormatMountLabel(shmproperty, c.GetMountLabel())); err != nil {
return fmt.Errorf("mounting shm tmpfs: %s", err)
}
if err := os.Chown(shmPath, rootUID, rootGID); err != nil {
return err
}
}
return nil
@ -474,7 +283,19 @@ func killProcessDirectly(container *container.Container) error {
return nil
}
func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []*configs.Device, err error) {
func specDevice(d *configs.Device) specs.Device {
return specs.Device{
Type: string(d.Type),
Path: d.Path,
Major: d.Major,
Minor: d.Minor,
FileMode: fmPtr(int64(d.FileMode)),
UID: u32Ptr(int64(d.Uid)),
GID: u32Ptr(int64(d.Gid)),
}
}
func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []specs.Device, err error) {
resolvedPathOnHost := deviceMapping.PathOnHost
// check if it is a symbolic link
@ -488,7 +309,7 @@ func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []*con
// if there was no error, return the device
if err == nil {
device.Path = deviceMapping.PathInContainer
return append(devs, device), nil
return append(devs, specDevice(device)), nil
}
// if the device is not a device node
@ -508,7 +329,7 @@ func getDevicesFromPath(deviceMapping containertypes.DeviceMapping) (devs []*con
// add the device to userSpecified devices
childDevice.Path = strings.Replace(dpath, resolvedPathOnHost, deviceMapping.PathInContainer, 1)
devs = append(devs, childDevice)
devs = append(devs, specDevice(childDevice))
return nil
})

View file

@ -4,14 +4,9 @@ package daemon
import (
"fmt"
"strings"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/execdriver/windows"
"github.com/docker/docker/layer"
networktypes "github.com/docker/engine-api/types/network"
"github.com/docker/libnetwork"
)
@ -29,135 +24,6 @@ func (daemon *Daemon) DisconnectFromNetwork(container *container.Container, n li
return fmt.Errorf("Windows does not support disconnecting a running container from a network")
}
func (daemon *Daemon) populateCommand(c *container.Container, env []string) error {
en := &execdriver.Network{
Interface: nil,
}
var epList []string
// Connect all the libnetwork allocated networks to the container
if c.NetworkSettings != nil {
for n := range c.NetworkSettings.Networks {
sn, err := daemon.FindNetwork(n)
if err != nil {
continue
}
ep, err := c.GetEndpointInNetwork(sn)
if err != nil {
continue
}
data, err := ep.DriverInfo()
if err != nil {
continue
}
if data["hnsid"] != nil {
epList = append(epList, data["hnsid"].(string))
}
}
}
if daemon.netController == nil {
parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2)
switch parts[0] {
case "none":
case "default", "": // empty string to support existing containers
if !c.Config.NetworkDisabled {
en.Interface = &execdriver.NetworkInterface{
MacAddress: c.Config.MacAddress,
Bridge: daemon.configStore.bridgeConfig.Iface,
PortBindings: c.HostConfig.PortBindings,
// TODO Windows. Include IPAddress. There already is a
// property IPAddress on execDrive.CommonNetworkInterface,
// but there is no CLI option in docker to pass through
// an IPAddress on docker run.
}
}
default:
return fmt.Errorf("invalid network mode: %s", c.HostConfig.NetworkMode)
}
}
// TODO Windows. More resource controls to be implemented later.
resources := &execdriver.Resources{
CommonResources: execdriver.CommonResources{
CPUShares: c.HostConfig.CPUShares,
},
}
processConfig := execdriver.ProcessConfig{
CommonProcessConfig: execdriver.CommonProcessConfig{
Entrypoint: c.Path,
Arguments: c.Args,
Tty: c.Config.Tty,
},
ConsoleSize: c.HostConfig.ConsoleSize,
}
processConfig.Env = env
var layerPaths []string
img, err := daemon.imageStore.Get(c.ImageID)
if err != nil {
return fmt.Errorf("Failed to graph.Get on ImageID %s - %s", c.ImageID, err)
}
if img.RootFS != nil && img.RootFS.Type == "layers+base" {
max := len(img.RootFS.DiffIDs)
for i := 0; i <= max; i++ {
img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
path, err := layer.GetLayerPath(daemon.layerStore, img.RootFS.ChainID())
if err != nil {
return fmt.Errorf("Failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.layerStore, img.RootFS.ChainID(), err)
}
// Reverse order, expecting parent most first
layerPaths = append([]string{path}, layerPaths...)
}
}
m, err := c.RWLayer.Metadata()
if err != nil {
return fmt.Errorf("Failed to get layer metadata - %s", err)
}
layerFolder := m["dir"]
var hvPartition bool
// Work out the isolation (whether it is a hypervisor partition)
if c.HostConfig.Isolation.IsDefault() {
// Not specified by caller. Take daemon default
hvPartition = windows.DefaultIsolation.IsHyperV()
} else {
// Take value specified by caller
hvPartition = c.HostConfig.Isolation.IsHyperV()
}
c.Command = &execdriver.Command{
CommonCommand: execdriver.CommonCommand{
ID: c.ID,
Rootfs: c.BaseFS,
WorkingDir: c.Config.WorkingDir,
Network: en,
MountLabel: c.GetMountLabel(),
Resources: resources,
ProcessConfig: processConfig,
ProcessLabel: c.GetProcessLabel(),
},
FirstStart: !c.HasBeenStartedBefore,
LayerFolder: layerFolder,
LayerPaths: layerPaths,
Hostname: c.Config.Hostname,
Isolation: string(c.HostConfig.Isolation),
ArgsEscaped: c.Config.ArgsEscaped,
HvPartition: hvPartition,
EpList: epList,
}
return nil
}
// getSize returns real size & virtual size
func (daemon *Daemon) getSize(container *container.Container) (int64, int64) {
// TODO Windows

View file

@ -20,13 +20,12 @@ import (
"time"
"github.com/Sirupsen/logrus"
containerd "github.com/docker/containerd/api/grpc/types"
"github.com/docker/docker/api"
"github.com/docker/docker/builder"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/events"
"github.com/docker/docker/daemon/exec"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/execdriver/execdrivers"
"github.com/docker/docker/errors"
"github.com/docker/engine-api/types"
containertypes "github.com/docker/engine-api/types/container"
@ -46,12 +45,12 @@ import (
"github.com/docker/docker/image"
"github.com/docker/docker/image/tarexport"
"github.com/docker/docker/layer"
"github.com/docker/docker/libcontainerd"
"github.com/docker/docker/migrate/v1"
"github.com/docker/docker/pkg/archive"
"github.com/docker/docker/pkg/fileutils"
"github.com/docker/docker/pkg/graphdb"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/namesgenerator"
"github.com/docker/docker/pkg/progress"
"github.com/docker/docker/pkg/registrar"
@ -115,7 +114,6 @@ type Daemon struct {
trustKey libtrust.PrivateKey
idIndex *truncindex.TruncIndex
configStore *Config
execDriver execdriver.Driver
statsCollector *statsCollector
defaultLogConfig containertypes.LogConfig
RegistryService *registry.Service
@ -132,6 +130,8 @@ type Daemon struct {
imageStore image.Store
nameIndex *registrar.Registrar
linkIndex *linkIndex
containerd libcontainerd.Client
defaultIsolation containertypes.Isolation // Default isolation mode on Windows
}
// GetContainer looks for a container using the provided information, which could be
@ -220,36 +220,16 @@ func (daemon *Daemon) registerName(container *container.Container) error {
}
// Register makes a container object usable by the daemon as <container.ID>
func (daemon *Daemon) Register(container *container.Container) error {
func (daemon *Daemon) Register(c *container.Container) error {
// Attach to stdout and stderr
if container.Config.OpenStdin {
container.NewInputPipes()
if c.Config.OpenStdin {
c.NewInputPipes()
} else {
container.NewNopInputPipe()
c.NewNopInputPipe()
}
daemon.containers.Add(container.ID, container)
daemon.idIndex.Add(container.ID)
if container.IsRunning() {
logrus.Debugf("killing old running container %s", container.ID)
// Set exit code to 128 + SIGKILL (9) to properly represent unsuccessful exit
container.SetStoppedLocking(&execdriver.ExitStatus{ExitCode: 137})
// use the current driver and ensure that the container is dead x.x
cmd := &execdriver.Command{
CommonCommand: execdriver.CommonCommand{
ID: container.ID,
},
}
daemon.execDriver.Terminate(cmd)
container.UnmountIpcMounts(mount.Unmount)
daemon.Unmount(container)
if err := container.ToDiskLocking(); err != nil {
logrus.Errorf("Error saving stopped state to disk: %v", err)
}
}
daemon.containers.Add(c.ID, c)
daemon.idIndex.Add(c.ID)
return nil
}
@ -307,17 +287,38 @@ func (daemon *Daemon) restore() error {
logrus.Errorf("Failed to register container %s: %s", c.ID, err)
continue
}
// get list of containers we need to restart
if daemon.configStore.AutoRestart && c.ShouldRestart() {
restartContainers[c] = make(chan struct{})
}
// if c.hostConfig.Links is nil (not just empty), then it is using the old sqlite links and needs to be migrated
if c.HostConfig != nil && c.HostConfig.Links == nil {
migrateLegacyLinks = true
}
}
var wg sync.WaitGroup
var mapLock sync.Mutex
for _, c := range containers {
wg.Add(1)
go func(c *container.Container) {
defer wg.Done()
if c.IsRunning() || c.IsPaused() {
if err := daemon.containerd.Restore(c.ID, libcontainerd.WithRestartManager(c.RestartManager(true))); err != nil {
logrus.Errorf("Failed to restore with containerd: %q", err)
return
}
}
// fixme: only if not running
// get list of containers we need to restart
if daemon.configStore.AutoRestart && !c.IsRunning() && !c.IsPaused() && c.ShouldRestart() {
mapLock.Lock()
restartContainers[c] = make(chan struct{})
mapLock.Unlock()
} else if !c.IsRunning() && !c.IsPaused() {
if mountid, err := daemon.layerStore.GetMountID(c.ID); err == nil {
daemon.cleanupMountsByID(mountid)
}
}
// if c.hostConfig.Links is nil (not just empty), then it is using the old sqlite links and needs to be migrated
if c.HostConfig != nil && c.HostConfig.Links == nil {
migrateLegacyLinks = true
}
}(c)
}
wg.Wait()
// migrate any legacy links from sqlite
linkdbFile := filepath.Join(daemon.root, "linkgraph.db")
@ -599,7 +600,7 @@ func (daemon *Daemon) registerLink(parent, child *container.Container, alias str
// NewDaemon sets up everything for the daemon to be able to service
// requests from the webserver.
func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemon, err error) {
func NewDaemon(config *Config, registryService *registry.Service, containerdRemote libcontainerd.Remote) (daemon *Daemon, err error) {
setDefaultMtu(config)
// Ensure we have compatible and valid configuration options
@ -659,7 +660,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
}
os.Setenv("TMPDIR", realTmp)
d := &Daemon{}
d := &Daemon{configStore: config}
// Ensure the daemon is properly shutdown if there is a failure during
// initialization
defer func() {
@ -670,6 +671,11 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
}
}()
// Set the default isolation mode (only applicable on Windows)
if err := d.setDefaultIsolation(); err != nil {
return nil, fmt.Errorf("error setting default isolation mode: %v", err)
}
// Verify logging driver type
if config.LogConfig.Type != "none" {
if _, err := logger.GetLogDriver(config.LogConfig.Type); err != nil {
@ -682,6 +688,7 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
logrus.Warnf("Failed to configure golang's threads limit: %v", err)
}
installDefaultAppArmorProfile()
daemonRepo := filepath.Join(config.Root, "containers")
if err := idtools.MkdirAllAs(daemonRepo, 0700, rootUID, rootGID); err != nil && !os.IsExist(err) {
return nil, err
@ -781,11 +788,6 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
return nil, fmt.Errorf("Devices cgroup isn't mounted")
}
ed, err := execdrivers.NewDriver(config.ExecOptions, config.ExecRoot, config.Root, sysInfo)
if err != nil {
return nil, err
}
d.ID = trustKey.PublicKey().KeyID()
d.repository = daemonRepo
d.containers = container.NewMemoryStore()
@ -794,8 +796,6 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
d.distributionMetadataStore = distributionMetadataStore
d.trustKey = trustKey
d.idIndex = truncindex.NewTruncIndex([]string{})
d.configStore = config
d.execDriver = ed
d.statsCollector = d.newStatsCollector(1 * time.Second)
d.defaultLogConfig = containertypes.LogConfig{
Type: config.LogConfig.Type,
@ -812,10 +812,12 @@ func NewDaemon(config *Config, registryService *registry.Service) (daemon *Daemo
d.nameIndex = registrar.NewRegistrar()
d.linkIndex = newLinkIndex()
if err := d.cleanupMounts(); err != nil {
go d.execCommandGC()
d.containerd, err = containerdRemote.Client(d)
if err != nil {
return nil, err
}
go d.execCommandGC()
if err := d.restore(); err != nil {
return nil, err
@ -877,6 +879,9 @@ func (daemon *Daemon) Shutdown() error {
logrus.Errorf("Stop container error: %v", err)
return
}
if mountid, err := daemon.layerStore.GetMountID(c.ID); err == nil {
daemon.cleanupMountsByID(mountid)
}
logrus.Debugf("container stopped %s", c.ID)
})
}
@ -923,29 +928,16 @@ func (daemon *Daemon) Mount(container *container.Container) error {
}
// Unmount unsets the container base filesystem
func (daemon *Daemon) Unmount(container *container.Container) {
func (daemon *Daemon) Unmount(container *container.Container) error {
if err := container.RWLayer.Unmount(); err != nil {
logrus.Errorf("Error unmounting container %s: %s", container.ID, err)
return err
}
}
// Run uses the execution driver to run a given container
func (daemon *Daemon) Run(c *container.Container, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (execdriver.ExitStatus, error) {
hooks := execdriver.Hooks{
Start: startCallback,
}
hooks.PreStart = append(hooks.PreStart, func(processConfig *execdriver.ProcessConfig, pid int, chOOM <-chan struct{}) error {
return daemon.setNetworkNamespaceKey(c.ID, pid)
})
return daemon.execDriver.Run(c.Command, pipes, hooks)
return nil
}
func (daemon *Daemon) kill(c *container.Container, sig int) error {
return daemon.execDriver.Kill(c.Command, sig)
}
func (daemon *Daemon) stats(c *container.Container) (*execdriver.ResourceStats, error) {
return daemon.execDriver.Stats(c.ID)
return daemon.containerd.Signal(c.ID, sig)
}
func (daemon *Daemon) subscribeToContainerStats(c *container.Container) chan interface{} {
@ -1322,12 +1314,6 @@ func (daemon *Daemon) GraphDriverName() string {
return daemon.layerStore.DriverName()
}
// ExecutionDriver returns the currently used driver for creating and
// starting execs in a container.
func (daemon *Daemon) ExecutionDriver() execdriver.Driver {
return daemon.execDriver
}
// GetUIDGIDMaps returns the current daemon's user namespace settings
// for the full uid and gid maps which will be applied to containers
// started in this instance.
@ -1536,7 +1522,7 @@ func (daemon *Daemon) IsShuttingDown() bool {
}
// GetContainerStats collects all the stats published by a container
func (daemon *Daemon) GetContainerStats(container *container.Container) (*execdriver.ResourceStats, error) {
func (daemon *Daemon) GetContainerStats(container *container.Container) (*types.StatsJSON, error) {
stats, err := daemon.stats(container)
if err != nil {
return nil, err
@ -1547,7 +1533,22 @@ func (daemon *Daemon) GetContainerStats(container *container.Container) (*execdr
if nwStats, err = daemon.getNetworkStats(container); err != nil {
return nil, err
}
stats.Interfaces = nwStats
stats.Networks = make(map[string]types.NetworkStats)
for _, iface := range nwStats {
// For API Version >= 1.21, the original data of network will
// be returned.
stats.Networks[iface.Name] = types.NetworkStats{
RxBytes: iface.RxBytes,
RxPackets: iface.RxPackets,
RxErrors: iface.RxErrors,
RxDropped: iface.RxDropped,
TxBytes: iface.TxBytes,
TxPackets: iface.TxPackets,
TxErrors: iface.TxErrors,
TxDropped: iface.TxDropped,
}
}
return stats, nil
}
@ -1735,3 +1736,16 @@ func (daemon *Daemon) networkOptions(dconfig *Config) ([]nwconfig.Option, error)
options = append(options, driverOptions(dconfig)...)
return options, nil
}
func copyBlkioEntry(entries []*containerd.BlkioStatsEntry) []types.BlkioStatEntry {
out := make([]types.BlkioStatEntry, len(entries))
for i, re := range entries {
out[i] = types.BlkioStatEntry{
Major: re.Major,
Minor: re.Minor,
Op: re.Op,
Value: re.Value,
}
}
return out
}

View file

@ -12,6 +12,64 @@ import (
"github.com/docker/docker/pkg/mount"
)
func (daemon *Daemon) cleanupMountsByID(id string) error {
logrus.Debugf("Cleaning up old mountid %s: start.", id)
f, err := os.Open("/proc/self/mountinfo")
if err != nil {
return err
}
defer f.Close()
return daemon.cleanupMountsFromReaderByID(f, id, mount.Unmount)
}
func (daemon *Daemon) cleanupMountsFromReaderByID(reader io.Reader, id string, unmount func(target string) error) error {
if daemon.root == "" {
return nil
}
var errors []string
mountRoot := ""
shmSuffix := "/" + id + "/shm"
mergedSuffix := "/" + id + "/merged"
sc := bufio.NewScanner(reader)
for sc.Scan() {
line := sc.Text()
fields := strings.Fields(line)
if strings.HasPrefix(fields[4], daemon.root) {
logrus.Debugf("Mount base: %v", fields[4])
mnt := fields[4]
if strings.HasSuffix(mnt, shmSuffix) || strings.HasSuffix(mnt, mergedSuffix) {
logrus.Debugf("Unmounting %v", mnt)
if err := unmount(mnt); err != nil {
logrus.Error(err)
errors = append(errors, err.Error())
}
} else if mountBase := filepath.Base(mnt); mountBase == id {
mountRoot = mnt
}
}
}
if mountRoot != "" {
logrus.Debugf("Unmounting %v", mountRoot)
if err := unmount(mountRoot); err != nil {
logrus.Error(err)
errors = append(errors, err.Error())
}
}
if err := sc.Err(); err != nil {
return err
}
if len(errors) > 0 {
return fmt.Errorf("Error cleaningup mounts:\n%v", strings.Join(errors, "\n"))
}
logrus.Debugf("Cleaning up old container shm/mqueue/rootfs mounts: done.")
return nil
}
// cleanupMounts umounts shm/mqueue mounts for old containers
func (daemon *Daemon) cleanupMounts() error {
logrus.Debugf("Cleaning up old container shm/mqueue/rootfs mounts: start.")
@ -25,7 +83,7 @@ func (daemon *Daemon) cleanupMounts() error {
}
func (daemon *Daemon) cleanupMountsFromReader(reader io.Reader, unmount func(target string) error) error {
if daemon.repository == "" {
if daemon.root == "" {
return nil
}
sc := bufio.NewScanner(reader)
@ -37,7 +95,7 @@ func (daemon *Daemon) cleanupMountsFromReader(reader io.Reader, unmount func(tar
logrus.Debugf("Mount base: %v", fields[4])
mnt := fields[4]
mountBase := filepath.Base(mnt)
if mountBase == "mqueue" || mountBase == "shm" || mountBase == "merged" {
if mountBase == "shm" || mountBase == "merged" {
logrus.Debugf("Unmounting %v", mnt)
if err := unmount(mnt); err != nil {
logrus.Error(err)

View file

@ -7,53 +7,83 @@ import (
"testing"
)
func TestCleanupMounts(t *testing.T) {
fixture := `230 138 0:60 / / rw,relatime - overlay overlay rw,lowerdir=/var/lib/docker/overlay/0ef9f93d5d365c1385b09d54bbee6afff3d92002c16f22eccb6e1549b2ff97d8/root,upperdir=/var/lib/docker/overlay/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb/upper,workdir=/var/lib/docker/overlay/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb/work
231 230 0:56 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
232 230 0:57 / /dev rw,nosuid - tmpfs tmpfs rw,mode=755
233 232 0:58 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
234 232 0:59 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
235 232 0:55 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
236 230 0:61 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw
237 236 0:62 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw
238 237 0:21 /system.slice/docker.service /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,xattr,release_agent=/lib/systemd/systemd-cgroups-agent,name=systemd
239 237 0:23 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,perf_event
240 237 0:24 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset,clone_children
241 237 0:25 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices
242 237 0:26 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer
243 237 0:27 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/cpu,cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu,cpuacct
244 237 0:28 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio
245 237 0:29 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/net_cls,net_prio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,net_cls,net_prio
246 237 0:30 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,hugetlb
247 237 0:31 /docker/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory
248 230 253:1 /var/lib/docker/volumes/510cc41ac68c48bd4eac932e3e09711673876287abf1b185312cfbfe6261a111/_data /var/lib/docker rw,relatime - ext4 /dev/disk/by-uuid/ba70ea0c-1a8f-4ee4-9687-cb393730e2b5 rw,errors=remount-ro,data=ordered
250 230 253:1 /var/lib/docker/containers/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb/hostname /etc/hostname rw,relatime - ext4 /dev/disk/by-uuid/ba70ea0c-1a8f-4ee4-9687-cb393730e2b5 rw,errors=remount-ro,data=ordered
251 230 253:1 /var/lib/docker/containers/dfac036ce135a8914e292cb2f6fea114f7339983c186366aa26d0051e93162cb/hosts /etc/hosts rw,relatime - ext4 /dev/disk/by-uuid/ba70ea0c-1a8f-4ee4-9687-cb393730e2b5 rw,errors=remount-ro,data=ordered
252 232 0:13 /1 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
139 236 0:11 / /sys/kernel/security rw,relatime - securityfs none rw
140 230 0:54 / /tmp rw,relatime - tmpfs none rw
145 230 0:3 / /run/docker/netns/default rw - nsfs nsfs rw
130 140 0:45 / /tmp/docker_recursive_mount_test312125472/tmpfs rw,relatime - tmpfs tmpfs rw
131 230 0:3 / /run/docker/netns/47903e2e6701 rw - nsfs nsfs rw
133 230 0:55 / /go/src/github.com/docker/docker/bundles/1.9.0-dev/test-integration-cli/d45526097/graph/containers/47903e2e67014246eba27607809d5f5c2437c3bf84c2986393448f84093cc40b/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw`
const mountsFixture = `142 78 0:38 / / rw,relatime - aufs none rw,si=573b861da0b3a05b,dio
143 142 0:60 / /proc rw,nosuid,nodev,noexec,relatime - proc proc rw
144 142 0:67 / /dev rw,nosuid - tmpfs tmpfs rw,mode=755
145 144 0:78 / /dev/pts rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=666
146 144 0:49 / /dev/mqueue rw,nosuid,nodev,noexec,relatime - mqueue mqueue rw
147 142 0:84 / /sys rw,nosuid,nodev,noexec,relatime - sysfs sysfs rw
148 147 0:86 / /sys/fs/cgroup rw,nosuid,nodev,noexec,relatime - tmpfs tmpfs rw,mode=755
149 148 0:22 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/cpuset rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuset
150 148 0:25 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/cpu rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpu
151 148 0:27 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/cpuacct rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,cpuacct
152 148 0:28 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/memory rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,memory
153 148 0:29 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/devices rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,devices
154 148 0:30 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/freezer rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,freezer
155 148 0:31 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/blkio rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,blkio
156 148 0:32 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/perf_event rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,perf_event
157 148 0:33 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/hugetlb rw,nosuid,nodev,noexec,relatime - cgroup cgroup rw,hugetlb
158 148 0:35 /docker/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a /sys/fs/cgroup/systemd rw,nosuid,nodev,noexec,relatime - cgroup systemd rw,name=systemd
159 142 8:4 /home/mlaventure/gopath /home/mlaventure/gopath rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
160 142 8:4 /var/lib/docker/volumes/9a428b651ee4c538130143cad8d87f603a4bf31b928afe7ff3ecd65480692b35/_data /var/lib/docker rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
164 142 8:4 /home/mlaventure/gopath/src/github.com/docker/docker /go/src/github.com/docker/docker rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
165 142 8:4 /var/lib/docker/containers/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a/resolv.conf /etc/resolv.conf rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
166 142 8:4 /var/lib/docker/containers/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a/hostname /etc/hostname rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
167 142 8:4 /var/lib/docker/containers/5425782a95e643181d8a485a2bab3c0bb21f51d7dfc03511f0e6fbf3f3aa356a/hosts /etc/hosts rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
168 144 0:39 / /dev/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
169 144 0:12 /14 /dev/console rw,nosuid,noexec,relatime - devpts devpts rw,gid=5,mode=620,ptmxmode=000
83 147 0:10 / /sys/kernel/security rw,relatime - securityfs none rw
89 142 0:87 / /tmp rw,relatime - tmpfs none rw
97 142 0:60 / /run/docker/netns/default rw,nosuid,nodev,noexec,relatime - proc proc rw
100 160 8:4 /var/lib/docker/volumes/9a428b651ee4c538130143cad8d87f603a4bf31b928afe7ff3ecd65480692b35/_data/aufs /var/lib/docker/aufs rw,relatime - ext4 /dev/disk/by-uuid/d99e196c-1fc4-4b4f-bab9-9962b2b34e99 rw,errors=remount-ro,data=ordered
115 100 0:102 / /var/lib/docker/aufs/mnt/0ecda1c63e5b58b3d89ff380bf646c95cc980252cf0b52466d43619aec7c8432 rw,relatime - aufs none rw,si=573b861dbc01905b,dio
116 160 0:107 / /var/lib/docker/containers/d045dc441d2e2e1d5b3e328d47e5943811a40819fb47497c5f5a5df2d6d13c37/shm rw,nosuid,nodev,noexec,relatime - tmpfs shm rw,size=65536k
118 142 0:102 / /run/docker/libcontainerd/d045dc441d2e2e1d5b3e328d47e5943811a40819fb47497c5f5a5df2d6d13c37/rootfs rw,relatime - aufs none rw,si=573b861dbc01905b,dio
242 142 0:60 / /run/docker/netns/c3664df2a0f7 rw,nosuid,nodev,noexec,relatime - proc proc rw
120 100 0:122 / /var/lib/docker/aufs/mnt/03ca4b49e71f1e49a41108829f4d5c70ac95934526e2af8984a1f65f1de0715d rw,relatime - aufs none rw,si=573b861eb147805b,dio
171 142 0:122 / /run/docker/libcontainerd/e406ff6f3e18516d50e03dbca4de54767a69a403a6f7ec1edc2762812824521e/rootfs rw,relatime - aufs none rw,si=573b861eb147805b,dio
310 142 0:60 / /run/docker/netns/71a18572176b rw,nosuid,nodev,noexec,relatime - proc proc rw
`
func TestCleanupMounts(t *testing.T) {
d := &Daemon{
repository: "/go/src/github.com/docker/docker/bundles/1.9.0-dev/test-integration-cli/d45526097/graph/containers/",
root: "/var/lib/docker/",
}
expected := "/go/src/github.com/docker/docker/bundles/1.9.0-dev/test-integration-cli/d45526097/graph/containers/47903e2e67014246eba27607809d5f5c2437c3bf84c2986393448f84093cc40b/mqueue"
var unmounted bool
expected := "/var/lib/docker/containers/d045dc441d2e2e1d5b3e328d47e5943811a40819fb47497c5f5a5df2d6d13c37/shm"
var unmounted int
unmount := func(target string) error {
if target == expected {
unmounted = true
unmounted++
}
return nil
}
d.cleanupMountsFromReader(strings.NewReader(fixture), unmount)
d.cleanupMountsFromReader(strings.NewReader(mountsFixture), unmount)
if !unmounted {
t.Fatalf("Expected to unmount the mqueue")
if unmounted != 1 {
t.Fatalf("Expected to unmount the shm (and the shm only)")
}
}
func TestCleanupMountsByID(t *testing.T) {
d := &Daemon{
root: "/var/lib/docker/",
}
expected := "/var/lib/docker/aufs/mnt/03ca4b49e71f1e49a41108829f4d5c70ac95934526e2af8984a1f65f1de0715d"
var unmounted int
unmount := func(target string) error {
if target == expected {
unmounted++
}
return nil
}
d.cleanupMountsFromReaderByID(strings.NewReader(mountsFixture), "03ca4b49e71f1e49a41108829f4d5c70ac95934526e2af8984a1f65f1de0715d", unmount)
if unmounted != 1 {
t.Fatalf("Expected to unmount the auf root (and that only)")
}
}

View file

@ -13,6 +13,7 @@ import (
"strconv"
"strings"
"syscall"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/container"
@ -25,6 +26,7 @@ import (
"github.com/docker/docker/reference"
"github.com/docker/docker/runconfig"
runconfigopts "github.com/docker/docker/runconfig/opts"
"github.com/docker/engine-api/types"
pblkiodev "github.com/docker/engine-api/types/blkiodev"
containertypes "github.com/docker/engine-api/types/container"
"github.com/docker/libnetwork"
@ -33,10 +35,10 @@ import (
"github.com/docker/libnetwork/ipamutils"
"github.com/docker/libnetwork/netlabel"
"github.com/docker/libnetwork/options"
"github.com/docker/libnetwork/types"
blkiodev "github.com/opencontainers/runc/libcontainer/configs"
lntypes "github.com/docker/libnetwork/types"
"github.com/opencontainers/runc/libcontainer/label"
"github.com/opencontainers/runc/libcontainer/user"
"github.com/opencontainers/specs/specs-go"
)
const (
@ -51,16 +53,81 @@ const (
defaultRemappedID string = "dockremap"
)
func getBlkioWeightDevices(config *containertypes.HostConfig) ([]*blkiodev.WeightDevice, error) {
func getMemoryResources(config containertypes.Resources) *specs.Memory {
memory := specs.Memory{}
if config.Memory > 0 {
limit := uint64(config.Memory)
memory.Limit = &limit
}
if config.MemoryReservation > 0 {
reservation := uint64(config.MemoryReservation)
memory.Reservation = &reservation
}
if config.MemorySwap != 0 {
swap := uint64(config.MemorySwap)
memory.Swap = &swap
}
if config.MemorySwappiness != nil {
swappiness := uint64(*config.MemorySwappiness)
memory.Swappiness = &swappiness
}
if config.KernelMemory != 0 {
kernelMemory := uint64(config.KernelMemory)
memory.Kernel = &kernelMemory
}
return &memory
}
func getCPUResources(config containertypes.Resources) *specs.CPU {
cpu := specs.CPU{}
if config.CPUShares != 0 {
shares := uint64(config.CPUShares)
cpu.Shares = &shares
}
if config.CpusetCpus != "" {
cpuset := config.CpusetCpus
cpu.Cpus = &cpuset
}
if config.CpusetMems != "" {
cpuset := config.CpusetMems
cpu.Mems = &cpuset
}
if config.CPUPeriod != 0 {
period := uint64(config.CPUPeriod)
cpu.Period = &period
}
if config.CPUQuota != 0 {
quota := uint64(config.CPUQuota)
cpu.Quota = &quota
}
return &cpu
}
func getBlkioWeightDevices(config containertypes.Resources) ([]specs.WeightDevice, error) {
var stat syscall.Stat_t
var blkioWeightDevices []*blkiodev.WeightDevice
var blkioWeightDevices []specs.WeightDevice
for _, weightDevice := range config.BlkioWeightDevice {
if err := syscall.Stat(weightDevice.Path, &stat); err != nil {
return nil, err
}
weightDevice := blkiodev.NewWeightDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), weightDevice.Weight, 0)
blkioWeightDevices = append(blkioWeightDevices, weightDevice)
weight := weightDevice.Weight
d := specs.WeightDevice{Weight: &weight}
d.Major = int64(stat.Rdev / 256)
d.Major = int64(stat.Rdev % 256)
blkioWeightDevices = append(blkioWeightDevices, d)
}
return blkioWeightDevices, nil
@ -105,61 +172,73 @@ func parseSecurityOpt(container *container.Container, config *containertypes.Hos
return err
}
func getBlkioReadIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
var blkioReadIOpsDevice []*blkiodev.ThrottleDevice
func getBlkioReadIOpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) {
var blkioReadIOpsDevice []specs.ThrottleDevice
var stat syscall.Stat_t
for _, iopsDevice := range config.BlkioDeviceReadIOps {
if err := syscall.Stat(iopsDevice.Path, &stat); err != nil {
return nil, err
}
readIOpsDevice := blkiodev.NewThrottleDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), iopsDevice.Rate)
blkioReadIOpsDevice = append(blkioReadIOpsDevice, readIOpsDevice)
rate := iopsDevice.Rate
d := specs.ThrottleDevice{Rate: &rate}
d.Major = int64(stat.Rdev / 256)
d.Major = int64(stat.Rdev % 256)
blkioReadIOpsDevice = append(blkioReadIOpsDevice, d)
}
return blkioReadIOpsDevice, nil
}
func getBlkioWriteIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
var blkioWriteIOpsDevice []*blkiodev.ThrottleDevice
func getBlkioWriteIOpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) {
var blkioWriteIOpsDevice []specs.ThrottleDevice
var stat syscall.Stat_t
for _, iopsDevice := range config.BlkioDeviceWriteIOps {
if err := syscall.Stat(iopsDevice.Path, &stat); err != nil {
return nil, err
}
writeIOpsDevice := blkiodev.NewThrottleDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), iopsDevice.Rate)
blkioWriteIOpsDevice = append(blkioWriteIOpsDevice, writeIOpsDevice)
rate := iopsDevice.Rate
d := specs.ThrottleDevice{Rate: &rate}
d.Major = int64(stat.Rdev / 256)
d.Major = int64(stat.Rdev % 256)
blkioWriteIOpsDevice = append(blkioWriteIOpsDevice, d)
}
return blkioWriteIOpsDevice, nil
}
func getBlkioReadBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
var blkioReadBpsDevice []*blkiodev.ThrottleDevice
func getBlkioReadBpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) {
var blkioReadBpsDevice []specs.ThrottleDevice
var stat syscall.Stat_t
for _, bpsDevice := range config.BlkioDeviceReadBps {
if err := syscall.Stat(bpsDevice.Path, &stat); err != nil {
return nil, err
}
readBpsDevice := blkiodev.NewThrottleDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), bpsDevice.Rate)
blkioReadBpsDevice = append(blkioReadBpsDevice, readBpsDevice)
rate := bpsDevice.Rate
d := specs.ThrottleDevice{Rate: &rate}
d.Major = int64(stat.Rdev / 256)
d.Major = int64(stat.Rdev % 256)
blkioReadBpsDevice = append(blkioReadBpsDevice, d)
}
return blkioReadBpsDevice, nil
}
func getBlkioWriteBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
var blkioWriteBpsDevice []*blkiodev.ThrottleDevice
func getBlkioWriteBpsDevices(config containertypes.Resources) ([]specs.ThrottleDevice, error) {
var blkioWriteBpsDevice []specs.ThrottleDevice
var stat syscall.Stat_t
for _, bpsDevice := range config.BlkioDeviceWriteBps {
if err := syscall.Stat(bpsDevice.Path, &stat); err != nil {
return nil, err
}
writeBpsDevice := blkiodev.NewThrottleDevice(int64(stat.Rdev/256), int64(stat.Rdev%256), bpsDevice.Rate)
blkioWriteBpsDevice = append(blkioWriteBpsDevice, writeBpsDevice)
rate := bpsDevice.Rate
d := specs.ThrottleDevice{Rate: &rate}
d.Major = int64(stat.Rdev / 256)
d.Major = int64(stat.Rdev % 256)
blkioWriteBpsDevice = append(blkioWriteBpsDevice, d)
}
return blkioWriteBpsDevice, nil
@ -600,8 +679,8 @@ func initBridgeDriver(controller libnetwork.NetworkController, config *Config) e
nw, nw6List, err := ipamutils.ElectInterfaceAddresses(bridgeName)
if err == nil {
ipamV4Conf.PreferredPool = types.GetIPNetCanonical(nw).String()
hip, _ := types.GetHostPartIP(nw.IP, nw.Mask)
ipamV4Conf.PreferredPool = lntypes.GetIPNetCanonical(nw).String()
hip, _ := lntypes.GetHostPartIP(nw.IP, nw.Mask)
if hip.IsGlobalUnicast() {
ipamV4Conf.Gateway = nw.IP.String()
}
@ -953,11 +1032,69 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
// conditionalUnmountOnCleanup is a platform specific helper function called
// during the cleanup of a container to unmount.
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) {
daemon.Unmount(container)
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
return daemon.Unmount(container)
}
func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) error {
// Unix has no custom images to register
return nil
}
func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
if !c.IsRunning() {
return nil, errNotRunning{c.ID}
}
stats, err := daemon.containerd.Stats(c.ID)
if err != nil {
return nil, err
}
s := &types.StatsJSON{}
cgs := stats.CgroupStats
if cgs != nil {
s.BlkioStats = types.BlkioStats{
IoServiceBytesRecursive: copyBlkioEntry(cgs.BlkioStats.IoServiceBytesRecursive),
IoServicedRecursive: copyBlkioEntry(cgs.BlkioStats.IoServicedRecursive),
IoQueuedRecursive: copyBlkioEntry(cgs.BlkioStats.IoQueuedRecursive),
IoServiceTimeRecursive: copyBlkioEntry(cgs.BlkioStats.IoServiceTimeRecursive),
IoWaitTimeRecursive: copyBlkioEntry(cgs.BlkioStats.IoWaitTimeRecursive),
IoMergedRecursive: copyBlkioEntry(cgs.BlkioStats.IoMergedRecursive),
IoTimeRecursive: copyBlkioEntry(cgs.BlkioStats.IoTimeRecursive),
SectorsRecursive: copyBlkioEntry(cgs.BlkioStats.SectorsRecursive),
}
cpu := cgs.CpuStats
s.CPUStats = types.CPUStats{
CPUUsage: types.CPUUsage{
TotalUsage: cpu.CpuUsage.TotalUsage,
PercpuUsage: cpu.CpuUsage.PercpuUsage,
UsageInKernelmode: cpu.CpuUsage.UsageInKernelmode,
UsageInUsermode: cpu.CpuUsage.UsageInUsermode,
},
ThrottlingData: types.ThrottlingData{
Periods: cpu.ThrottlingData.Periods,
ThrottledPeriods: cpu.ThrottlingData.ThrottledPeriods,
ThrottledTime: cpu.ThrottlingData.ThrottledTime,
},
}
mem := cgs.MemoryStats.Usage
s.MemoryStats = types.MemoryStats{
Usage: mem.Usage,
MaxUsage: mem.MaxUsage,
Stats: cgs.MemoryStats.Stats,
Failcnt: mem.Failcnt,
}
if cgs.PidsStats != nil {
s.PidsStats = types.PidsStats{
Current: cgs.PidsStats.Current,
}
}
}
s.Read = time.Unix(int64(stats.Timestamp), 0)
return s, nil
}
// setDefaultIsolation determine the default isolation mode for the
// daemon to run in. This is only applicable on Windows
func (daemon *Daemon) setDefaultIsolation() error {
return nil
}

View file

@ -18,11 +18,13 @@ import (
"github.com/docker/docker/layer"
"github.com/docker/docker/reference"
"github.com/docker/docker/runconfig"
containertypes "github.com/docker/engine-api/types/container"
// register the windows graph driver
"github.com/docker/docker/daemon/graphdriver/windows"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/system"
"github.com/docker/engine-api/types"
containertypes "github.com/docker/engine-api/types/container"
"github.com/docker/libnetwork"
nwconfig "github.com/docker/libnetwork/config"
winlibnetwork "github.com/docker/libnetwork/drivers/windows"
@ -39,7 +41,7 @@ const (
windowsMaxCPUShares = 10000
)
func getBlkioWeightDevices(config *containertypes.HostConfig) ([]*blkiodev.WeightDevice, error) {
func getBlkioWeightDevices(config *containertypes.HostConfig) ([]blkiodev.WeightDevice, error) {
return nil, nil
}
@ -47,19 +49,19 @@ func parseSecurityOpt(container *container.Container, config *containertypes.Hos
return nil
}
func getBlkioReadIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
func getBlkioReadIOpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) {
return nil, nil
}
func getBlkioWriteIOpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
func getBlkioWriteIOpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) {
return nil, nil
}
func getBlkioReadBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
func getBlkioReadBpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) {
return nil, nil
}
func getBlkioWriteBpsDevices(config *containertypes.HostConfig) ([]*blkiodev.ThrottleDevice, error) {
func getBlkioWriteBpsDevices(config *containertypes.HostConfig) ([]blkiodev.ThrottleDevice, error) {
return nil, nil
}
@ -287,6 +289,10 @@ func (daemon *Daemon) registerLinks(container *container.Container, hostConfig *
return nil
}
func (daemon *Daemon) cleanupMountsByID(in string) error {
return nil
}
func (daemon *Daemon) cleanupMounts() error {
return nil
}
@ -307,8 +313,19 @@ func setupDaemonRoot(config *Config, rootDir string, rootUID, rootGID int) error
// conditionalMountOnStart is a platform specific helper function during the
// container start to call mount.
func (daemon *Daemon) conditionalMountOnStart(container *container.Container) error {
// Are we going to run as a Hyper-V container?
hv := false
if container.HostConfig.Isolation.IsDefault() {
// Container is set to use the default, so take the default from the daemon configuration
hv = daemon.defaultIsolation.IsHyperV()
} else {
// Container is requesting an isolation mode. Honour it.
hv = container.HostConfig.Isolation.IsHyperV()
}
// We do not mount if a Hyper-V container
if !container.HostConfig.Isolation.IsHyperV() {
if !hv {
if err := daemon.Mount(container); err != nil {
return err
}
@ -318,11 +335,12 @@ func (daemon *Daemon) conditionalMountOnStart(container *container.Container) er
// conditionalUnmountOnCleanup is a platform specific helper function called
// during the cleanup of a container to unmount.
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) {
func (daemon *Daemon) conditionalUnmountOnCleanup(container *container.Container) error {
// We do not unmount if a Hyper-V container
if !container.HostConfig.Isolation.IsHyperV() {
daemon.Unmount(container)
return daemon.Unmount(container)
}
return nil
}
func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) error {
@ -404,3 +422,35 @@ func restoreCustomImage(is image.Store, ls layer.Store, rs reference.Store) erro
func driverOptions(config *Config) []nwconfig.Option {
return []nwconfig.Option{}
}
func (daemon *Daemon) stats(c *container.Container) (*types.StatsJSON, error) {
return nil, nil
}
// setDefaultIsolation determine the default isolation mode for the
// daemon to run in. This is only applicable on Windows
func (daemon *Daemon) setDefaultIsolation() error {
daemon.defaultIsolation = containertypes.Isolation("process")
for _, option := range daemon.configStore.ExecOptions {
key, val, err := parsers.ParseKeyValueOpt(option)
if err != nil {
return err
}
key = strings.ToLower(key)
switch key {
case "isolation":
if !containertypes.Isolation(val).IsValid() {
return fmt.Errorf("Invalid exec-opt value for 'isolation':'%s'", val)
}
if containertypes.Isolation(val).IsHyperV() {
daemon.defaultIsolation = containertypes.Isolation("hyperv")
}
default:
return fmt.Errorf("Unrecognised exec-opt '%s'\n", key)
}
}
logrus.Infof("Windows default isolation mode: %s", daemon.defaultIsolation)
return nil
}

View file

@ -129,9 +129,6 @@ func (daemon *Daemon) cleanupContainer(container *container.Container, forceRemo
return fmt.Errorf("Driver %s failed to remove root filesystem %s: %s", daemon.GraphDriverName(), container.ID, err)
}
if err = daemon.execDriver.Clean(container.ID); err != nil {
return fmt.Errorf("Unable to remove execdriver data for %s: %s", container.ID, err)
}
return nil
}

View file

@ -11,10 +11,9 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/exec"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/errors"
"github.com/docker/docker/libcontainerd"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/promise"
"github.com/docker/docker/pkg/term"
"github.com/docker/engine-api/types"
"github.com/docker/engine-api/types/strslice"
@ -106,33 +105,31 @@ func (d *Daemon) ContainerExecCreate(config *types.ExecConfig) (string, error) {
}
}
processConfig := &execdriver.ProcessConfig{
CommonProcessConfig: execdriver.CommonProcessConfig{
Tty: config.Tty,
Entrypoint: entrypoint,
Arguments: args,
},
}
setPlatformSpecificExecProcessConfig(config, container, processConfig)
execConfig := exec.NewConfig()
execConfig.OpenStdin = config.AttachStdin
execConfig.OpenStdout = config.AttachStdout
execConfig.OpenStderr = config.AttachStderr
execConfig.ProcessConfig = processConfig
execConfig.ContainerID = container.ID
execConfig.DetachKeys = keys
execConfig.Entrypoint = entrypoint
execConfig.Args = args
execConfig.Tty = config.Tty
execConfig.Privileged = config.Privileged
execConfig.User = config.User
if len(execConfig.User) == 0 {
execConfig.User = container.Config.User
}
d.registerExecCommand(container, execConfig)
d.LogContainerEvent(container, "exec_create: "+execConfig.ProcessConfig.Entrypoint+" "+strings.Join(execConfig.ProcessConfig.Arguments, " "))
d.LogContainerEvent(container, "exec_create: "+execConfig.Entrypoint+" "+strings.Join(execConfig.Args, " "))
return execConfig.ID, nil
}
// ContainerExecStart starts a previously set up exec instance. The
// std streams are set up.
func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) error {
func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.Writer, stderr io.Writer) (err error) {
var (
cStdin io.ReadCloser
cStdout, cStderr io.Writer
@ -155,11 +152,18 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.
return fmt.Errorf("Error: Exec command %s is already running", ec.ID)
}
ec.Running = true
defer func() {
if err != nil {
ec.Running = false
exitCode := 126
ec.ExitCode = &exitCode
}
}()
ec.Unlock()
c := d.containers.Get(ec.ContainerID)
logrus.Debugf("starting exec command %s in container %s", ec.ID, c.ID)
d.LogContainerEvent(c, "exec_start: "+ec.ProcessConfig.Entrypoint+" "+strings.Join(ec.ProcessConfig.Arguments, " "))
d.LogContainerEvent(c, "exec_start: "+ec.Entrypoint+" "+strings.Join(ec.Args, " "))
if ec.OpenStdin && stdin != nil {
r, w := io.Pipe()
@ -183,56 +187,26 @@ func (d *Daemon) ContainerExecStart(name string, stdin io.ReadCloser, stdout io.
ec.NewNopInputPipe()
}
attachErr := container.AttachStreams(context.Background(), ec.StreamConfig, ec.OpenStdin, true, ec.ProcessConfig.Tty, cStdin, cStdout, cStderr, ec.DetachKeys)
p := libcontainerd.Process{
Args: append([]string{ec.Entrypoint}, ec.Args...),
Terminal: ec.Tty,
}
execErr := make(chan error)
// Note, the ExecConfig data will be removed when the container
// itself is deleted. This allows us to query it (for things like
// the exitStatus) even after the cmd is done running.
go func() {
execErr <- d.containerExec(c, ec)
}()
select {
case err := <-attachErr:
if err != nil {
return fmt.Errorf("attach failed with error: %v", err)
}
if err := execSetPlatformOpt(c, ec, &p); err != nil {
return nil
case err := <-execErr:
if aErr := <-attachErr; aErr != nil && err == nil {
return fmt.Errorf("attach failed with error: %v", aErr)
}
if err == nil {
return nil
}
// Maybe the container stopped while we were trying to exec
if !c.IsRunning() {
return fmt.Errorf("container stopped while running exec: %s", c.ID)
}
return fmt.Errorf("Cannot run exec command %s in container %s: %s", ec.ID, c.ID, err)
}
}
// Exec calls the underlying exec driver to run
func (d *Daemon) Exec(c *container.Container, execConfig *exec.Config, pipes *execdriver.Pipes, startCallback execdriver.DriverCallback) (int, error) {
hooks := execdriver.Hooks{
Start: startCallback,
}
exitStatus, err := d.execDriver.Exec(c.Command, execConfig.ProcessConfig, pipes, hooks)
// On err, make sure we don't leave ExitCode at zero
if err != nil && exitStatus == 0 {
exitStatus = 128
}
execConfig.ExitCode = &exitStatus
execConfig.Running = false
attachErr := container.AttachStreams(context.Background(), ec.StreamConfig, ec.OpenStdin, true, ec.Tty, cStdin, cStdout, cStderr, ec.DetachKeys)
return exitStatus, err
if err := d.containerd.AddProcess(c.ID, name, p); err != nil {
return err
}
err = <-attachErr
if err != nil {
return fmt.Errorf("attach failed with error: %v", err)
}
return nil
}
// execCommandGC runs a ticker to clean up the daemon references
@ -270,52 +244,3 @@ func (d *Daemon) containerExecIds() map[string]struct{} {
}
return ids
}
func (d *Daemon) containerExec(container *container.Container, ec *exec.Config) error {
container.Lock()
defer container.Unlock()
callback := func(processConfig *execdriver.ProcessConfig, pid int, chOOM <-chan struct{}) error {
if processConfig.Tty {
// The callback is called after the process Start()
// so we are in the parent process. In TTY mode, stdin/out/err is the PtySlave
// which we close here.
if c, ok := processConfig.Stdout.(io.Closer); ok {
c.Close()
}
}
ec.Close()
return nil
}
// We use a callback here instead of a goroutine and an chan for
// synchronization purposes
cErr := promise.Go(func() error { return d.monitorExec(container, ec, callback) })
return ec.Wait(cErr)
}
func (d *Daemon) monitorExec(container *container.Container, execConfig *exec.Config, callback execdriver.DriverCallback) error {
pipes := execdriver.NewPipes(execConfig.Stdin(), execConfig.Stdout(), execConfig.Stderr(), execConfig.OpenStdin)
exitCode, err := d.Exec(container, execConfig, pipes, callback)
if err != nil {
logrus.Errorf("Error running command in existing container %s: %s", container.ID, err)
}
logrus.Debugf("Exec task in container %s exited with code %d", container.ID, exitCode)
if err := execConfig.CloseStreams(); err != nil {
logrus.Errorf("%s: %s", container.ID, err)
}
if execConfig.ProcessConfig.Terminal != nil {
if err := execConfig.WaitResize(); err != nil {
logrus.Errorf("Error waiting for resize: %v", err)
}
if err := execConfig.ProcessConfig.Terminal.Close(); err != nil {
logrus.Errorf("Error closing terminal while running in container %s: %s", container.ID, err)
}
}
// remove the exec command from the container's store only and not the
// daemon's store so that the exec command can be inspected.
container.ExecCommands.Delete(execConfig.ID)
return err
}

View file

@ -1,11 +1,8 @@
package exec
import (
"fmt"
"sync"
"time"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/stringid"
"github.com/docker/docker/runconfig"
)
@ -16,22 +13,20 @@ import (
type Config struct {
sync.Mutex
*runconfig.StreamConfig
ID string
Running bool
ExitCode *int
ProcessConfig *execdriver.ProcessConfig
OpenStdin bool
OpenStderr bool
OpenStdout bool
CanRemove bool
ContainerID string
DetachKeys []byte
// waitStart will be closed immediately after the exec is really started.
waitStart chan struct{}
// waitResize will be closed after Resize is finished.
waitResize chan struct{}
ID string
Running bool
ExitCode *int
OpenStdin bool
OpenStderr bool
OpenStdout bool
CanRemove bool
ContainerID string
DetachKeys []byte
Entrypoint string
Args []string
Tty bool
Privileged bool
User string
}
// NewConfig initializes the a new exec configuration
@ -39,8 +34,6 @@ func NewConfig() *Config {
return &Config{
ID: stringid.GenerateNonCryptoID(),
StreamConfig: runconfig.NewStreamConfig(),
waitStart: make(chan struct{}),
waitResize: make(chan struct{}),
}
}
@ -98,45 +91,3 @@ func (e *Store) List() []string {
e.RUnlock()
return IDs
}
// Wait waits until the exec process finishes or there is an error in the error channel.
func (c *Config) Wait(cErr chan error) error {
// Exec should not return until the process is actually running
select {
case <-c.waitStart:
case err := <-cErr:
return err
}
return nil
}
// WaitResize waits until terminal resize finishes or time out.
func (c *Config) WaitResize() error {
select {
case <-c.waitResize:
case <-time.After(time.Second):
return fmt.Errorf("Terminal resize for exec %s time out.", c.ID)
}
return nil
}
// Close closes the wait channel for the progress.
func (c *Config) Close() {
close(c.waitStart)
}
// CloseResize closes the wait channel for resizing terminal.
func (c *Config) CloseResize() {
close(c.waitResize)
}
// Resize changes the size of the terminal for the exec process.
func (c *Config) Resize(h, w int) error {
defer c.CloseResize()
select {
case <-c.waitStart:
case <-time.After(time.Second):
return fmt.Errorf("Exec %s is not running, so it can not be resized.", c.ID)
}
return c.ProcessConfig.Terminal.Resize(h, w)
}

26
daemon/exec_linux.go Normal file
View file

@ -0,0 +1,26 @@
package daemon
import (
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/caps"
"github.com/docker/docker/daemon/exec"
"github.com/docker/docker/libcontainerd"
)
func execSetPlatformOpt(c *container.Container, ec *exec.Config, p *libcontainerd.Process) error {
if len(ec.User) > 0 {
uid, gid, additionalGids, err := getUser(c, ec.User)
if err != nil {
return err
}
p.User = &libcontainerd.User{
UID: uid,
GID: gid,
AdditionalGids: additionalGids,
}
}
if ec.Privileged {
p.Capabilities = caps.GetAllCapabilities()
}
return nil
}

View file

@ -1,21 +0,0 @@
// +build linux freebsd
package daemon
import (
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/engine-api/types"
)
// setPlatformSpecificExecProcessConfig sets platform-specific fields in the
// ProcessConfig structure.
func setPlatformSpecificExecProcessConfig(config *types.ExecConfig, container *container.Container, pc *execdriver.ProcessConfig) {
user := config.User
if len(user) == 0 {
user = container.Config.User
}
pc.User = user
pc.Privileged = config.Privileged
}

View file

@ -2,11 +2,13 @@ package daemon
import (
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/engine-api/types"
"github.com/docker/docker/daemon/exec"
"github.com/docker/docker/libcontainerd"
)
// setPlatformSpecificExecProcessConfig sets platform-specific fields in the
// ProcessConfig structure. This is a no-op on Windows
func setPlatformSpecificExecProcessConfig(config *types.ExecConfig, container *container.Container, pc *execdriver.ProcessConfig) {
func execSetPlatformOpt(c *container.Container, ec *exec.Config, p *libcontainerd.Process) error {
// Process arguments need to be escaped before sending to OCI.
// TODO (jstarks): escape the entrypoint too once the tests are fixed to not rely on this behavior
p.Args = append([]string{p.Args[0]}, escapeArgs(p.Args[1:])...)
return nil
}

View file

@ -1,133 +0,0 @@
package execdriver
import (
"errors"
"io"
"os/exec"
"time"
"github.com/opencontainers/runc/libcontainer"
)
// Context is a generic key value pair that allows
// arbitrary data to be sent
type Context map[string]string
// Define error messages
var (
ErrNotRunning = errors.New("Container is not running")
ErrWaitTimeoutReached = errors.New("Wait timeout reached")
ErrDriverAlreadyRegistered = errors.New("A driver already registered this docker init function")
ErrDriverNotFound = errors.New("The requested docker init has not been found")
)
// DriverCallback defines a callback function which is used in "Run" and "Exec".
// This allows work to be done in the parent process when the child is passing
// through PreStart, Start and PostStop events.
// Callbacks are provided a processConfig pointer and the pid of the child.
// The channel will be used to notify the OOM events.
type DriverCallback func(processConfig *ProcessConfig, pid int, chOOM <-chan struct{}) error
// Hooks is a struct containing function pointers to callbacks
// used by any execdriver implementation exploiting hooks capabilities
type Hooks struct {
// PreStart is called before container's CMD/ENTRYPOINT is executed
PreStart []DriverCallback
// Start is called after the container's process is full started
Start DriverCallback
// PostStop is called after the container process exits
PostStop []DriverCallback
}
// Terminal represents a pseudo TTY, it is for when
// using a container interactively.
type Terminal interface {
io.Closer
Resize(height, width int) error
}
// Driver is an interface for drivers to implement
// including all basic functions a driver should have
type Driver interface {
// Run executes the process, blocks until the process exits and returns
// the exit code. It's the last stage on Docker side for running a container.
Run(c *Command, pipes *Pipes, hooks Hooks) (ExitStatus, error)
// Exec executes the process in an existing container, blocks until the
// process exits and returns the exit code.
Exec(c *Command, processConfig *ProcessConfig, pipes *Pipes, hooks Hooks) (int, error)
// Kill sends signals to process in container.
Kill(c *Command, sig int) error
// Pause pauses a container.
Pause(c *Command) error
// Unpause unpauses a container.
Unpause(c *Command) error
// Name returns the name of the driver.
Name() string
// GetPidsForContainer returns a list of pid for the processes running in a container.
GetPidsForContainer(id string) ([]int, error)
// Terminate kills a container by sending signal SIGKILL.
Terminate(c *Command) error
// Clean removes all traces of container exec.
Clean(id string) error
// Stats returns resource stats for a running container
Stats(id string) (*ResourceStats, error)
// Update updates resource configs for a container
Update(c *Command) error
// SupportsHooks refers to the driver capability to exploit pre/post hook functionality
SupportsHooks() bool
}
// CommonResources contains the resource configs for a driver that are
// common across platforms.
type CommonResources struct {
Memory int64 `json:"memory"`
MemoryReservation int64 `json:"memory_reservation"`
CPUShares int64 `json:"cpu_shares"`
BlkioWeight uint16 `json:"blkio_weight"`
}
// ResourceStats contains information about resource usage by a container.
type ResourceStats struct {
*libcontainer.Stats
Read time.Time `json:"read"`
MemoryLimit int64 `json:"memory_limit"`
SystemUsage uint64 `json:"system_usage"`
}
// CommonProcessConfig is the common platform agnostic part of the ProcessConfig
// structure that describes a process that will be run inside a container.
type CommonProcessConfig struct {
exec.Cmd `json:"-"`
Tty bool `json:"tty"`
Entrypoint string `json:"entrypoint"`
Arguments []string `json:"arguments"`
Terminal Terminal `json:"-"` // standard or tty terminal
}
// CommonCommand is the common platform agnostic part of the Command structure
// which wraps an os/exec.Cmd to add more metadata
type CommonCommand struct {
ContainerPid int `json:"container_pid"` // the pid for the process inside a container
ID string `json:"id"`
MountLabel string `json:"mount_label"` // TODO Windows. More involved, but can be factored out
Mounts []Mount `json:"mounts"`
Network *Network `json:"network"`
ProcessConfig ProcessConfig `json:"process_config"` // Describes the init process of the container.
ProcessLabel string `json:"process_label"` // TODO Windows. More involved, but can be factored out
Resources *Resources `json:"resources"`
Rootfs string `json:"rootfs"` // root fs of the container
WorkingDir string `json:"working_dir"`
TmpDir string `json:"tmpdir"` // Directory used to store docker tmpdirs.
}

View file

@ -1,323 +0,0 @@
// +build !windows
package execdriver
import (
"encoding/json"
"io/ioutil"
"os"
"path/filepath"
"strconv"
"strings"
"time"
"github.com/docker/docker/daemon/execdriver/native/template"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/mount"
"github.com/docker/go-units"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/cgroups/fs"
"github.com/opencontainers/runc/libcontainer/configs"
blkiodev "github.com/opencontainers/runc/libcontainer/configs"
)
// Mount contains information for a mount operation.
type Mount struct {
Source string `json:"source"`
Destination string `json:"destination"`
Writable bool `json:"writable"`
Data string `json:"data"`
Propagation string `json:"mountpropagation"`
}
// Resources contains all resource configs for a driver.
// Currently these are all for cgroup configs.
type Resources struct {
CommonResources
// Fields below here are platform specific
BlkioWeightDevice []*blkiodev.WeightDevice `json:"blkio_weight_device"`
BlkioThrottleReadBpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_read_bps_device"`
BlkioThrottleWriteBpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_write_bps_device"`
BlkioThrottleReadIOpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_read_iops_device"`
BlkioThrottleWriteIOpsDevice []*blkiodev.ThrottleDevice `json:"blkio_throttle_write_iops_device"`
MemorySwap int64 `json:"memory_swap"`
KernelMemory int64 `json:"kernel_memory"`
CPUQuota int64 `json:"cpu_quota"`
CpusetCpus string `json:"cpuset_cpus"`
CpusetMems string `json:"cpuset_mems"`
CPUPeriod int64 `json:"cpu_period"`
Rlimits []*units.Rlimit `json:"rlimits"`
OomKillDisable bool `json:"oom_kill_disable"`
PidsLimit int64 `json:"pids_limit"`
MemorySwappiness int64 `json:"memory_swappiness"`
}
// ProcessConfig is the platform specific structure that describes a process
// that will be run inside a container.
type ProcessConfig struct {
CommonProcessConfig
// Fields below here are platform specific
Privileged bool `json:"privileged"`
User string `json:"user"`
Console string `json:"-"` // dev/console path
}
// Ipc settings of the container
// It is for IPC namespace setting. Usually different containers
// have their own IPC namespace, however this specifies to use
// an existing IPC namespace.
// You can join the host's or a container's IPC namespace.
type Ipc struct {
ContainerID string `json:"container_id"` // id of the container to join ipc.
HostIpc bool `json:"host_ipc"`
}
// Pid settings of the container
// It is for PID namespace setting. Usually different containers
// have their own PID namespace, however this specifies to use
// an existing PID namespace.
// Joining the host's PID namespace is currently the only supported
// option.
type Pid struct {
HostPid bool `json:"host_pid"`
}
// UTS settings of the container
// It is for UTS namespace setting. Usually different containers
// have their own UTS namespace, however this specifies to use
// an existing UTS namespace.
// Joining the host's UTS namespace is currently the only supported
// option.
type UTS struct {
HostUTS bool `json:"host_uts"`
}
// Network settings of the container
type Network struct {
Mtu int `json:"mtu"`
ContainerID string `json:"container_id"` // id of the container to join network.
NamespacePath string `json:"namespace_path"`
HostNetworking bool `json:"host_networking"`
}
// Command wraps an os/exec.Cmd to add more metadata
type Command struct {
CommonCommand
// Fields below here are platform specific
AllowedDevices []*configs.Device `json:"allowed_devices"`
AppArmorProfile string `json:"apparmor_profile"`
AutoCreatedDevices []*configs.Device `json:"autocreated_devices"`
CapAdd []string `json:"cap_add"`
CapDrop []string `json:"cap_drop"`
CgroupParent string `json:"cgroup_parent"` // The parent cgroup for this command.
GIDMapping []idtools.IDMap `json:"gidmapping"`
GroupAdd []string `json:"group_add"`
Ipc *Ipc `json:"ipc"`
OomScoreAdj int `json:"oom_score_adj"`
Pid *Pid `json:"pid"`
ReadonlyRootfs bool `json:"readonly_rootfs"`
RemappedRoot *User `json:"remap_root"`
SeccompProfile string `json:"seccomp_profile"`
UIDMapping []idtools.IDMap `json:"uidmapping"`
UTS *UTS `json:"uts"`
NoNewPrivileges bool `json:"no_new_privileges"`
}
// SetRootPropagation sets the root mount propagation mode.
func SetRootPropagation(config *configs.Config, propagation int) {
config.RootPropagation = propagation
}
// InitContainer is the initialization of a container config.
// It returns the initial configs for a container. It's mostly
// defined by the default template.
func InitContainer(c *Command) *configs.Config {
container := template.New()
container.Hostname = getEnv("HOSTNAME", c.ProcessConfig.Env)
container.Cgroups.Name = c.ID
container.Cgroups.Resources.AllowedDevices = c.AllowedDevices
container.Devices = filterDevices(c.AutoCreatedDevices, (c.RemappedRoot.UID != 0))
container.Rootfs = c.Rootfs
container.Readonlyfs = c.ReadonlyRootfs
// This can be overridden later by driver during mount setup based
// on volume options
SetRootPropagation(container, mount.RPRIVATE)
container.Cgroups.Parent = c.CgroupParent
// check to see if we are running in ramdisk to disable pivot root
container.NoPivotRoot = os.Getenv("DOCKER_RAMDISK") != ""
return container
}
func filterDevices(devices []*configs.Device, userNamespacesEnabled bool) []*configs.Device {
if !userNamespacesEnabled {
return devices
}
filtered := []*configs.Device{}
// if we have user namespaces enabled, these devices will not be created
// because of the mknod limitation in the kernel for an unprivileged process.
// Rather, they will be bind-mounted, which will only work if they exist;
// check for existence and remove non-existent entries from the list
for _, device := range devices {
if _, err := os.Stat(device.Path); err == nil {
filtered = append(filtered, device)
}
}
return filtered
}
func getEnv(key string, env []string) string {
for _, pair := range env {
parts := strings.SplitN(pair, "=", 2)
if parts[0] == key {
return parts[1]
}
}
return ""
}
// SetupCgroups setups cgroup resources for a container.
func SetupCgroups(container *configs.Config, c *Command) error {
if c.Resources != nil {
container.Cgroups.Resources.CpuShares = c.Resources.CPUShares
container.Cgroups.Resources.Memory = c.Resources.Memory
container.Cgroups.Resources.MemoryReservation = c.Resources.MemoryReservation
container.Cgroups.Resources.MemorySwap = c.Resources.MemorySwap
container.Cgroups.Resources.KernelMemory = c.Resources.KernelMemory
container.Cgroups.Resources.CpusetCpus = c.Resources.CpusetCpus
container.Cgroups.Resources.CpusetMems = c.Resources.CpusetMems
container.Cgroups.Resources.CpuPeriod = c.Resources.CPUPeriod
container.Cgroups.Resources.CpuQuota = c.Resources.CPUQuota
container.Cgroups.Resources.BlkioWeight = c.Resources.BlkioWeight
container.Cgroups.Resources.BlkioWeightDevice = c.Resources.BlkioWeightDevice
container.Cgroups.Resources.BlkioThrottleReadBpsDevice = c.Resources.BlkioThrottleReadBpsDevice
container.Cgroups.Resources.BlkioThrottleWriteBpsDevice = c.Resources.BlkioThrottleWriteBpsDevice
container.Cgroups.Resources.BlkioThrottleReadIOPSDevice = c.Resources.BlkioThrottleReadIOpsDevice
container.Cgroups.Resources.BlkioThrottleWriteIOPSDevice = c.Resources.BlkioThrottleWriteIOpsDevice
container.Cgroups.Resources.OomKillDisable = c.Resources.OomKillDisable
container.Cgroups.Resources.PidsLimit = c.Resources.PidsLimit
container.Cgroups.Resources.MemorySwappiness = c.Resources.MemorySwappiness
}
return nil
}
// Returns the network statistics for the network interfaces represented by the NetworkRuntimeInfo.
func getNetworkInterfaceStats(interfaceName string) (*libcontainer.NetworkInterface, error) {
out := &libcontainer.NetworkInterface{Name: interfaceName}
// This can happen if the network runtime information is missing - possible if the
// container was created by an old version of libcontainer.
if interfaceName == "" {
return out, nil
}
type netStatsPair struct {
// Where to write the output.
Out *uint64
// The network stats file to read.
File string
}
// Ingress for host veth is from the container. Hence tx_bytes stat on the host veth is actually number of bytes received by the container.
netStats := []netStatsPair{
{Out: &out.RxBytes, File: "tx_bytes"},
{Out: &out.RxPackets, File: "tx_packets"},
{Out: &out.RxErrors, File: "tx_errors"},
{Out: &out.RxDropped, File: "tx_dropped"},
{Out: &out.TxBytes, File: "rx_bytes"},
{Out: &out.TxPackets, File: "rx_packets"},
{Out: &out.TxErrors, File: "rx_errors"},
{Out: &out.TxDropped, File: "rx_dropped"},
}
for _, netStat := range netStats {
data, err := readSysfsNetworkStats(interfaceName, netStat.File)
if err != nil {
return nil, err
}
*(netStat.Out) = data
}
return out, nil
}
// Reads the specified statistics available under /sys/class/net/<EthInterface>/statistics
func readSysfsNetworkStats(ethInterface, statsFile string) (uint64, error) {
data, err := ioutil.ReadFile(filepath.Join("/sys/class/net", ethInterface, "statistics", statsFile))
if err != nil {
return 0, err
}
return strconv.ParseUint(strings.TrimSpace(string(data)), 10, 64)
}
// Stats collects all the resource usage information from a container.
func Stats(containerDir string, containerMemoryLimit int64, machineMemory int64) (*ResourceStats, error) {
f, err := os.Open(filepath.Join(containerDir, "state.json"))
if err != nil {
return nil, err
}
defer f.Close()
type network struct {
Type string
HostInterfaceName string
}
state := struct {
CgroupPaths map[string]string `json:"cgroup_paths"`
Networks []network
}{}
if err := json.NewDecoder(f).Decode(&state); err != nil {
return nil, err
}
now := time.Now()
mgr := fs.Manager{Paths: state.CgroupPaths}
cstats, err := mgr.GetStats()
if err != nil {
return nil, err
}
stats := &libcontainer.Stats{CgroupStats: cstats}
// if the container does not have any memory limit specified set the
// limit to the machines memory
memoryLimit := containerMemoryLimit
if memoryLimit == 0 {
memoryLimit = machineMemory
}
for _, iface := range state.Networks {
switch iface.Type {
case "veth":
istats, err := getNetworkInterfaceStats(iface.HostInterfaceName)
if err != nil {
return nil, err
}
stats.Interfaces = append(stats.Interfaces, istats)
}
}
return &ResourceStats{
Stats: stats,
Read: now,
MemoryLimit: memoryLimit,
}, nil
}
// User contains the uid and gid representing a Unix user
type User struct {
UID int `json:"root_uid"`
GID int `json:"root_gid"`
}
// ExitStatus provides exit reasons for a container.
type ExitStatus struct {
// The exit code with which the container exited.
ExitCode int
// Whether the container encountered an OOM.
OOMKilled bool
}

View file

@ -1,66 +0,0 @@
package execdriver
import "github.com/docker/go-connections/nat"
// Mount contains information for a mount operation.
type Mount struct {
Source string `json:"source"`
Destination string `json:"destination"`
Writable bool `json:"writable"`
}
// Resources contains all resource configs for a driver.
// Currently these are all for cgroup configs.
type Resources struct {
CommonResources
// Fields below here are platform specific
}
// ProcessConfig is the platform specific structure that describes a process
// that will be run inside a container.
type ProcessConfig struct {
CommonProcessConfig
// Fields below here are platform specific
ConsoleSize [2]int `json:"-"` // h,w of initial console size
}
// Network settings of the container
type Network struct {
Interface *NetworkInterface `json:"interface"`
ContainerID string `json:"container_id"` // id of the container to join network.
}
// NetworkInterface contains network configs for a driver
type NetworkInterface struct {
MacAddress string `json:"mac"`
Bridge string `json:"bridge"`
IPAddress string `json:"ip"`
// PortBindings is the port mapping between the exposed port in the
// container and the port on the host.
PortBindings nat.PortMap `json:"port_bindings"`
}
// Command wraps an os/exec.Cmd to add more metadata
type Command struct {
CommonCommand
// Fields below here are platform specific
FirstStart bool `json:"first_start"` // Optimization for first boot of Windows
Hostname string `json:"hostname"` // Windows sets the hostname in the execdriver
LayerFolder string `json:"layer_folder"` // Layer folder for a command
LayerPaths []string `json:"layer_paths"` // Layer paths for a command
Isolation string `json:"isolation"` // Isolation technology for the container
ArgsEscaped bool `json:"args_escaped"` // True if args are already escaped
HvPartition bool `json:"hv_partition"` // True if it's an hypervisor partition
EpList []string `json:"endpoints"` // List of network endpoints for HNS
}
// ExitStatus provides exit reasons for a container.
type ExitStatus struct {
// The exit code with which the container exited.
ExitCode int
}

View file

@ -1,15 +0,0 @@
// +build freebsd
package execdrivers
import (
"fmt"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/sysinfo"
)
// NewDriver returns a new execdriver.Driver from the given name configured with the provided options.
func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
return nil, fmt.Errorf("jail driver not yet supported on FreeBSD")
}

View file

@ -1,16 +0,0 @@
// +build linux
package execdrivers
import (
"path"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/execdriver/native"
"github.com/docker/docker/pkg/sysinfo"
)
// NewDriver returns a new execdriver.Driver from the given name configured with the provided options.
func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
return native.NewDriver(path.Join(root, "execdriver", "native"), options)
}

View file

@ -1,14 +0,0 @@
// +build windows
package execdrivers
import (
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/daemon/execdriver/windows"
"github.com/docker/docker/pkg/sysinfo"
)
// NewDriver returns a new execdriver.Driver from the given name configured with the provided options.
func NewDriver(options []string, root, libPath string, sysInfo *sysinfo.SysInfo) (execdriver.Driver, error) {
return windows.NewDriver(root, options)
}

View file

@ -1,514 +0,0 @@
// +build linux,cgo
package native
import (
"fmt"
"path/filepath"
"strings"
"syscall"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/profiles/seccomp"
"github.com/docker/docker/volume"
"github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/devices"
)
// createContainer populates and configures the container type with the
// data provided by the execdriver.Command
func (d *Driver) createContainer(c *execdriver.Command, hooks execdriver.Hooks) (container *configs.Config, err error) {
container = execdriver.InitContainer(c)
if err := d.createIpc(container, c); err != nil {
return nil, err
}
if err := d.createPid(container, c); err != nil {
return nil, err
}
if err := d.createUTS(container, c); err != nil {
return nil, err
}
if err := d.setupRemappedRoot(container, c); err != nil {
return nil, err
}
if err := d.createNetwork(container, c, hooks); err != nil {
return nil, err
}
if c.ProcessConfig.Privileged {
if !container.Readonlyfs {
// clear readonly for /sys
for i := range container.Mounts {
if container.Mounts[i].Destination == "/sys" {
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
}
}
container.ReadonlyPaths = nil
}
// clear readonly for cgroup
for i := range container.Mounts {
if container.Mounts[i].Device == "cgroup" {
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
}
}
container.MaskPaths = nil
if err := d.setPrivileged(container); err != nil {
return nil, err
}
} else {
if err := d.setCapabilities(container, c); err != nil {
return nil, err
}
if c.SeccompProfile == "" {
container.Seccomp, err = seccomp.GetDefaultProfile()
if err != nil {
return nil, err
}
}
}
// add CAP_ prefix to all caps for new libcontainer update to match
// the spec format.
for i, s := range container.Capabilities {
if !strings.HasPrefix(s, "CAP_") {
container.Capabilities[i] = fmt.Sprintf("CAP_%s", s)
}
}
container.AdditionalGroups = c.GroupAdd
if c.AppArmorProfile != "" {
container.AppArmorProfile = c.AppArmorProfile
}
if c.SeccompProfile != "" && c.SeccompProfile != "unconfined" {
container.Seccomp, err = seccomp.LoadProfile(c.SeccompProfile)
if err != nil {
return nil, err
}
}
if err := execdriver.SetupCgroups(container, c); err != nil {
return nil, err
}
container.OomScoreAdj = c.OomScoreAdj
if container.Readonlyfs {
for i := range container.Mounts {
switch container.Mounts[i].Destination {
case "/proc", "/dev", "/dev/pts", "/dev/mqueue":
continue
}
container.Mounts[i].Flags |= syscall.MS_RDONLY
}
/* These paths must be remounted as r/o */
container.ReadonlyPaths = append(container.ReadonlyPaths, "/dev")
}
if err := d.setupMounts(container, c); err != nil {
return nil, err
}
d.setupLabels(container, c)
d.setupRlimits(container, c)
container.NoNewPrivileges = c.NoNewPrivileges
return container, nil
}
func (d *Driver) createNetwork(container *configs.Config, c *execdriver.Command, hooks execdriver.Hooks) error {
if c.Network == nil {
return nil
}
if c.Network.ContainerID != "" {
d.Lock()
active := d.activeContainers[c.Network.ContainerID]
d.Unlock()
if active == nil {
return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID)
}
state, err := active.State()
if err != nil {
return err
}
container.Namespaces.Add(configs.NEWNET, state.NamespacePaths[configs.NEWNET])
return nil
}
if c.Network.NamespacePath != "" {
container.Namespaces.Add(configs.NEWNET, c.Network.NamespacePath)
return nil
}
// only set up prestart hook if the namespace path is not set (this should be
// all cases *except* for --net=host shared networking)
container.Hooks = &configs.Hooks{
Prestart: []configs.Hook{
configs.NewFunctionHook(func(s configs.HookState) error {
if len(hooks.PreStart) > 0 {
for _, fnHook := range hooks.PreStart {
// A closed channel for OOM is returned here as it will be
// non-blocking and return the correct result when read.
chOOM := make(chan struct{})
close(chOOM)
if err := fnHook(&c.ProcessConfig, s.Pid, chOOM); err != nil {
return err
}
}
}
return nil
}),
},
}
return nil
}
func (d *Driver) createIpc(container *configs.Config, c *execdriver.Command) error {
if c.Ipc.HostIpc {
container.Namespaces.Remove(configs.NEWIPC)
return nil
}
if c.Ipc.ContainerID != "" {
d.Lock()
active := d.activeContainers[c.Ipc.ContainerID]
d.Unlock()
if active == nil {
return fmt.Errorf("%s is not a valid running container to join", c.Ipc.ContainerID)
}
state, err := active.State()
if err != nil {
return err
}
container.Namespaces.Add(configs.NEWIPC, state.NamespacePaths[configs.NEWIPC])
}
return nil
}
func (d *Driver) createPid(container *configs.Config, c *execdriver.Command) error {
if c.Pid.HostPid {
container.Namespaces.Remove(configs.NEWPID)
return nil
}
return nil
}
func (d *Driver) createUTS(container *configs.Config, c *execdriver.Command) error {
if c.UTS.HostUTS {
container.Namespaces.Remove(configs.NEWUTS)
container.Hostname = ""
return nil
}
return nil
}
func (d *Driver) setupRemappedRoot(container *configs.Config, c *execdriver.Command) error {
if c.RemappedRoot.UID == 0 {
container.Namespaces.Remove(configs.NEWUSER)
return nil
}
// convert the Docker daemon id map to the libcontainer variant of the same struct
// this keeps us from having to import libcontainer code across Docker client + daemon packages
cuidMaps := []configs.IDMap{}
cgidMaps := []configs.IDMap{}
for _, idMap := range c.UIDMapping {
cuidMaps = append(cuidMaps, configs.IDMap(idMap))
}
for _, idMap := range c.GIDMapping {
cgidMaps = append(cgidMaps, configs.IDMap(idMap))
}
container.UidMappings = cuidMaps
container.GidMappings = cgidMaps
for _, node := range container.Devices {
node.Uid = uint32(c.RemappedRoot.UID)
node.Gid = uint32(c.RemappedRoot.GID)
}
// TODO: until a kernel/mount solution exists for handling remount in a user namespace,
// we must clear the readonly flag for the cgroups mount (@mrunalp concurs)
for i := range container.Mounts {
if container.Mounts[i].Device == "cgroup" {
container.Mounts[i].Flags &= ^syscall.MS_RDONLY
}
}
return nil
}
func (d *Driver) setPrivileged(container *configs.Config) (err error) {
container.Capabilities = execdriver.GetAllCapabilities()
container.Cgroups.Resources.AllowAllDevices = true
hostDevices, err := devices.HostDevices()
if err != nil {
return err
}
container.Devices = hostDevices
if apparmor.IsEnabled() {
container.AppArmorProfile = "unconfined"
}
return nil
}
func (d *Driver) setCapabilities(container *configs.Config, c *execdriver.Command) (err error) {
container.Capabilities, err = execdriver.TweakCapabilities(container.Capabilities, c.CapAdd, c.CapDrop)
return err
}
func (d *Driver) setupRlimits(container *configs.Config, c *execdriver.Command) {
if c.Resources == nil {
return
}
for _, rlimit := range c.Resources.Rlimits {
container.Rlimits = append(container.Rlimits, configs.Rlimit{
Type: rlimit.Type,
Hard: rlimit.Hard,
Soft: rlimit.Soft,
})
}
}
// If rootfs mount propagation is RPRIVATE, that means all the volumes are
// going to be private anyway. There is no need to apply per volume
// propagation on top. This is just an optimization so that cost of per volume
// propagation is paid only if user decides to make some volume non-private
// which will force rootfs mount propagation to be non RPRIVATE.
func checkResetVolumePropagation(container *configs.Config) {
if container.RootPropagation != mount.RPRIVATE {
return
}
for _, m := range container.Mounts {
m.PropagationFlags = nil
}
}
func getMountInfo(mountinfo []*mount.Info, dir string) *mount.Info {
for _, m := range mountinfo {
if m.Mountpoint == dir {
return m
}
}
return nil
}
// Get the source mount point of directory passed in as argument. Also return
// optional fields.
func getSourceMount(source string) (string, string, error) {
// Ensure any symlinks are resolved.
sourcePath, err := filepath.EvalSymlinks(source)
if err != nil {
return "", "", err
}
mountinfos, err := mount.GetMounts()
if err != nil {
return "", "", err
}
mountinfo := getMountInfo(mountinfos, sourcePath)
if mountinfo != nil {
return sourcePath, mountinfo.Optional, nil
}
path := sourcePath
for {
path = filepath.Dir(path)
mountinfo = getMountInfo(mountinfos, path)
if mountinfo != nil {
return path, mountinfo.Optional, nil
}
if path == "/" {
break
}
}
// If we are here, we did not find parent mount. Something is wrong.
return "", "", fmt.Errorf("Could not find source mount of %s", source)
}
// Ensure mount point on which path is mounted, is shared.
func ensureShared(path string) error {
sharedMount := false
sourceMount, optionalOpts, err := getSourceMount(path)
if err != nil {
return err
}
// Make sure source mount point is shared.
optsSplit := strings.Split(optionalOpts, " ")
for _, opt := range optsSplit {
if strings.HasPrefix(opt, "shared:") {
sharedMount = true
break
}
}
if !sharedMount {
return fmt.Errorf("Path %s is mounted on %s but it is not a shared mount.", path, sourceMount)
}
return nil
}
// Ensure mount point on which path is mounted, is either shared or slave.
func ensureSharedOrSlave(path string) error {
sharedMount := false
slaveMount := false
sourceMount, optionalOpts, err := getSourceMount(path)
if err != nil {
return err
}
// Make sure source mount point is shared.
optsSplit := strings.Split(optionalOpts, " ")
for _, opt := range optsSplit {
if strings.HasPrefix(opt, "shared:") {
sharedMount = true
break
} else if strings.HasPrefix(opt, "master:") {
slaveMount = true
break
}
}
if !sharedMount && !slaveMount {
return fmt.Errorf("Path %s is mounted on %s but it is not a shared or slave mount.", path, sourceMount)
}
return nil
}
func (d *Driver) setupMounts(container *configs.Config, c *execdriver.Command) error {
userMounts := make(map[string]struct{})
for _, m := range c.Mounts {
userMounts[m.Destination] = struct{}{}
}
// Filter out mounts that are overridden by user supplied mounts
var defaultMounts []*configs.Mount
_, mountDev := userMounts["/dev"]
for _, m := range container.Mounts {
if _, ok := userMounts[m.Destination]; !ok {
if mountDev && strings.HasPrefix(m.Destination, "/dev/") {
container.Devices = nil
continue
}
defaultMounts = append(defaultMounts, m)
}
}
container.Mounts = defaultMounts
mountPropagationMap := map[string]int{
"private": mount.PRIVATE,
"rprivate": mount.RPRIVATE,
"shared": mount.SHARED,
"rshared": mount.RSHARED,
"slave": mount.SLAVE,
"rslave": mount.RSLAVE,
}
for _, m := range c.Mounts {
for _, cm := range container.Mounts {
if cm.Destination == m.Destination {
return fmt.Errorf("Duplicate mount point '%s'", m.Destination)
}
}
if m.Source == "tmpfs" {
var (
data = "size=65536k"
flags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
err error
)
if m.Data != "" {
flags, data, err = mount.ParseTmpfsOptions(m.Data)
if err != nil {
return err
}
}
container.Mounts = append(container.Mounts, &configs.Mount{
Source: m.Source,
Destination: m.Destination,
Data: data,
Device: "tmpfs",
Flags: flags,
PropagationFlags: []int{mountPropagationMap[volume.DefaultPropagationMode]},
})
continue
}
flags := syscall.MS_BIND | syscall.MS_REC
var pFlag int
if !m.Writable {
flags |= syscall.MS_RDONLY
}
// Determine property of RootPropagation based on volume
// properties. If a volume is shared, then keep root propagation
// shared. This should work for slave and private volumes too.
//
// For slave volumes, it can be either [r]shared/[r]slave.
//
// For private volumes any root propagation value should work.
pFlag = mountPropagationMap[m.Propagation]
if pFlag == mount.SHARED || pFlag == mount.RSHARED {
if err := ensureShared(m.Source); err != nil {
return err
}
rootpg := container.RootPropagation
if rootpg != mount.SHARED && rootpg != mount.RSHARED {
execdriver.SetRootPropagation(container, mount.SHARED)
}
} else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE {
if err := ensureSharedOrSlave(m.Source); err != nil {
return err
}
rootpg := container.RootPropagation
if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE {
execdriver.SetRootPropagation(container, mount.RSLAVE)
}
}
mount := &configs.Mount{
Source: m.Source,
Destination: m.Destination,
Device: "bind",
Flags: flags,
}
if pFlag != 0 {
mount.PropagationFlags = []int{pFlag}
}
container.Mounts = append(container.Mounts, mount)
}
checkResetVolumePropagation(container)
return nil
}
func (d *Driver) setupLabels(container *configs.Config, c *execdriver.Command) {
container.ProcessLabel = c.ProcessLabel
container.MountLabel = c.MountLabel
}

View file

@ -1,606 +0,0 @@
// +build linux,cgo
package native
import (
"fmt"
"io"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
"syscall"
"time"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/docker/pkg/pools"
"github.com/docker/docker/pkg/reexec"
sysinfo "github.com/docker/docker/pkg/system"
"github.com/docker/docker/pkg/term"
aaprofile "github.com/docker/docker/profiles/apparmor"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
"github.com/opencontainers/runc/libcontainer/configs"
"github.com/opencontainers/runc/libcontainer/system"
"github.com/opencontainers/runc/libcontainer/utils"
)
// Define constants for native driver
const (
DriverName = "native"
Version = "0.2"
defaultApparmorProfile = "docker-default"
)
// Driver contains all information for native driver,
// it implements execdriver.Driver.
type Driver struct {
root string
activeContainers map[string]libcontainer.Container
machineMemory int64
factory libcontainer.Factory
sync.Mutex
}
// NewDriver returns a new native driver, called from NewDriver of execdriver.
func NewDriver(root string, options []string) (*Driver, error) {
meminfo, err := sysinfo.ReadMemInfo()
if err != nil {
return nil, err
}
if err := sysinfo.MkdirAll(root, 0700); err != nil {
return nil, err
}
if apparmor.IsEnabled() {
if err := aaprofile.InstallDefault(defaultApparmorProfile); err != nil {
apparmorProfiles := []string{defaultApparmorProfile}
// Allow daemon to run if loading failed, but are active
// (possibly through another run, manually, or via system startup)
for _, policy := range apparmorProfiles {
if err := aaprofile.IsLoaded(policy); err != nil {
return nil, fmt.Errorf("AppArmor enabled on system but the %s profile could not be loaded.", policy)
}
}
}
}
// choose cgroup manager
// this makes sure there are no breaking changes to people
// who upgrade from versions without native.cgroupdriver opt
cgm := libcontainer.Cgroupfs
// parse the options
for _, option := range options {
key, val, err := parsers.ParseKeyValueOpt(option)
if err != nil {
return nil, err
}
key = strings.ToLower(key)
switch key {
case "native.cgroupdriver":
// override the default if they set options
switch val {
case "systemd":
if systemd.UseSystemd() {
cgm = libcontainer.SystemdCgroups
} else {
// warn them that they chose the wrong driver
logrus.Warn("You cannot use systemd as native.cgroupdriver, using cgroupfs instead")
}
case "cgroupfs":
cgm = libcontainer.Cgroupfs
default:
return nil, fmt.Errorf("Unknown native.cgroupdriver given %q. try cgroupfs or systemd", val)
}
default:
return nil, fmt.Errorf("Unknown option %s\n", key)
}
}
f, err := libcontainer.New(
root,
cgm,
libcontainer.InitPath(reexec.Self(), DriverName),
)
if err != nil {
return nil, err
}
return &Driver{
root: root,
activeContainers: make(map[string]libcontainer.Container),
machineMemory: meminfo.MemTotal,
factory: f,
}, nil
}
// Run implements the exec driver Driver interface,
// it calls libcontainer APIs to run a container.
func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
destroyed := false
var err error
c.TmpDir, err = ioutil.TempDir("", c.ID)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
defer os.RemoveAll(c.TmpDir)
// take the Command and populate the libcontainer.Config from it
container, err := d.createContainer(c, hooks)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
p := &libcontainer.Process{
Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...),
Env: c.ProcessConfig.Env,
Cwd: c.WorkingDir,
User: c.ProcessConfig.User,
}
wg := sync.WaitGroup{}
writers, err := setupPipes(container, &c.ProcessConfig, p, pipes, &wg)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
cont, err := d.factory.Create(c.ID, container)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
if err := cont.Start(p); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
d.Lock()
d.activeContainers[c.ID] = cont
d.Unlock()
defer func() {
if !destroyed {
cont.Destroy()
}
d.cleanContainer(c.ID)
}()
//close the write end of any opened pipes now that they are dup'ed into the container
for _, writer := range writers {
writer.Close()
}
// 'oom' is used to emit 'oom' events to the eventstream, 'oomKilled' is used
// to set the 'OOMKilled' flag in state
oom := notifyOnOOM(cont)
oomKilled := notifyOnOOM(cont)
if hooks.Start != nil {
pid, err := p.Pid()
if err != nil {
p.Signal(os.Kill)
p.Wait()
return execdriver.ExitStatus{ExitCode: -1}, err
}
hooks.Start(&c.ProcessConfig, pid, oom)
}
waitF := p.Wait
if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) {
// we need such hack for tracking processes with inherited fds,
// because cmd.Wait() waiting for all streams to be copied
waitF = waitInPIDHost(p, cont)
}
ps, err := waitF()
if err != nil {
execErr, ok := err.(*exec.ExitError)
if !ok {
return execdriver.ExitStatus{ExitCode: -1}, err
}
ps = execErr.ProcessState
}
// wait for all IO goroutine copiers to finish
wg.Wait()
cont.Destroy()
destroyed = true
// oomKilled will have an oom event if any process within the container was
// OOM killed at any time, not only if the init process OOMed.
//
// Perhaps we only want the OOMKilled flag to be set if the OOM
// resulted in a container death, but there isn't a good way to do this
// because the kernel's cgroup oom notification does not provide information
// such as the PID. This could be heuristically done by checking that the OOM
// happened within some very small time slice for the container dying (and
// optionally exit-code 137), but I don't think the cgroup oom notification
// can be used to reliably determine this
//
// Even if there were multiple OOMs, it's sufficient to read one value
// because libcontainer's oom notify will discard the channel after the
// cgroup is destroyed
_, oomKill := <-oomKilled
return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil
}
// notifyOnOOM returns a channel that signals if the container received an OOM notification
// for any process. If it is unable to subscribe to OOM notifications then a closed
// channel is returned as it will be non-blocking and return the correct result when read.
func notifyOnOOM(container libcontainer.Container) <-chan struct{} {
oom, err := container.NotifyOOM()
if err != nil {
logrus.Warnf("Your kernel does not support OOM notifications: %s", err)
c := make(chan struct{})
close(c)
return c
}
return oom
}
func killCgroupProcs(c libcontainer.Container) {
var procs []*os.Process
if err := c.Pause(); err != nil {
logrus.Warn(err)
}
pids, err := c.Processes()
if err != nil {
// don't care about childs if we can't get them, this is mostly because cgroup already deleted
logrus.Warnf("Failed to get processes from container %s: %v", c.ID(), err)
}
for _, pid := range pids {
if p, err := os.FindProcess(pid); err == nil {
procs = append(procs, p)
if err := p.Kill(); err != nil {
logrus.Warn(err)
}
}
}
if err := c.Resume(); err != nil {
logrus.Warn(err)
}
for _, p := range procs {
if _, err := p.Wait(); err != nil {
logrus.Warn(err)
}
}
}
func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) {
return func() (*os.ProcessState, error) {
pid, err := p.Pid()
if err != nil {
return nil, err
}
process, err := os.FindProcess(pid)
s, err := process.Wait()
if err != nil {
execErr, ok := err.(*exec.ExitError)
if !ok {
return s, err
}
s = execErr.ProcessState
}
killCgroupProcs(c)
p.Wait()
return s, err
}
}
// Kill implements the exec driver Driver interface.
func (d *Driver) Kill(c *execdriver.Command, sig int) error {
d.Lock()
active := d.activeContainers[c.ID]
d.Unlock()
if active == nil {
return fmt.Errorf("active container for %s does not exist", c.ID)
}
state, err := active.State()
if err != nil {
return err
}
if state.InitProcessPid == -1 {
return fmt.Errorf("avoid sending signal %d to container %s with pid -1", sig, c.ID)
}
return syscall.Kill(state.InitProcessPid, syscall.Signal(sig))
}
// Pause implements the exec driver Driver interface,
// it calls libcontainer API to pause a container.
func (d *Driver) Pause(c *execdriver.Command) error {
d.Lock()
active := d.activeContainers[c.ID]
d.Unlock()
if active == nil {
return fmt.Errorf("active container for %s does not exist", c.ID)
}
return active.Pause()
}
// Unpause implements the exec driver Driver interface,
// it calls libcontainer API to unpause a container.
func (d *Driver) Unpause(c *execdriver.Command) error {
d.Lock()
active := d.activeContainers[c.ID]
d.Unlock()
if active == nil {
return fmt.Errorf("active container for %s does not exist", c.ID)
}
return active.Resume()
}
// Terminate implements the exec driver Driver interface.
func (d *Driver) Terminate(c *execdriver.Command) error {
defer d.cleanContainer(c.ID)
container, err := d.factory.Load(c.ID)
if err != nil {
return err
}
defer container.Destroy()
state, err := container.State()
if err != nil {
return err
}
pid := state.InitProcessPid
currentStartTime, err := system.GetProcessStartTime(pid)
if err != nil {
return err
}
if state.InitProcessStartTime == currentStartTime {
err = syscall.Kill(pid, 9)
syscall.Wait4(pid, nil, 0, nil)
}
return err
}
// Name implements the exec driver Driver interface.
func (d *Driver) Name() string {
return fmt.Sprintf("%s-%s", DriverName, Version)
}
// GetPidsForContainer implements the exec driver Driver interface.
func (d *Driver) GetPidsForContainer(id string) ([]int, error) {
d.Lock()
active := d.activeContainers[id]
d.Unlock()
if active == nil {
return nil, fmt.Errorf("active container for %s does not exist", id)
}
return active.Processes()
}
func (d *Driver) cleanContainer(id string) error {
d.Lock()
delete(d.activeContainers, id)
d.Unlock()
return os.RemoveAll(filepath.Join(d.root, id))
}
func (d *Driver) createContainerRoot(id string) error {
return os.MkdirAll(filepath.Join(d.root, id), 0655)
}
// Clean implements the exec driver Driver interface.
func (d *Driver) Clean(id string) error {
return os.RemoveAll(filepath.Join(d.root, id))
}
// Stats implements the exec driver Driver interface.
func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) {
d.Lock()
c := d.activeContainers[id]
d.Unlock()
if c == nil {
return nil, execdriver.ErrNotRunning
}
now := time.Now()
stats, err := c.Stats()
if err != nil {
return nil, err
}
memoryLimit := c.Config().Cgroups.Resources.Memory
// if the container does not have any memory limit specified set the
// limit to the machines memory
if memoryLimit == 0 {
memoryLimit = d.machineMemory
}
return &execdriver.ResourceStats{
Stats: stats,
Read: now,
MemoryLimit: memoryLimit,
}, nil
}
// Update updates configs for a container
func (d *Driver) Update(c *execdriver.Command) error {
d.Lock()
cont := d.activeContainers[c.ID]
d.Unlock()
if cont == nil {
return execdriver.ErrNotRunning
}
config := cont.Config()
if err := execdriver.SetupCgroups(&config, c); err != nil {
return err
}
if err := cont.Set(config); err != nil {
return err
}
return nil
}
// TtyConsole implements the exec driver Terminal interface.
type TtyConsole struct {
console libcontainer.Console
}
// NewTtyConsole returns a new TtyConsole struct.
func NewTtyConsole(console libcontainer.Console, pipes *execdriver.Pipes, wg *sync.WaitGroup) (*TtyConsole, error) {
tty := &TtyConsole{
console: console,
}
if err := tty.AttachPipes(pipes, wg); err != nil {
tty.Close()
return nil, err
}
return tty, nil
}
// Resize implements Resize method of Terminal interface
func (t *TtyConsole) Resize(h, w int) error {
return term.SetWinsize(t.console.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
}
// AttachPipes attaches given pipes to TtyConsole
func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes, wg *sync.WaitGroup) error {
wg.Add(1)
go func() {
defer wg.Done()
if wb, ok := pipes.Stdout.(interface {
CloseWriters() error
}); ok {
defer wb.CloseWriters()
}
pools.Copy(pipes.Stdout, t.console)
}()
if pipes.Stdin != nil {
go func() {
pools.Copy(t.console, pipes.Stdin)
pipes.Stdin.Close()
}()
}
return nil
}
// Close implements Close method of Terminal interface
func (t *TtyConsole) Close() error {
return t.console.Close()
}
func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes, wg *sync.WaitGroup) ([]io.WriteCloser, error) {
writers := []io.WriteCloser{}
rootuid, err := container.HostUID()
if err != nil {
return writers, err
}
if processConfig.Tty {
cons, err := p.NewConsole(rootuid)
if err != nil {
return writers, err
}
term, err := NewTtyConsole(cons, pipes, wg)
if err != nil {
return writers, err
}
processConfig.Terminal = term
return writers, nil
}
// not a tty--set up stdio pipes
term := &execdriver.StdConsole{}
processConfig.Terminal = term
// if we are not in a user namespace, there is no reason to go through
// the hassle of setting up os-level pipes with proper (remapped) ownership
// so we will do the prior shortcut for non-userns containers
if rootuid == 0 {
p.Stdout = pipes.Stdout
p.Stderr = pipes.Stderr
r, w, err := os.Pipe()
if err != nil {
return writers, err
}
if pipes.Stdin != nil {
go func() {
io.Copy(w, pipes.Stdin)
w.Close()
}()
p.Stdin = r
}
return writers, nil
}
// if we have user namespaces enabled (rootuid != 0), we will set
// up os pipes for stderr, stdout, stdin so we can chown them to
// the proper ownership to allow for proper access to the underlying
// fds
var fds []uintptr
copyPipes := func(out io.Writer, in io.ReadCloser) {
defer wg.Done()
io.Copy(out, in)
in.Close()
}
//setup stdout
r, w, err := os.Pipe()
if err != nil {
w.Close()
return writers, err
}
writers = append(writers, w)
fds = append(fds, r.Fd(), w.Fd())
if pipes.Stdout != nil {
wg.Add(1)
go copyPipes(pipes.Stdout, r)
}
term.Closers = append(term.Closers, r)
p.Stdout = w
//setup stderr
r, w, err = os.Pipe()
if err != nil {
w.Close()
return writers, err
}
writers = append(writers, w)
fds = append(fds, r.Fd(), w.Fd())
if pipes.Stderr != nil {
wg.Add(1)
go copyPipes(pipes.Stderr, r)
}
term.Closers = append(term.Closers, r)
p.Stderr = w
//setup stdin
r, w, err = os.Pipe()
if err != nil {
r.Close()
return writers, err
}
fds = append(fds, r.Fd(), w.Fd())
if pipes.Stdin != nil {
go func() {
io.Copy(w, pipes.Stdin)
w.Close()
}()
p.Stdin = r
}
for _, fd := range fds {
if err := syscall.Fchown(int(fd), rootuid, rootuid); err != nil {
return writers, fmt.Errorf("Failed to chown pipes fd: %v", err)
}
}
return writers, nil
}
// SupportsHooks implements the execdriver Driver interface.
// The libcontainer/runC-based native execdriver does exploit the hook mechanism
func (d *Driver) SupportsHooks() bool {
return true
}

View file

@ -1,14 +0,0 @@
// +build !linux
package native
import (
"fmt"
"github.com/docker/docker/daemon/execdriver"
)
// NewDriver returns a new native driver, called from NewDriver of execdriver.
func NewDriver(root string, options []string) (execdriver.Driver, error) {
return nil, fmt.Errorf("native driver not supported on non-linux")
}

View file

@ -1,14 +0,0 @@
// +build linux,!cgo
package native
import (
"fmt"
"github.com/docker/docker/daemon/execdriver"
)
// NewDriver returns a new native driver, called from NewDriver of execdriver.
func NewDriver(root string, options []string) (execdriver.Driver, error) {
return nil, fmt.Errorf("native driver not supported on non-linux")
}

View file

@ -1,96 +0,0 @@
// +build linux
package native
import (
"fmt"
"os"
"os/exec"
"strings"
"sync"
"syscall"
"github.com/docker/docker/daemon/execdriver"
"github.com/opencontainers/runc/libcontainer"
// Blank import 'nsenter' so that init in that package will call c
// function 'nsexec()' to do 'setns' before Go runtime take over,
// it's used for join to exist ns like 'docker exec' command.
_ "github.com/opencontainers/runc/libcontainer/nsenter"
"github.com/opencontainers/runc/libcontainer/utils"
)
// Exec implements the exec driver Driver interface,
// it calls libcontainer APIs to execute a container.
func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
active := d.activeContainers[c.ID]
if active == nil {
return -1, fmt.Errorf("No active container exists with ID %s", c.ID)
}
user := processConfig.User
if c.RemappedRoot.UID != 0 && user == "" {
//if user namespaces are enabled, set user explicitly so uid/gid is set to 0
//otherwise we end up with the overflow id and no permissions (65534)
user = "0"
}
p := &libcontainer.Process{
Args: append([]string{processConfig.Entrypoint}, processConfig.Arguments...),
Env: c.ProcessConfig.Env,
Cwd: c.WorkingDir,
User: user,
}
if processConfig.Privileged {
p.Capabilities = execdriver.GetAllCapabilities()
}
// add CAP_ prefix to all caps for new libcontainer update to match
// the spec format.
for i, s := range p.Capabilities {
if !strings.HasPrefix(s, "CAP_") {
p.Capabilities[i] = fmt.Sprintf("CAP_%s", s)
}
}
config := active.Config()
wg := sync.WaitGroup{}
writers, err := setupPipes(&config, processConfig, p, pipes, &wg)
if err != nil {
return -1, err
}
if err := active.Start(p); err != nil {
return -1, err
}
//close the write end of any opened pipes now that they are dup'ed into the container
for _, writer := range writers {
writer.Close()
}
if hooks.Start != nil {
pid, err := p.Pid()
if err != nil {
p.Signal(os.Kill)
p.Wait()
return -1, err
}
// A closed channel for OOM is returned here as it will be
// non-blocking and return the correct result when read.
chOOM := make(chan struct{})
close(chOOM)
hooks.Start(&c.ProcessConfig, pid, chOOM)
}
ps, err := p.Wait()
if err != nil {
exitErr, ok := err.(*exec.ExitError)
if !ok {
return -1, err
}
ps = exitErr.ProcessState
}
// wait for all IO goroutine copiers to finish
wg.Wait()
return utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), nil
}

View file

@ -1,40 +0,0 @@
// +build linux
package native
import (
"fmt"
"os"
"runtime"
"github.com/docker/docker/pkg/reexec"
"github.com/opencontainers/runc/libcontainer"
)
func init() {
reexec.Register(DriverName, initializer)
}
func fatal(err error) {
if lerr, ok := err.(libcontainer.Error); ok {
lerr.Detail(os.Stderr)
os.Exit(1)
}
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
func initializer() {
runtime.GOMAXPROCS(1)
runtime.LockOSThread()
factory, err := libcontainer.New("")
if err != nil {
fatal(err)
}
if err := factory.StartInitialization(); err != nil {
fatal(err)
}
panic("unreachable")
}

View file

@ -1,106 +0,0 @@
package template
import (
"syscall"
"github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/configs"
)
const defaultMountFlags = syscall.MS_NOEXEC | syscall.MS_NOSUID | syscall.MS_NODEV
// New returns the docker default configuration for libcontainer
func New() *configs.Config {
container := &configs.Config{
Capabilities: []string{
"CHOWN",
"DAC_OVERRIDE",
"FSETID",
"FOWNER",
"MKNOD",
"NET_RAW",
"SETGID",
"SETUID",
"SETFCAP",
"SETPCAP",
"NET_BIND_SERVICE",
"SYS_CHROOT",
"KILL",
"AUDIT_WRITE",
},
Namespaces: configs.Namespaces([]configs.Namespace{
{Type: "NEWNS"},
{Type: "NEWUTS"},
{Type: "NEWIPC"},
{Type: "NEWPID"},
{Type: "NEWNET"},
{Type: "NEWUSER"},
}),
Cgroups: &configs.Cgroup{
ScopePrefix: "docker", // systemd only
Resources: &configs.Resources{
AllowAllDevices: false,
MemorySwappiness: -1,
},
},
Mounts: []*configs.Mount{
{
Source: "proc",
Destination: "/proc",
Device: "proc",
Flags: defaultMountFlags,
},
{
Source: "tmpfs",
Destination: "/dev",
Device: "tmpfs",
Flags: syscall.MS_NOSUID | syscall.MS_STRICTATIME,
Data: "mode=755",
},
{
Source: "devpts",
Destination: "/dev/pts",
Device: "devpts",
Flags: syscall.MS_NOSUID | syscall.MS_NOEXEC,
Data: "newinstance,ptmxmode=0666,mode=0620,gid=5",
},
{
Source: "mqueue",
Destination: "/dev/mqueue",
Device: "mqueue",
Flags: defaultMountFlags,
},
{
Source: "sysfs",
Destination: "/sys",
Device: "sysfs",
Flags: defaultMountFlags | syscall.MS_RDONLY,
},
{
Source: "cgroup",
Destination: "/sys/fs/cgroup",
Device: "cgroup",
Flags: defaultMountFlags | syscall.MS_RDONLY,
},
},
MaskPaths: []string{
"/proc/kcore",
"/proc/latency_stats",
"/proc/timer_stats",
},
ReadonlyPaths: []string{
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
},
}
if apparmor.IsEnabled() {
container.AppArmorProfile = "docker-default"
}
return container
}

View file

@ -1,3 +0,0 @@
// +build !linux
package template

View file

@ -1,24 +0,0 @@
package execdriver
import (
"io"
)
// Pipes is a wrapper around a container's output for
// stdin, stdout, stderr
type Pipes struct {
Stdin io.ReadCloser
Stdout, Stderr io.Writer
}
// NewPipes returns a wrapper around a container's output
func NewPipes(stdin io.ReadCloser, stdout, stderr io.Writer, useStdin bool) *Pipes {
p := &Pipes{
Stdout: stdout,
Stderr: stderr,
}
if useStdin {
p.Stdin = stdin
}
return p
}

View file

@ -1,55 +0,0 @@
package execdriver
import (
"io"
"os/exec"
)
// StdConsole defines standard console operations for execdriver
type StdConsole struct {
// Closers holds io.Closer references for closing at terminal close time
Closers []io.Closer
}
// NewStdConsole returns a new StdConsole struct
func NewStdConsole(processConfig *ProcessConfig, pipes *Pipes) (*StdConsole, error) {
std := &StdConsole{}
if err := std.AttachPipes(&processConfig.Cmd, pipes); err != nil {
return nil, err
}
return std, nil
}
// AttachPipes attaches given pipes to exec.Cmd
func (s *StdConsole) AttachPipes(command *exec.Cmd, pipes *Pipes) error {
command.Stdout = pipes.Stdout
command.Stderr = pipes.Stderr
if pipes.Stdin != nil {
stdin, err := command.StdinPipe()
if err != nil {
return err
}
go func() {
defer stdin.Close()
io.Copy(stdin, pipes.Stdin)
}()
}
return nil
}
// Resize implements Resize method of Terminal interface
func (s *StdConsole) Resize(h, w int) error {
// we do not need to resize a non tty
return nil
}
// Close implements Close method of Terminal interface
func (s *StdConsole) Close() error {
for _, c := range s.Closers {
c.Close()
}
return nil
}

View file

@ -1,8 +0,0 @@
// +build windows
package windows
// Clean implements the exec driver Driver interface.
func (d *Driver) Clean(id string) error {
return nil
}

View file

@ -1,36 +0,0 @@
//+build windows
package windows
import (
"errors"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
)
// createCommandLine creates a command line from the Entrypoint and args
// of the ProcessConfig. It escapes the arguments if they are not already
// escaped
func createCommandLine(processConfig *execdriver.ProcessConfig, alreadyEscaped bool) (commandLine string, err error) {
// While this should get caught earlier, just in case, validate that we
// have something to run.
if processConfig.Entrypoint == "" {
return "", errors.New("No entrypoint specified")
}
// Build the command line of the process
commandLine = processConfig.Entrypoint
logrus.Debugf("Entrypoint: %s", processConfig.Entrypoint)
for _, arg := range processConfig.Arguments {
logrus.Debugf("appending %s", arg)
if !alreadyEscaped {
arg = syscall.EscapeArg(arg)
}
commandLine += " " + arg
}
logrus.Debugf("commandLine: %s", commandLine)
return commandLine, nil
}

View file

@ -1,89 +0,0 @@
// +build windows
package windows
import (
"fmt"
"syscall"
"github.com/Microsoft/hcsshim"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
)
// Exec implements the exec driver Driver interface.
func (d *Driver) Exec(c *execdriver.Command, processConfig *execdriver.ProcessConfig, pipes *execdriver.Pipes, hooks execdriver.Hooks) (int, error) {
var (
term execdriver.Terminal
err error
exitCode int32
)
active := d.activeContainers[c.ID]
if active == nil {
return -1, fmt.Errorf("Exec - No active container exists with ID %s", c.ID)
}
createProcessParms := hcsshim.CreateProcessParams{
EmulateConsole: processConfig.Tty, // Note NOT c.ProcessConfig.Tty
WorkingDirectory: c.WorkingDir,
}
// Configure the environment for the process // Note NOT c.ProcessConfig.Env
createProcessParms.Environment = setupEnvironmentVariables(processConfig.Env)
// Create the commandline for the process // Note NOT c.ProcessConfig
createProcessParms.CommandLine, err = createCommandLine(processConfig, false)
if err != nil {
return -1, err
}
// Start the command running in the container.
pid, stdin, stdout, stderr, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !processConfig.Tty, createProcessParms)
if err != nil {
// TODO Windows: TP4 Workaround. In Hyper-V containers, there is a limitation
// of one exec per container. This should be fixed post TP4. CreateProcessInComputeSystem
// will return a specific error which we handle here to give a good error message
// back to the user instead of an inactionable "An invalid argument was supplied"
if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err == hcsshim.WSAEINVAL {
return -1, fmt.Errorf("The limit of docker execs per Hyper-V container has been exceeded")
}
logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
return -1, err
}
// Now that the process has been launched, begin copying data to and from
// the named pipes for the std handles.
setupPipes(stdin, stdout, stderr, pipes)
// Note NOT c.ProcessConfig.Tty
if processConfig.Tty {
term = NewTtyConsole(c.ID, pid)
} else {
term = NewStdConsole()
}
processConfig.Terminal = term
// Invoke the start callback
if hooks.Start != nil {
// A closed channel for OOM is returned here as it will be
// non-blocking and return the correct result when read.
chOOM := make(chan struct{})
close(chOOM)
hooks.Start(&c.ProcessConfig, int(pid), chOOM)
}
if exitCode, err = hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite); err != nil {
if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err == syscall.ERROR_BROKEN_PIPE {
logrus.Debugf("Exiting Run() after WaitForProcessInComputeSystem failed with recognised error %s", err)
return hcsshim.WaitErrExecFailed, nil
}
logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err)
return -1, err
}
logrus.Debugln("Exiting Run()", c.ID)
return int(exitCode), nil
}

View file

@ -1,11 +0,0 @@
// +build windows
package windows
import "fmt"
// GetPidsForContainer implements the exec driver Driver interface.
func (d *Driver) GetPidsForContainer(id string) ([]int, error) {
// TODO Windows: Implementation required.
return nil, fmt.Errorf("GetPidsForContainer: GetPidsForContainer() not implemented")
}

View file

@ -1,63 +0,0 @@
// +build windows
package windows
import (
"fmt"
"io"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
)
// General comment. Handling I/O for a container is very different to Linux.
// We use a named pipe to HCS to copy I/O both in and out of the container,
// very similar to how docker daemon communicates with a CLI.
// startStdinCopy asynchronously copies an io.Reader to the container's
// process's stdin pipe and closes the pipe when there is no more data to copy.
func startStdinCopy(dst io.WriteCloser, src io.Reader) {
// Anything that comes from the client stdin should be copied
// across to the stdin named pipe of the container.
go func() {
defer dst.Close()
bytes, err := io.Copy(dst, src)
log := fmt.Sprintf("Copied %d bytes from stdin.", bytes)
if err != nil {
log = log + " err=" + err.Error()
}
logrus.Debugf(log)
}()
}
// startStdouterrCopy asynchronously copies data from the container's process's
// stdout or stderr pipe to an io.Writer and closes the pipe when there is no
// more data to copy.
func startStdouterrCopy(dst io.Writer, src io.ReadCloser, name string) {
// Anything that comes from the container named pipe stdout/err should be copied
// across to the stdout/err of the client
go func() {
defer src.Close()
bytes, err := io.Copy(dst, src)
log := fmt.Sprintf("Copied %d bytes from %s.", bytes, name)
if err != nil {
log = log + " err=" + err.Error()
}
logrus.Debugf(log)
}()
}
// setupPipes starts the asynchronous copying of data to and from the named
// pipes used byt he HCS for the std handles.
func setupPipes(stdin io.WriteCloser, stdout, stderr io.ReadCloser, pipes *execdriver.Pipes) {
if stdin != nil {
startStdinCopy(stdin, pipes.Stdin)
}
if stdout != nil {
startStdouterrCopy(pipes.Stdout, stdout, "stdout")
}
if stderr != nil {
startStdouterrCopy(pipes.Stderr, stderr, "stderr")
}
}

View file

@ -1,19 +0,0 @@
// +build windows
package windows
import (
"fmt"
"github.com/docker/docker/daemon/execdriver"
)
// Pause implements the exec driver Driver interface.
func (d *Driver) Pause(c *execdriver.Command) error {
return fmt.Errorf("Windows: Containers cannot be paused")
}
// Unpause implements the exec driver Driver interface.
func (d *Driver) Unpause(c *execdriver.Command) error {
return fmt.Errorf("Windows: Containers cannot be paused")
}

View file

@ -1,366 +0,0 @@
// +build windows
package windows
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"syscall"
"time"
"github.com/Microsoft/hcsshim"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
)
// defaultContainerNAT is the default name of the container NAT device that is
// preconfigured on the server.
const defaultContainerNAT = "ContainerNAT"
// Win32 error codes that are used for various workarounds
// These really should be ALL_CAPS to match golangs syscall library and standard
// Win32 error conventions, but golint insists on CamelCase.
const (
CoEClassstring = syscall.Errno(0x800401F3) // Invalid class string
ErrorNoNetwork = syscall.Errno(1222) // The network is not present or not started
ErrorBadPathname = syscall.Errno(161) // The specified path is invalid
ErrorInvalidObject = syscall.Errno(0x800710D8) // The object identifier does not represent a valid object
)
type layer struct {
ID string
Path string
}
type portBinding struct {
Protocol string
InternalPort int
ExternalPort int
}
type natSettings struct {
Name string
PortBindings []portBinding
}
type networkConnection struct {
NetworkName string
// TODO Windows: Add Ip4Address string to this structure when hooked up in
// docker CLI. This is present in the HCS JSON handler.
EnableNat bool
Nat natSettings
}
type networkSettings struct {
MacAddress string
}
type device struct {
DeviceType string
Connection interface{}
Settings interface{}
}
type mappedDir struct {
HostPath string
ContainerPath string
ReadOnly bool
}
type containerInit struct {
SystemType string // HCS requires this to be hard-coded to "Container"
Name string // Name of the container. We use the docker ID.
Owner string // The management platform that created this container
IsDummy bool // Used for development purposes.
VolumePath string // Windows volume path for scratch space
Devices []device // Devices used by the container
IgnoreFlushesDuringBoot bool // Optimization hint for container startup in Windows
LayerFolderPath string // Where the layer folders are located
Layers []layer // List of storage layers
ProcessorWeight int64 `json:",omitempty"` // CPU Shares 0..10000 on Windows; where 0 will be omitted and HCS will default.
HostName string // Hostname
MappedDirectories []mappedDir // List of mapped directories (volumes/mounts)
SandboxPath string // Location of unmounted sandbox (used for Hyper-V containers, not Windows Server containers)
HvPartition bool // True if it a Hyper-V Container
EndpointList []string // List of endpoints to be attached to container
}
// defaultOwner is a tag passed to HCS to allow it to differentiate between
// container creator management stacks. We hard code "docker" in the case
// of docker.
const defaultOwner = "docker"
// Run implements the exec driver Driver interface
func (d *Driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, hooks execdriver.Hooks) (execdriver.ExitStatus, error) {
var (
term execdriver.Terminal
err error
)
// Allocate Network only if there is no network interface
cu := &containerInit{
SystemType: "Container",
Name: c.ID,
Owner: defaultOwner,
IsDummy: dummyMode,
VolumePath: c.Rootfs,
IgnoreFlushesDuringBoot: c.FirstStart,
LayerFolderPath: c.LayerFolder,
ProcessorWeight: c.Resources.CPUShares,
HostName: c.Hostname,
EndpointList: c.EpList,
}
cu.HvPartition = c.HvPartition
if cu.HvPartition {
cu.SandboxPath = filepath.Dir(c.LayerFolder)
} else {
cu.VolumePath = c.Rootfs
cu.LayerFolderPath = c.LayerFolder
}
for _, layerPath := range c.LayerPaths {
_, filename := filepath.Split(layerPath)
g, err := hcsshim.NameToGuid(filename)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
cu.Layers = append(cu.Layers, layer{
ID: g.ToString(),
Path: layerPath,
})
}
// Add the mounts (volumes, bind mounts etc) to the structure
mds := make([]mappedDir, len(c.Mounts))
for i, mount := range c.Mounts {
mds[i] = mappedDir{
HostPath: mount.Source,
ContainerPath: mount.Destination,
ReadOnly: !mount.Writable}
}
cu.MappedDirectories = mds
// TODO Windows. At some point, when there is CLI on docker run to
// enable the IP Address of the container to be passed into docker run,
// the IP Address needs to be wired through to HCS in the JSON. It
// would be present in c.Network.Interface.IPAddress. See matching
// TODO in daemon\container_windows.go, function populateCommand.
if c.Network.Interface != nil {
var pbs []portBinding
// Enumerate through the port bindings specified by the user and convert
// them into the internal structure matching the JSON blob that can be
// understood by the HCS.
for i, v := range c.Network.Interface.PortBindings {
proto := strings.ToUpper(i.Proto())
if proto != "TCP" && proto != "UDP" {
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid protocol %s", i.Proto())
}
if len(v) > 1 {
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support more than one host port in NAT settings")
}
for _, v2 := range v {
var (
iPort, ePort int
err error
)
if len(v2.HostIP) != 0 {
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("Windows does not support host IP addresses in NAT settings")
}
if ePort, err = strconv.Atoi(v2.HostPort); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid container port %s: %s", v2.HostPort, err)
}
if iPort, err = strconv.Atoi(i.Port()); err != nil {
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("invalid internal port %s: %s", i.Port(), err)
}
if iPort < 0 || iPort > 65535 || ePort < 0 || ePort > 65535 {
return execdriver.ExitStatus{ExitCode: -1}, fmt.Errorf("specified NAT port is not in allowed range")
}
pbs = append(pbs,
portBinding{ExternalPort: ePort,
InternalPort: iPort,
Protocol: proto})
}
}
// TODO Windows: TP3 workaround. Allow the user to override the name of
// the Container NAT device through an environment variable. This will
// ultimately be a global daemon parameter on Windows, similar to -b
// for the name of the virtual switch (aka bridge).
cn := os.Getenv("DOCKER_CONTAINER_NAT")
if len(cn) == 0 {
cn = defaultContainerNAT
}
dev := device{
DeviceType: "Network",
Connection: &networkConnection{
NetworkName: c.Network.Interface.Bridge,
// TODO Windows: Fixme, next line. Needs HCS fix.
EnableNat: false,
Nat: natSettings{
Name: cn,
PortBindings: pbs,
},
},
}
if c.Network.Interface.MacAddress != "" {
windowsStyleMAC := strings.Replace(
c.Network.Interface.MacAddress, ":", "-", -1)
dev.Settings = networkSettings{
MacAddress: windowsStyleMAC,
}
}
cu.Devices = append(cu.Devices, dev)
} else {
logrus.Debugln("No network interface")
}
configurationb, err := json.Marshal(cu)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
configuration := string(configurationb)
// TODO Windows TP5 timeframe. Remove when TP4 is no longer supported.
// The following a workaround for Windows TP4 which has a networking
// bug which fairly frequently returns an error. Back off and retry.
maxAttempts := 5
for i := 0; i < maxAttempts; i++ {
err = hcsshim.CreateComputeSystem(c.ID, configuration)
if err == nil {
break
}
if !TP4RetryHack {
return execdriver.ExitStatus{ExitCode: -1}, err
}
if herr, ok := err.(*hcsshim.HcsError); ok {
if herr.Err != syscall.ERROR_NOT_FOUND && // Element not found
herr.Err != syscall.ERROR_FILE_NOT_FOUND && // The system cannot find the file specified
herr.Err != ErrorNoNetwork && // The network is not present or not started
herr.Err != ErrorBadPathname && // The specified path is invalid
herr.Err != CoEClassstring && // Invalid class string
herr.Err != ErrorInvalidObject { // The object identifier does not represent a valid object
logrus.Debugln("Failed to create temporary container ", err)
return execdriver.ExitStatus{ExitCode: -1}, err
}
logrus.Warnf("Invoking Windows TP4 retry hack (%d of %d)", i, maxAttempts-1)
time.Sleep(50 * time.Millisecond)
}
}
// Start the container
logrus.Debugln("Starting container ", c.ID)
err = hcsshim.StartComputeSystem(c.ID)
if err != nil {
logrus.Errorf("Failed to start compute system: %s", err)
return execdriver.ExitStatus{ExitCode: -1}, err
}
defer func() {
// Stop the container
if forceKill {
logrus.Debugf("Forcibly terminating container %s", c.ID)
if err := hcsshim.TerminateComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil {
logrus.Warnf("Ignoring error from TerminateComputeSystem %s", err)
}
} else {
logrus.Debugf("Shutting down container %s", c.ID)
if err := hcsshim.ShutdownComputeSystem(c.ID, hcsshim.TimeoutInfinite, "exec-run-defer"); err != nil {
if herr, ok := err.(*hcsshim.HcsError); !ok ||
(herr.Err != hcsshim.ERROR_SHUTDOWN_IN_PROGRESS &&
herr.Err != ErrorBadPathname &&
herr.Err != syscall.ERROR_PATH_NOT_FOUND) {
logrus.Warnf("Ignoring error from ShutdownComputeSystem %s", err)
}
}
}
}()
createProcessParms := hcsshim.CreateProcessParams{
EmulateConsole: c.ProcessConfig.Tty,
WorkingDirectory: c.WorkingDir,
ConsoleSize: c.ProcessConfig.ConsoleSize,
}
// Configure the environment for the process
createProcessParms.Environment = setupEnvironmentVariables(c.ProcessConfig.Env)
createProcessParms.CommandLine, err = createCommandLine(&c.ProcessConfig, c.ArgsEscaped)
if err != nil {
return execdriver.ExitStatus{ExitCode: -1}, err
}
// Start the command running in the container.
pid, stdin, stdout, stderr, err := hcsshim.CreateProcessInComputeSystem(c.ID, pipes.Stdin != nil, true, !c.ProcessConfig.Tty, createProcessParms)
if err != nil {
logrus.Errorf("CreateProcessInComputeSystem() failed %s", err)
return execdriver.ExitStatus{ExitCode: -1}, err
}
// Now that the process has been launched, begin copying data to and from
// the named pipes for the std handles.
setupPipes(stdin, stdout, stderr, pipes)
//Save the PID as we'll need this in Kill()
logrus.Debugf("PID %d", pid)
c.ContainerPid = int(pid)
if c.ProcessConfig.Tty {
term = NewTtyConsole(c.ID, pid)
} else {
term = NewStdConsole()
}
c.ProcessConfig.Terminal = term
// Maintain our list of active containers. We'll need this later for exec
// and other commands.
d.Lock()
d.activeContainers[c.ID] = &activeContainer{
command: c,
}
d.Unlock()
if hooks.Start != nil {
// A closed channel for OOM is returned here as it will be
// non-blocking and return the correct result when read.
chOOM := make(chan struct{})
close(chOOM)
hooks.Start(&c.ProcessConfig, int(pid), chOOM)
}
exitCode, err := hcsshim.WaitForProcessInComputeSystem(c.ID, pid, hcsshim.TimeoutInfinite)
if err != nil {
if herr, ok := err.(*hcsshim.HcsError); ok && herr.Err != syscall.ERROR_BROKEN_PIPE {
logrus.Warnf("WaitForProcessInComputeSystem failed (container may have been killed): %s", err)
}
// Do NOT return err here as the container would have
// started, otherwise docker will deadlock. It's perfectly legitimate
// for WaitForProcessInComputeSystem to fail in situations such
// as the container being killed on another thread.
return execdriver.ExitStatus{ExitCode: hcsshim.WaitErrExecFailed}, nil
}
logrus.Debugf("Exiting Run() exitCode %d id=%s", exitCode, c.ID)
return execdriver.ExitStatus{ExitCode: int(exitCode)}, nil
}
// SupportsHooks implements the execdriver Driver interface.
// The windows driver does not support the hook mechanism
func (d *Driver) SupportsHooks() bool {
return false
}

View file

@ -1,14 +0,0 @@
// +build windows
package windows
import (
"fmt"
"github.com/docker/docker/daemon/execdriver"
)
// Stats implements the exec driver Driver interface.
func (d *Driver) Stats(id string) (*execdriver.ResourceStats, error) {
return nil, fmt.Errorf("Windows: Stats not implemented")
}

View file

@ -1,24 +0,0 @@
// +build windows
package windows
// StdConsole is for when using a container non-interactively
type StdConsole struct {
}
// NewStdConsole returns a new StdConsole struct.
func NewStdConsole() *StdConsole {
return &StdConsole{}
}
// Resize implements Resize method of Terminal interface.
func (s *StdConsole) Resize(h, w int) error {
// we do not need to resize a non tty
return nil
}
// Close implements Close method of Terminal interface.
func (s *StdConsole) Close() error {
// nothing to close here
return nil
}

View file

@ -1,49 +0,0 @@
// +build windows
package windows
import (
"fmt"
"syscall"
"github.com/Microsoft/hcsshim"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
)
// Terminate implements the exec driver Driver interface.
func (d *Driver) Terminate(p *execdriver.Command) error {
return kill(p.ID, p.ContainerPid, syscall.SIGTERM)
}
// Kill implements the exec driver Driver interface.
func (d *Driver) Kill(p *execdriver.Command, sig int) error {
return kill(p.ID, p.ContainerPid, syscall.Signal(sig))
}
func kill(id string, pid int, sig syscall.Signal) error {
logrus.Debugf("WindowsExec: kill() id=%s pid=%d sig=%d", id, pid, sig)
var err error
context := fmt.Sprintf("kill: sig=%d pid=%d", sig, pid)
if sig == syscall.SIGKILL || forceKill {
// Terminate the compute system
if err := hcsshim.TerminateComputeSystem(id, hcsshim.TimeoutInfinite, context); err != nil {
logrus.Errorf("Failed to terminate %s - %q", id, err)
}
} else {
// Terminate Process
if err = hcsshim.TerminateProcessInComputeSystem(id, uint32(pid)); err != nil {
logrus.Warnf("Failed to terminate pid %d in %s: %q", pid, id, err)
// Ignore errors
err = nil
}
// Shutdown the compute system
if err := hcsshim.ShutdownComputeSystem(id, hcsshim.TimeoutInfinite, context); err != nil {
logrus.Errorf("Failed to shutdown %s - %q", id, err)
}
}
return err
}

View file

@ -1,32 +0,0 @@
// +build windows
package windows
import (
"github.com/Microsoft/hcsshim"
)
// TtyConsole implements the exec driver Terminal interface.
type TtyConsole struct {
id string
processid uint32
}
// NewTtyConsole returns a new TtyConsole struct.
func NewTtyConsole(id string, processid uint32) *TtyConsole {
tty := &TtyConsole{
id: id,
processid: processid,
}
return tty
}
// Resize implements Resize method of Terminal interface.
func (t *TtyConsole) Resize(h, w int) error {
return hcsshim.ResizeConsoleInComputeSystem(t.id, t.processid, h, w)
}
// Close implements Close method of Terminal interface.
func (t *TtyConsole) Close() error {
return nil
}

View file

@ -1,14 +0,0 @@
// +build !windows
package windows
import (
"fmt"
"github.com/docker/docker/daemon/execdriver"
)
// NewDriver returns a new execdriver.Driver
func NewDriver(root, initPath string) (execdriver.Driver, error) {
return nil, fmt.Errorf("Windows driver not supported on non-Windows")
}

View file

@ -1,14 +0,0 @@
// +build windows
package windows
import (
"github.com/docker/docker/daemon/execdriver"
)
// Update updates resource configs for a container.
func (d *Driver) Update(c *execdriver.Command) error {
// Updating resource isn't supported on Windows
// but we should return nil for enabling updating container
return nil
}

View file

@ -1,123 +0,0 @@
// +build windows
package windows
import (
"fmt"
"strings"
"sync"
"github.com/Microsoft/hcsshim"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/pkg/parsers"
"github.com/docker/engine-api/types/container"
)
// TP4RetryHack is a hack to retry CreateComputeSystem if it fails with
// known return codes from Windows due to bugs in TP4.
var TP4RetryHack bool
// This is a daemon development variable only and should not be
// used for running production containers on Windows.
var dummyMode bool
// This allows the daemon to terminate containers rather than shutdown
// This allows the daemon to force kill (HCS terminate) rather than shutdown
var forceKill bool
// DefaultIsolation allows users to specify a default isolation technology for
// when running a container on Windows. For example docker daemon -D
// --exec-opt isolation=hyperv will cause Windows to always run containers
// as Hyper-V containers unless otherwise specified.
var DefaultIsolation container.Isolation = "process"
// Define name and version for windows
var (
DriverName = "Windows 1854"
Version = dockerversion.Version + " " + dockerversion.GitCommit
)
type activeContainer struct {
command *execdriver.Command
}
// Driver contains all information for windows driver,
// it implements execdriver.Driver
type Driver struct {
root string
activeContainers map[string]*activeContainer
sync.Mutex
}
// Name implements the exec driver Driver interface.
func (d *Driver) Name() string {
return fmt.Sprintf("\n Name: %s\n Build: %s \n Default Isolation: %s", DriverName, Version, DefaultIsolation)
}
// NewDriver returns a new windows driver, called from NewDriver of execdriver.
func NewDriver(root string, options []string) (*Driver, error) {
for _, option := range options {
key, val, err := parsers.ParseKeyValueOpt(option)
if err != nil {
return nil, err
}
key = strings.ToLower(key)
switch key {
case "dummy":
switch val {
case "1":
dummyMode = true
logrus.Warn("Using dummy mode in Windows exec driver. This is for development use only!")
}
case "forcekill":
switch val {
case "1":
forceKill = true
logrus.Warn("Using force kill mode in Windows exec driver. This is for testing purposes only.")
}
case "isolation":
if !container.Isolation(val).IsValid() {
return nil, fmt.Errorf("Unrecognised exec driver option 'isolation':'%s'", val)
}
if container.Isolation(val).IsHyperV() {
DefaultIsolation = "hyperv"
}
logrus.Infof("Windows default isolation: '%s'", val)
default:
return nil, fmt.Errorf("Unrecognised exec driver option %s\n", key)
}
}
// TODO Windows TP5 timeframe. Remove this next block of code once TP4
// is no longer supported. Also remove the workaround in run.go.
//
// Hack for TP4.
// This overcomes an issue on TP4 which causes CreateComputeSystem to
// intermittently fail. It's predominantly here to make Windows to Windows
// CI more reliable.
TP4RetryHack = hcsshim.IsTP4()
return &Driver{
root: root,
activeContainers: make(map[string]*activeContainer),
}, nil
}
// setupEnvironmentVariables convert a string array of environment variables
// into a map as required by the HCS. Source array is in format [v1=k1] [v2=k2] etc.
func setupEnvironmentVariables(a []string) map[string]string {
r := make(map[string]string)
for _, s := range a {
arr := strings.Split(s, "=")
if len(arr) == 2 {
r[arr[0]] = arr[1]
}
}
return r
}

View file

@ -84,7 +84,6 @@ func (daemon *Daemon) SystemInfo() (*types.Info, error) {
NFd: fileutils.GetTotalUsedFds(),
NGoroutines: runtime.NumGoroutine(),
SystemTime: time.Now().Format(time.RFC3339Nano),
ExecutionDriver: daemon.ExecutionDriver().Name(),
LoggingDriver: daemon.defaultLogConfig.Type,
CgroupDriver: daemon.getCgroupDriver(),
NEventsListener: daemon.EventsService.SubscribersCount(),

View file

@ -82,10 +82,10 @@ func addMountPoints(container *container.Container) []types.MountPoint {
func inspectExecProcessConfig(e *exec.Config) *backend.ExecProcessConfig {
return &backend.ExecProcessConfig{
Tty: e.ProcessConfig.Tty,
Entrypoint: e.ProcessConfig.Entrypoint,
Arguments: e.ProcessConfig.Arguments,
Privileged: &e.ProcessConfig.Privileged,
User: e.ProcessConfig.User,
Tty: e.Tty,
Entrypoint: e.Entrypoint,
Arguments: e.Args,
Privileged: &e.Privileged,
User: e.User,
}
}

View file

@ -33,8 +33,8 @@ func (daemon *Daemon) containerInspectPre120(name string) (*types.ContainerJSON,
func inspectExecProcessConfig(e *exec.Config) *backend.ExecProcessConfig {
return &backend.ExecProcessConfig{
Tty: e.ProcessConfig.Tty,
Entrypoint: e.ProcessConfig.Entrypoint,
Arguments: e.ProcessConfig.Arguments,
Tty: e.Tty,
Entrypoint: e.Entrypoint,
Arguments: e.Args,
}
}

View file

@ -69,6 +69,10 @@ func (daemon *Daemon) killWithSignal(container *container.Container, sig int) er
container.ExitOnNext()
if !daemon.IsShuttingDown() {
container.HasBeenManuallyStopped = true
}
// if the container is currently restarting we do not need to send the signal
// to the process. Telling the monitor that it should exit on it's next event
// loop is enough

143
daemon/monitor.go Normal file
View file

@ -0,0 +1,143 @@
package daemon
import (
"errors"
"fmt"
"io"
"runtime"
"strconv"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/libcontainerd"
"github.com/docker/docker/runconfig"
)
// StateChanged updates daemon state changes from containerd
func (daemon *Daemon) StateChanged(id string, e libcontainerd.StateInfo) error {
c := daemon.containers.Get(id)
if c == nil {
return fmt.Errorf("no such container: %s", id)
}
switch e.State {
case libcontainerd.StateOOM:
// StateOOM is Linux specific and should never be hit on Windows
if runtime.GOOS == "windows" {
return errors.New("Received StateOOM from libcontainerd on Windows. This should never happen.")
}
daemon.LogContainerEvent(c, "oom")
case libcontainerd.StateExit:
c.Lock()
defer c.Unlock()
c.Wait()
c.Reset(false)
c.SetStopped(platformConstructExitStatus(e))
attributes := map[string]string{
"exitCode": strconv.Itoa(int(e.ExitCode)),
}
daemon.LogContainerEventWithAttributes(c, "die", attributes)
daemon.Cleanup(c)
// FIXME: here is race condition between two RUN instructions in Dockerfile
// because they share same runconfig and change image. Must be fixed
// in builder/builder.go
return c.ToDisk()
case libcontainerd.StateRestart:
c.Lock()
defer c.Unlock()
c.Reset(false)
c.RestartCount++
c.SetRestarting(platformConstructExitStatus(e))
attributes := map[string]string{
"exitCode": strconv.Itoa(int(e.ExitCode)),
}
daemon.LogContainerEventWithAttributes(c, "die", attributes)
return c.ToDisk()
case libcontainerd.StateExitProcess:
c.Lock()
defer c.Unlock()
if execConfig := c.ExecCommands.Get(e.ProcessID); execConfig != nil {
ec := int(e.ExitCode)
execConfig.ExitCode = &ec
execConfig.Running = false
execConfig.Wait()
if err := execConfig.CloseStreams(); err != nil {
logrus.Errorf("%s: %s", c.ID, err)
}
// remove the exec command from the container's store only and not the
// daemon's store so that the exec command can be inspected.
c.ExecCommands.Delete(execConfig.ID)
} else {
logrus.Warnf("Ignoring StateExitProcess for %v but no exec command found", e)
}
case libcontainerd.StateStart, libcontainerd.StateRestore:
c.SetRunning(int(e.Pid), e.State == libcontainerd.StateStart)
c.HasBeenManuallyStopped = false
if err := c.ToDisk(); err != nil {
c.Reset(false)
return err
}
case libcontainerd.StatePause:
c.Paused = true
daemon.LogContainerEvent(c, "pause")
case libcontainerd.StateResume:
c.Paused = false
daemon.LogContainerEvent(c, "unpause")
}
return nil
}
// AttachStreams is called by libcontainerd to connect the stdio.
func (daemon *Daemon) AttachStreams(id string, iop libcontainerd.IOPipe) error {
var s *runconfig.StreamConfig
c := daemon.containers.Get(id)
if c == nil {
ec, err := daemon.getExecConfig(id)
if err != nil {
return fmt.Errorf("no such exec/container: %s", id)
}
s = ec.StreamConfig
} else {
s = c.StreamConfig
if err := daemon.StartLogging(c); err != nil {
c.Reset(false)
return err
}
}
if stdin := s.Stdin(); stdin != nil {
if iop.Stdin != nil {
go func() {
io.Copy(iop.Stdin, stdin)
iop.Stdin.Close()
}()
}
} else {
if c != nil && !c.Config.Tty {
// tty is enabled, so dont close containerd's iopipe stdin.
if iop.Stdin != nil {
iop.Stdin.Close()
}
}
}
copy := func(w io.Writer, r io.Reader) {
s.Add(1)
go func() {
if _, err := io.Copy(w, r); err != nil {
logrus.Errorf("%v stream copy error: %v", id, err)
}
s.Done()
}()
}
if iop.Stdout != nil {
copy(s.Stdout(), iop.Stdout)
}
if iop.Stderr != nil {
copy(s.Stderr(), iop.Stderr)
}
return nil
}

14
daemon/monitor_linux.go Normal file
View file

@ -0,0 +1,14 @@
package daemon
import (
"github.com/docker/docker/container"
"github.com/docker/docker/libcontainerd"
)
// platformConstructExitStatus returns a platform specific exit status structure
func platformConstructExitStatus(e libcontainerd.StateInfo) *container.ExitStatus {
return &container.ExitStatus{
ExitCode: int(e.ExitCode),
OOMKilled: e.OOMKilled,
}
}

13
daemon/monitor_windows.go Normal file
View file

@ -0,0 +1,13 @@
package daemon
import (
"github.com/docker/docker/container"
"github.com/docker/docker/libcontainerd"
)
// platformConstructExitStatus returns a platform specific exit status structure
func platformConstructExitStatus(e libcontainerd.StateInfo) *container.ExitStatus {
return &container.ExitStatus{
ExitCode: int(e.ExitCode),
}
}

652
daemon/oci_linux.go Normal file
View file

@ -0,0 +1,652 @@
package daemon
import (
"fmt"
"io"
"os"
"path/filepath"
"strconv"
"strings"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/caps"
"github.com/docker/docker/libcontainerd"
"github.com/docker/docker/oci"
"github.com/docker/docker/pkg/idtools"
"github.com/docker/docker/pkg/mount"
"github.com/docker/docker/pkg/stringutils"
"github.com/docker/docker/pkg/symlink"
"github.com/docker/docker/volume"
containertypes "github.com/docker/engine-api/types/container"
"github.com/opencontainers/runc/libcontainer/apparmor"
"github.com/opencontainers/runc/libcontainer/devices"
"github.com/opencontainers/runc/libcontainer/user"
"github.com/opencontainers/specs/specs-go"
)
func setResources(s *specs.Spec, r containertypes.Resources) error {
weightDevices, err := getBlkioWeightDevices(r)
if err != nil {
return err
}
readBpsDevice, err := getBlkioReadBpsDevices(r)
if err != nil {
return err
}
writeBpsDevice, err := getBlkioWriteBpsDevices(r)
if err != nil {
return err
}
readIOpsDevice, err := getBlkioReadIOpsDevices(r)
if err != nil {
return err
}
writeIOpsDevice, err := getBlkioWriteIOpsDevices(r)
if err != nil {
return err
}
memoryRes := getMemoryResources(r)
cpuRes := getCPUResources(r)
blkioWeight := r.BlkioWeight
specResources := &specs.Resources{
Memory: memoryRes,
CPU: cpuRes,
BlockIO: &specs.BlockIO{
Weight: &blkioWeight,
WeightDevice: weightDevices,
ThrottleReadBpsDevice: readBpsDevice,
ThrottleWriteBpsDevice: writeBpsDevice,
ThrottleReadIOPSDevice: readIOpsDevice,
ThrottleWriteIOPSDevice: writeIOpsDevice,
},
DisableOOMKiller: r.OomKillDisable,
Pids: &specs.Pids{
Limit: &r.PidsLimit,
},
}
if s.Linux.Resources != nil && len(s.Linux.Resources.Devices) > 0 {
specResources.Devices = s.Linux.Resources.Devices
}
s.Linux.Resources = specResources
return nil
}
func setDevices(s *specs.Spec, c *container.Container) error {
// Build lists of devices allowed and created within the container.
var devs []specs.Device
if c.HostConfig.Privileged {
hostDevices, err := devices.HostDevices()
if err != nil {
return err
}
for _, d := range hostDevices {
devs = append(devs, specDevice(d))
}
} else {
for _, deviceMapping := range c.HostConfig.Devices {
d, err := getDevicesFromPath(deviceMapping)
if err != nil {
return err
}
devs = append(devs, d...)
}
}
s.Linux.Devices = append(s.Linux.Devices, devs...)
return nil
}
func setRlimits(daemon *Daemon, s *specs.Spec, c *container.Container) error {
var rlimits []specs.Rlimit
ulimits := c.HostConfig.Ulimits
// Merge ulimits with daemon defaults
ulIdx := make(map[string]struct{})
for _, ul := range ulimits {
ulIdx[ul.Name] = struct{}{}
}
for name, ul := range daemon.configStore.Ulimits {
if _, exists := ulIdx[name]; !exists {
ulimits = append(ulimits, ul)
}
}
for _, ul := range ulimits {
rlimits = append(rlimits, specs.Rlimit{
Type: "RLIMIT_" + strings.ToUpper(ul.Name),
Soft: uint64(ul.Soft),
Hard: uint64(ul.Hard),
})
}
s.Process.Rlimits = rlimits
return nil
}
func setUser(s *specs.Spec, c *container.Container) error {
uid, gid, additionalGids, err := getUser(c, c.Config.User)
if err != nil {
return err
}
s.Process.User.UID = uid
s.Process.User.GID = gid
s.Process.User.AdditionalGids = additionalGids
return nil
}
func readUserFile(c *container.Container, p string) (io.ReadCloser, error) {
fp, err := symlink.FollowSymlinkInScope(filepath.Join(c.BaseFS, p), c.BaseFS)
if err != nil {
return nil, err
}
return os.Open(fp)
}
func getUser(c *container.Container, username string) (uint32, uint32, []uint32, error) {
passwdPath, err := user.GetPasswdPath()
if err != nil {
return 0, 0, nil, err
}
groupPath, err := user.GetGroupPath()
if err != nil {
return 0, 0, nil, err
}
passwdFile, err := readUserFile(c, passwdPath)
if err == nil {
defer passwdFile.Close()
}
groupFile, err := readUserFile(c, groupPath)
if err == nil {
defer groupFile.Close()
}
execUser, err := user.GetExecUser(username, nil, passwdFile, groupFile)
if err != nil {
return 0, 0, nil, err
}
// todo: fix this double read by a change to libcontainer/user pkg
groupFile, err = readUserFile(c, groupPath)
if err == nil {
defer groupFile.Close()
}
var addGroups []int
if len(c.HostConfig.GroupAdd) > 0 {
addGroups, err = user.GetAdditionalGroups(c.HostConfig.GroupAdd, groupFile)
if err != nil {
return 0, 0, nil, err
}
}
uid := uint32(execUser.Uid)
gid := uint32(execUser.Gid)
sgids := append(execUser.Sgids, addGroups...)
var additionalGids []uint32
for _, g := range sgids {
additionalGids = append(additionalGids, uint32(g))
}
return uid, gid, additionalGids, nil
}
func setNamespace(s *specs.Spec, ns specs.Namespace) {
for i, n := range s.Linux.Namespaces {
if n.Type == ns.Type {
s.Linux.Namespaces[i] = ns
return
}
}
s.Linux.Namespaces = append(s.Linux.Namespaces, ns)
}
func setCapabilities(s *specs.Spec, c *container.Container) error {
var caplist []string
var err error
if c.HostConfig.Privileged {
caplist = caps.GetAllCapabilities()
} else {
caplist, err = caps.TweakCapabilities(s.Process.Capabilities, c.HostConfig.CapAdd, c.HostConfig.CapDrop)
if err != nil {
return err
}
}
s.Process.Capabilities = caplist
return nil
}
func delNamespace(s *specs.Spec, nsType specs.NamespaceType) {
idx := -1
for i, n := range s.Linux.Namespaces {
if n.Type == nsType {
idx = i
}
}
if idx >= 0 {
s.Linux.Namespaces = append(s.Linux.Namespaces[:idx], s.Linux.Namespaces[idx+1:]...)
}
}
func setNamespaces(daemon *Daemon, s *specs.Spec, c *container.Container) error {
// network
if !c.Config.NetworkDisabled {
ns := specs.Namespace{Type: "network"}
parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2)
if parts[0] == "container" {
nc, err := daemon.getNetworkedContainer(c.ID, c.HostConfig.NetworkMode.ConnectedContainer())
if err != nil {
return err
}
ns.Path = fmt.Sprintf("/proc/%d/ns/net", nc.State.GetPID())
} else if c.HostConfig.NetworkMode.IsHost() {
ns.Path = c.NetworkSettings.SandboxKey
}
setNamespace(s, ns)
}
// ipc
if c.HostConfig.IpcMode.IsContainer() {
ns := specs.Namespace{Type: "ipc"}
ic, err := daemon.getIpcContainer(c)
if err != nil {
return err
}
ns.Path = fmt.Sprintf("/proc/%d/ns/ipc", ic.State.GetPID())
setNamespace(s, ns)
} else if c.HostConfig.IpcMode.IsHost() {
delNamespace(s, specs.NamespaceType("ipc"))
} else {
ns := specs.Namespace{Type: "ipc"}
setNamespace(s, ns)
}
// pid
if c.HostConfig.PidMode.IsHost() {
delNamespace(s, specs.NamespaceType("pid"))
}
// uts
if c.HostConfig.UTSMode.IsHost() {
delNamespace(s, specs.NamespaceType("uts"))
s.Hostname = ""
}
// user
if c.HostConfig.UsernsMode.IsPrivate() {
uidMap, gidMap := daemon.GetUIDGIDMaps()
if uidMap != nil {
ns := specs.Namespace{Type: "user"}
setNamespace(s, ns)
s.Linux.UIDMappings = specMapping(uidMap)
s.Linux.GIDMappings = specMapping(gidMap)
}
}
return nil
}
func specMapping(s []idtools.IDMap) []specs.IDMapping {
var ids []specs.IDMapping
for _, item := range s {
ids = append(ids, specs.IDMapping{
HostID: uint32(item.HostID),
ContainerID: uint32(item.ContainerID),
Size: uint32(item.Size),
})
}
return ids
}
func getMountInfo(mountinfo []*mount.Info, dir string) *mount.Info {
for _, m := range mountinfo {
if m.Mountpoint == dir {
return m
}
}
return nil
}
// Get the source mount point of directory passed in as argument. Also return
// optional fields.
func getSourceMount(source string) (string, string, error) {
// Ensure any symlinks are resolved.
sourcePath, err := filepath.EvalSymlinks(source)
if err != nil {
return "", "", err
}
mountinfos, err := mount.GetMounts()
if err != nil {
return "", "", err
}
mountinfo := getMountInfo(mountinfos, sourcePath)
if mountinfo != nil {
return sourcePath, mountinfo.Optional, nil
}
path := sourcePath
for {
path = filepath.Dir(path)
mountinfo = getMountInfo(mountinfos, path)
if mountinfo != nil {
return path, mountinfo.Optional, nil
}
if path == "/" {
break
}
}
// If we are here, we did not find parent mount. Something is wrong.
return "", "", fmt.Errorf("Could not find source mount of %s", source)
}
// Ensure mount point on which path is mounted, is shared.
func ensureShared(path string) error {
sharedMount := false
sourceMount, optionalOpts, err := getSourceMount(path)
if err != nil {
return err
}
// Make sure source mount point is shared.
optsSplit := strings.Split(optionalOpts, " ")
for _, opt := range optsSplit {
if strings.HasPrefix(opt, "shared:") {
sharedMount = true
break
}
}
if !sharedMount {
return fmt.Errorf("Path %s is mounted on %s but it is not a shared mount.", path, sourceMount)
}
return nil
}
// Ensure mount point on which path is mounted, is either shared or slave.
func ensureSharedOrSlave(path string) error {
sharedMount := false
slaveMount := false
sourceMount, optionalOpts, err := getSourceMount(path)
if err != nil {
return err
}
// Make sure source mount point is shared.
optsSplit := strings.Split(optionalOpts, " ")
for _, opt := range optsSplit {
if strings.HasPrefix(opt, "shared:") {
sharedMount = true
break
} else if strings.HasPrefix(opt, "master:") {
slaveMount = true
break
}
}
if !sharedMount && !slaveMount {
return fmt.Errorf("Path %s is mounted on %s but it is not a shared or slave mount.", path, sourceMount)
}
return nil
}
var (
mountPropagationMap = map[string]int{
"private": mount.PRIVATE,
"rprivate": mount.RPRIVATE,
"shared": mount.SHARED,
"rshared": mount.RSHARED,
"slave": mount.SLAVE,
"rslave": mount.RSLAVE,
}
mountPropagationReverseMap = map[int]string{
mount.PRIVATE: "private",
mount.RPRIVATE: "rprivate",
mount.SHARED: "shared",
mount.RSHARED: "rshared",
mount.SLAVE: "slave",
mount.RSLAVE: "rslave",
}
)
func setMounts(daemon *Daemon, s *specs.Spec, c *container.Container, mounts []container.Mount) error {
userMounts := make(map[string]struct{})
for _, m := range mounts {
userMounts[m.Destination] = struct{}{}
}
// Filter out mounts that are overriden by user supplied mounts
var defaultMounts []specs.Mount
_, mountDev := userMounts["/dev"]
for _, m := range s.Mounts {
if _, ok := userMounts[m.Destination]; !ok {
if mountDev && strings.HasPrefix(m.Destination, "/dev/") {
continue
}
defaultMounts = append(defaultMounts, m)
}
}
s.Mounts = defaultMounts
for _, m := range mounts {
for _, cm := range s.Mounts {
if cm.Destination == m.Destination {
return fmt.Errorf("Duplicate mount point '%s'", m.Destination)
}
}
if m.Source == "tmpfs" {
opt := []string{"noexec", "nosuid", "nodev", volume.DefaultPropagationMode}
if m.Data != "" {
opt = append(opt, strings.Split(m.Data, ",")...)
} else {
opt = append(opt, "size=65536k")
}
s.Mounts = append(s.Mounts, specs.Mount{Destination: m.Destination, Source: m.Source, Type: "tmpfs", Options: opt})
continue
}
mt := specs.Mount{Destination: m.Destination, Source: m.Source, Type: "bind"}
// Determine property of RootPropagation based on volume
// properties. If a volume is shared, then keep root propagation
// shared. This should work for slave and private volumes too.
//
// For slave volumes, it can be either [r]shared/[r]slave.
//
// For private volumes any root propagation value should work.
pFlag := mountPropagationMap[m.Propagation]
if pFlag == mount.SHARED || pFlag == mount.RSHARED {
if err := ensureShared(m.Source); err != nil {
return err
}
rootpg := mountPropagationMap[s.Linux.RootfsPropagation]
if rootpg != mount.SHARED && rootpg != mount.RSHARED {
s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.SHARED]
}
} else if pFlag == mount.SLAVE || pFlag == mount.RSLAVE {
if err := ensureSharedOrSlave(m.Source); err != nil {
return err
}
rootpg := mountPropagationMap[s.Linux.RootfsPropagation]
if rootpg != mount.SHARED && rootpg != mount.RSHARED && rootpg != mount.SLAVE && rootpg != mount.RSLAVE {
s.Linux.RootfsPropagation = mountPropagationReverseMap[mount.RSLAVE]
}
}
opts := []string{"rbind"}
if !m.Writable {
opts = append(opts, "ro")
}
if pFlag != 0 {
opts = append(opts, mountPropagationReverseMap[pFlag])
}
mt.Options = opts
s.Mounts = append(s.Mounts, mt)
}
if s.Root.Readonly {
for i, m := range s.Mounts {
switch m.Destination {
case "/proc", "/dev/pts", "/dev/mqueue": // /dev is remounted by runc
continue
}
if _, ok := userMounts[m.Destination]; !ok {
if !stringutils.InSlice(m.Options, "ro") {
s.Mounts[i].Options = append(s.Mounts[i].Options, "ro")
}
}
}
}
if c.HostConfig.Privileged {
if !s.Root.Readonly {
// clear readonly for /sys
for i := range s.Mounts {
if s.Mounts[i].Destination == "/sys" {
clearReadOnly(&s.Mounts[i])
}
}
}
}
// TODO: until a kernel/mount solution exists for handling remount in a user namespace,
// we must clear the readonly flag for the cgroups mount (@mrunalp concurs)
if uidMap, _ := daemon.GetUIDGIDMaps(); uidMap != nil || c.HostConfig.Privileged {
for i, m := range s.Mounts {
if m.Type == "cgroup" {
clearReadOnly(&s.Mounts[i])
}
}
}
return nil
}
func (daemon *Daemon) populateCommonSpec(s *specs.Spec, c *container.Container) error {
linkedEnv, err := daemon.setupLinkedContainers(c)
if err != nil {
return err
}
s.Root = specs.Root{
Path: c.BaseFS,
Readonly: c.HostConfig.ReadonlyRootfs,
}
rootUID, rootGID := daemon.GetRemappedUIDGID()
if err := c.SetupWorkingDirectory(rootUID, rootGID); err != nil {
return err
}
cwd := c.Config.WorkingDir
if len(cwd) == 0 {
cwd = "/"
}
s.Process.Args = append([]string{c.Path}, c.Args...)
s.Process.Cwd = cwd
s.Process.Env = c.CreateDaemonEnvironment(linkedEnv)
s.Process.Terminal = c.Config.Tty
s.Hostname = c.FullHostname()
return nil
}
func (daemon *Daemon) createSpec(c *container.Container) (*libcontainerd.Spec, error) {
s := oci.DefaultSpec()
if err := daemon.populateCommonSpec(&s, c); err != nil {
return nil, err
}
var cgroupsPath string
if c.HostConfig.CgroupParent != "" {
cgroupsPath = filepath.Join(c.HostConfig.CgroupParent, c.ID)
} else {
defaultCgroupParent := "/docker"
if daemon.configStore.CgroupParent != "" {
defaultCgroupParent = daemon.configStore.CgroupParent
} else if daemon.usingSystemd() {
defaultCgroupParent = "system.slice"
}
cgroupsPath = filepath.Join(defaultCgroupParent, c.ID)
}
s.Linux.CgroupsPath = &cgroupsPath
if err := setResources(&s, c.HostConfig.Resources); err != nil {
return nil, fmt.Errorf("linux runtime spec resources: %v", err)
}
s.Linux.Resources.OOMScoreAdj = &c.HostConfig.OomScoreAdj
if err := setDevices(&s, c); err != nil {
return nil, fmt.Errorf("linux runtime spec devices: %v", err)
}
if err := setRlimits(daemon, &s, c); err != nil {
return nil, fmt.Errorf("linux runtime spec rlimits: %v", err)
}
if err := setUser(&s, c); err != nil {
return nil, fmt.Errorf("linux spec user: %v", err)
}
if err := setNamespaces(daemon, &s, c); err != nil {
return nil, fmt.Errorf("linux spec namespaces: %v", err)
}
if err := setCapabilities(&s, c); err != nil {
return nil, fmt.Errorf("linux spec capabilities: %v", err)
}
if err := setSeccomp(daemon, &s, c); err != nil {
return nil, fmt.Errorf("linux seccomp: %v", err)
}
if err := daemon.setupIpcDirs(c); err != nil {
return nil, err
}
mounts, err := daemon.setupMounts(c)
if err != nil {
return nil, err
}
mounts = append(mounts, c.IpcMounts()...)
mounts = append(mounts, c.TmpfsMounts()...)
if err := setMounts(daemon, &s, c, mounts); err != nil {
return nil, fmt.Errorf("linux mounts: %v", err)
}
for _, ns := range s.Linux.Namespaces {
if ns.Type == "network" && ns.Path == "" && !c.Config.NetworkDisabled {
target, err := os.Readlink(filepath.Join("/proc", strconv.Itoa(os.Getpid()), "exe"))
if err != nil {
return nil, err
}
s.Hooks = specs.Hooks{
Prestart: []specs.Hook{{
Path: target, // FIXME: cross-platform
Args: []string{"libnetwork-setkey", c.ID, daemon.netController.ID()},
}},
}
}
}
if apparmor.IsEnabled() {
appArmorProfile := "docker-default"
if c.HostConfig.Privileged {
appArmorProfile = "unconfined"
} else if len(c.AppArmorProfile) > 0 {
appArmorProfile = c.AppArmorProfile
}
s.Process.ApparmorProfile = appArmorProfile
}
s.Process.SelinuxLabel = c.GetProcessLabel()
s.Process.NoNewPrivileges = c.NoNewPrivileges
return (*libcontainerd.Spec)(&s), nil
}
func clearReadOnly(m *specs.Mount) {
var opt []string
for _, o := range m.Options {
if o != "ro" {
opt = append(opt, o)
}
}
m.Options = opt
}

204
daemon/oci_windows.go Normal file
View file

@ -0,0 +1,204 @@
package daemon
import (
"fmt"
"strings"
"syscall"
"github.com/docker/docker/container"
"github.com/docker/docker/layer"
"github.com/docker/docker/libcontainerd"
"github.com/docker/docker/libcontainerd/windowsoci"
"github.com/docker/docker/oci"
)
func (daemon *Daemon) createSpec(c *container.Container) (*libcontainerd.Spec, error) {
s := oci.DefaultSpec()
linkedEnv, err := daemon.setupLinkedContainers(c)
if err != nil {
return nil, err
}
// TODO Windows - this can be removed. Not used (UID/GID)
rootUID, rootGID := daemon.GetRemappedUIDGID()
if err := c.SetupWorkingDirectory(rootUID, rootGID); err != nil {
return nil, err
}
img, err := daemon.imageStore.Get(c.ImageID)
if err != nil {
return nil, fmt.Errorf("Failed to graph.Get on ImageID %s - %s", c.ImageID, err)
}
// In base spec
s.Hostname = c.FullHostname()
// In s.Mounts
mounts, err := daemon.setupMounts(c)
if err != nil {
return nil, err
}
for _, mount := range mounts {
s.Mounts = append(s.Mounts, windowsoci.Mount{
Source: mount.Source,
Destination: mount.Destination,
Readonly: !mount.Writable,
})
}
// Are we going to run as a Hyper-V container?
hv := false
if c.HostConfig.Isolation.IsDefault() {
// Container is set to use the default, so take the default from the daemon configuration
hv = daemon.defaultIsolation.IsHyperV()
} else {
// Container is requesting an isolation mode. Honour it.
hv = c.HostConfig.Isolation.IsHyperV()
}
if hv {
// TODO We don't yet have the ImagePath hooked up. But set to
// something non-nil to pickup in libcontainerd.
s.Windows.HvRuntime = &windowsoci.HvRuntime{}
}
// In s.Process
if c.Config.ArgsEscaped {
s.Process.Args = append([]string{c.Path}, c.Args...)
} else {
// TODO (jstarks): escape the entrypoint too once the tests are fixed to not rely on this behavior
s.Process.Args = append([]string{c.Path}, escapeArgs(c.Args)...)
}
s.Process.Cwd = c.Config.WorkingDir
s.Process.Env = c.CreateDaemonEnvironment(linkedEnv)
s.Process.InitialConsoleSize = c.HostConfig.ConsoleSize
s.Process.Terminal = c.Config.Tty
s.Process.User.User = c.Config.User
// In spec.Root
s.Root.Path = c.BaseFS
s.Root.Readonly = c.HostConfig.ReadonlyRootfs
// In s.Windows
s.Windows.FirstStart = !c.HasBeenStartedBefore
// s.Windows.LayerFolder.
m, err := c.RWLayer.Metadata()
if err != nil {
return nil, fmt.Errorf("Failed to get layer metadata - %s", err)
}
s.Windows.LayerFolder = m["dir"]
// s.Windows.LayerPaths
var layerPaths []string
if img.RootFS != nil && img.RootFS.Type == "layers+base" {
max := len(img.RootFS.DiffIDs)
for i := 0; i <= max; i++ {
img.RootFS.DiffIDs = img.RootFS.DiffIDs[:i]
path, err := layer.GetLayerPath(daemon.layerStore, img.RootFS.ChainID())
if err != nil {
return nil, fmt.Errorf("Failed to get layer path from graphdriver %s for ImageID %s - %s", daemon.layerStore, img.RootFS.ChainID(), err)
}
// Reverse order, expecting parent most first
layerPaths = append([]string{path}, layerPaths...)
}
}
s.Windows.LayerPaths = layerPaths
// In s.Windows.Networking (TP5+ libnetwork way of doing things)
// Connect all the libnetwork allocated networks to the container
var epList []string
if c.NetworkSettings != nil {
for n := range c.NetworkSettings.Networks {
sn, err := daemon.FindNetwork(n)
if err != nil {
continue
}
ep, err := c.GetEndpointInNetwork(sn)
if err != nil {
continue
}
data, err := ep.DriverInfo()
if err != nil {
continue
}
if data["hnsid"] != nil {
epList = append(epList, data["hnsid"].(string))
}
}
}
s.Windows.Networking = &windowsoci.Networking{
EndpointList: epList,
}
// In s.Windows.Networking (TP4 back compat)
// TODO Windows: Post TP4 - Remove this along with definitions from spec
// and changes to libcontainerd to not read these fields.
if daemon.netController == nil {
parts := strings.SplitN(string(c.HostConfig.NetworkMode), ":", 2)
switch parts[0] {
case "none":
case "default", "": // empty string to support existing containers
if !c.Config.NetworkDisabled {
s.Windows.Networking = &windowsoci.Networking{
MacAddress: c.Config.MacAddress,
Bridge: daemon.configStore.bridgeConfig.Iface,
PortBindings: c.HostConfig.PortBindings,
}
}
default:
return nil, fmt.Errorf("invalid network mode: %s", c.HostConfig.NetworkMode)
}
}
// In s.Windows.Resources
// @darrenstahlmsft implement these resources
cpuShares := uint64(c.HostConfig.CPUShares)
s.Windows.Resources = &windowsoci.Resources{
CPU: &windowsoci.CPU{
//TODO Count: ...,
//TODO Percent: ...,
Shares: &cpuShares,
},
Memory: &windowsoci.Memory{
//TODO Limit: ...,
//TODO Reservation: ...,
},
Network: &windowsoci.Network{
//TODO Bandwidth: ...,
},
Storage: &windowsoci.Storage{
//TODO Bps: ...,
//TODO Iops: ...,
//TODO SandboxSize: ...,
},
}
// BUGBUG - Next problem. This was an exec opt. Where do we now get these?
// Come back to this when add Xenon support.
// var hvPartition bool
// // Work out the isolation (whether it is a hypervisor partition)
// if c.HostConfig.Isolation.IsDefault() {
// // Not specified by caller. Take daemon default
// hvPartition = windows.DefaultIsolation.IsHyperV()
// } else {
// // Take value specified by caller
// hvPartition = c.HostConfig.Isolation.IsHyperV()
// }
// Isolation: string(c.HostConfig.Isolation),
// HvPartition: hvPartition,
// }
return (*libcontainerd.Spec)(&s), nil
}
func escapeArgs(args []string) []string {
escapedArgs := make([]string, len(args))
for i, a := range args {
escapedArgs[i] = syscall.EscapeArg(a)
}
return escapedArgs
}

View file

@ -41,10 +41,9 @@ func (daemon *Daemon) containerPause(container *container.Container) error {
return errContainerIsRestarting(container.ID)
}
if err := daemon.execDriver.Pause(container.Command); err != nil {
if err := daemon.containerd.Pause(container.ID); err != nil {
return fmt.Errorf("Cannot pause container %s: %s", container.ID, err)
}
container.Paused = true
daemon.LogContainerEvent(container, "pause")
return nil
}

View file

@ -1,6 +1,10 @@
package daemon
import "fmt"
import (
"fmt"
"github.com/docker/docker/libcontainerd"
)
// ContainerResize changes the size of the TTY of the process running
// in the container with the given name to the given height and width.
@ -14,7 +18,7 @@ func (daemon *Daemon) ContainerResize(name string, height, width int) error {
return errNotRunning{container.ID}
}
if err = container.Resize(height, width); err == nil {
if err = daemon.containerd.Resize(container.ID, libcontainerd.InitFriendlyName, width, height); err == nil {
attributes := map[string]string{
"height": fmt.Sprintf("%d", height),
"width": fmt.Sprintf("%d", width),
@ -28,10 +32,9 @@ func (daemon *Daemon) ContainerResize(name string, height, width int) error {
// running in the exec with the given name to the given height and
// width.
func (daemon *Daemon) ContainerExecResize(name string, height, width int) error {
ExecConfig, err := daemon.getExecConfig(name)
ec, err := daemon.getExecConfig(name)
if err != nil {
return err
}
return ExecConfig.Resize(height, width)
return daemon.containerd.Resize(ec.ContainerID, ec.ID, width, height)
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,12 @@
// +build !seccomp,!windows
package daemon
import (
"github.com/docker/docker/container"
"github.com/opencontainers/specs/specs-go"
)
func setSeccomp(daemon *Daemon, rs *specs.Spec, c *container.Container) error {
return nil
}

100
daemon/seccomp_linux.go Normal file
View file

@ -0,0 +1,100 @@
// +build linux,seccomp
package daemon
import (
"encoding/json"
"fmt"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/container"
"github.com/docker/engine-api/types"
"github.com/opencontainers/specs/specs-go"
)
func setSeccomp(daemon *Daemon, rs *specs.Spec, c *container.Container) error {
var seccomp *specs.Seccomp
var err error
if c.HostConfig.Privileged {
return nil
}
if !daemon.seccompEnabled {
if c.SeccompProfile != "" && c.SeccompProfile != "unconfined" {
return fmt.Errorf("Seccomp is not enabled in your kernel, cannot run a custom seccomp profile.")
}
logrus.Warn("Seccomp is not enabled in your kernel, running container without default profile.")
c.SeccompProfile = "unconfined"
}
if c.SeccompProfile == "unconfined" {
return nil
}
if c.SeccompProfile != "" {
seccomp, err = loadSeccompProfile(c.SeccompProfile)
if err != nil {
return err
}
} else {
seccomp = &defaultSeccompProfile
}
rs.Linux.Seccomp = seccomp
return nil
}
func loadSeccompProfile(body string) (*specs.Seccomp, error) {
var config types.Seccomp
if err := json.Unmarshal([]byte(body), &config); err != nil {
return nil, fmt.Errorf("Decoding seccomp profile failed: %v", err)
}
return setupSeccomp(&config)
}
func setupSeccomp(config *types.Seccomp) (newConfig *specs.Seccomp, err error) {
if config == nil {
return nil, nil
}
// No default action specified, no syscalls listed, assume seccomp disabled
if config.DefaultAction == "" && len(config.Syscalls) == 0 {
return nil, nil
}
newConfig = &specs.Seccomp{}
// if config.Architectures == 0 then libseccomp will figure out the architecture to use
if len(config.Architectures) > 0 {
// newConfig.Architectures = []string{}
for _, arch := range config.Architectures {
newConfig.Architectures = append(newConfig.Architectures, specs.Arch(arch))
}
}
newConfig.DefaultAction = specs.Action(config.DefaultAction)
// Loop through all syscall blocks and convert them to libcontainer format
for _, call := range config.Syscalls {
newCall := specs.Syscall{
Name: call.Name,
Action: specs.Action(call.Action),
}
// Loop through all the arguments of the syscall and convert them
for _, arg := range call.Args {
newArg := specs.Arg{
Index: arg.Index,
Value: arg.Value,
ValueTwo: arg.ValueTwo,
Op: specs.Operator(arg.Op),
}
newCall.Args = append(newCall.Args, newArg)
}
newConfig.Syscalls = append(newConfig.Syscalls, newCall)
}
return newConfig, nil
}

View file

@ -4,10 +4,13 @@ import (
"fmt"
"net/http"
"runtime"
"strings"
"syscall"
"github.com/Sirupsen/logrus"
"github.com/docker/docker/container"
"github.com/docker/docker/errors"
"github.com/docker/docker/libcontainerd"
"github.com/docker/docker/runconfig"
containertypes "github.com/docker/engine-api/types/container"
)
@ -122,44 +125,36 @@ func (daemon *Daemon) containerStart(container *container.Container) (err error)
if err := daemon.initializeNetworking(container); err != nil {
return err
}
linkedEnv, err := daemon.setupLinkedContainers(container)
spec, err := daemon.createSpec(container)
if err != nil {
return err
}
rootUID, rootGID := daemon.GetRemappedUIDGID()
if err := container.SetupWorkingDirectory(rootUID, rootGID); err != nil {
return err
}
env := container.CreateDaemonEnvironment(linkedEnv)
if err := daemon.populateCommand(container, env); err != nil {
return err
}
if !container.HostConfig.IpcMode.IsContainer() && !container.HostConfig.IpcMode.IsHost() {
if err := daemon.setupIpcDirs(container); err != nil {
return err
defer daemon.LogContainerEvent(container, "start") // this is logged even on error
if err := daemon.containerd.Create(container.ID, *spec, libcontainerd.WithRestartManager(container.RestartManager(true))); err != nil {
// if we receive an internal error from the initial start of a container then lets
// return it instead of entering the restart loop
// set to 127 for container cmd not found/does not exist)
if strings.Contains(err.Error(), "executable file not found") ||
strings.Contains(err.Error(), "no such file or directory") ||
strings.Contains(err.Error(), "system cannot find the file specified") {
container.ExitCode = 127
err = fmt.Errorf("Container command not found or does not exist.")
}
// set to 126 for container cmd can't be invoked errors
if strings.Contains(err.Error(), syscall.EACCES.Error()) {
container.ExitCode = 126
err = fmt.Errorf("Container command could not be invoked.")
}
}
mounts, err := daemon.setupMounts(container)
if err != nil {
container.Reset(false)
return err
}
mounts = append(mounts, container.IpcMounts()...)
mounts = append(mounts, container.TmpfsMounts()...)
container.Command.Mounts = mounts
if err := daemon.waitForStart(container); err != nil {
return err
}
container.HasBeenStartedBefore = true
return nil
}
func (daemon *Daemon) waitForStart(container *container.Container) error {
return container.StartMonitor(daemon)
}
// Cleanup releases any network resources allocated to the container along with any rules
// around how containers are linked together. It also unmounts the container's root filesystem.
func (daemon *Daemon) Cleanup(container *container.Container) {
@ -167,7 +162,13 @@ func (daemon *Daemon) Cleanup(container *container.Container) {
container.UnmountIpcMounts(detachMounted)
daemon.conditionalUnmountOnCleanup(container)
if err := daemon.conditionalUnmountOnCleanup(container); err != nil {
// FIXME: remove once reference counting for graphdrivers has been refactored
// Ensure that all the mounts are gone
if mountid, err := daemon.layerStore.GetMountID(container.ID); err == nil {
daemon.cleanupMountsByID(mountid)
}
}
for _, eConfig := range container.ExecCommands.Commands() {
daemon.unregisterExecCommand(container, eConfig)

View file

@ -6,7 +6,6 @@ import (
"runtime"
"github.com/docker/docker/api/types/backend"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/ioutils"
"github.com/docker/docker/pkg/version"
"github.com/docker/engine-api/types"
@ -42,12 +41,9 @@ func (daemon *Daemon) ContainerStats(prefixOrName string, config *backend.Contai
var preCPUStats types.CPUStats
getStatJSON := func(v interface{}) *types.StatsJSON {
update := v.(*execdriver.ResourceStats)
ss := convertStatsToAPITypes(update.Stats)
ss := v.(*types.StatsJSON)
ss.PreCPUStats = preCPUStats
ss.MemoryStats.Limit = uint64(update.MemoryLimit)
ss.Read = update.Read
ss.CPUStats.SystemUsage = update.SystemUsage
// ss.MemoryStats.Limit = uint64(update.MemoryLimit)
preCPUStats = ss.CPUStats
return ss
}

View file

@ -13,14 +13,14 @@ import (
"github.com/Sirupsen/logrus"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/pkg/pubsub"
"github.com/docker/engine-api/types"
"github.com/opencontainers/runc/libcontainer/system"
)
type statsSupervisor interface {
// GetContainerStats collects all the stats related to a container
GetContainerStats(container *container.Container) (*execdriver.ResourceStats, error)
GetContainerStats(container *container.Container) (*types.StatsJSON, error)
}
// newStatsCollector returns a new statsCollector that collections
@ -120,12 +120,13 @@ func (s *statsCollector) run() {
for _, pair := range pairs {
stats, err := s.supervisor.GetContainerStats(pair.container)
if err != nil {
if err != execdriver.ErrNotRunning {
if err, ok := err.(errNotRunning); ok {
logrus.Errorf("collecting stats for %s: %v", pair.container.ID, err)
}
continue
}
stats.SystemUsage = systemUsage
// FIXME: move to containerd
stats.CPUStats.SystemUsage = systemUsage
pair.publisher.Publish(stats)
}

View file

@ -1,84 +0,0 @@
package daemon
import (
"github.com/docker/engine-api/types"
"github.com/opencontainers/runc/libcontainer"
"github.com/opencontainers/runc/libcontainer/cgroups"
)
// convertStatsToAPITypes converts the libcontainer.Stats to the api specific
// structs. This is done to preserve API compatibility and versioning.
func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON {
s := &types.StatsJSON{}
if ls.Interfaces != nil {
s.Networks = make(map[string]types.NetworkStats)
for _, iface := range ls.Interfaces {
// For API Version >= 1.21, the original data of network will
// be returned.
s.Networks[iface.Name] = types.NetworkStats{
RxBytes: iface.RxBytes,
RxPackets: iface.RxPackets,
RxErrors: iface.RxErrors,
RxDropped: iface.RxDropped,
TxBytes: iface.TxBytes,
TxPackets: iface.TxPackets,
TxErrors: iface.TxErrors,
TxDropped: iface.TxDropped,
}
}
}
cs := ls.CgroupStats
if cs != nil {
s.BlkioStats = types.BlkioStats{
IoServiceBytesRecursive: copyBlkioEntry(cs.BlkioStats.IoServiceBytesRecursive),
IoServicedRecursive: copyBlkioEntry(cs.BlkioStats.IoServicedRecursive),
IoQueuedRecursive: copyBlkioEntry(cs.BlkioStats.IoQueuedRecursive),
IoServiceTimeRecursive: copyBlkioEntry(cs.BlkioStats.IoServiceTimeRecursive),
IoWaitTimeRecursive: copyBlkioEntry(cs.BlkioStats.IoWaitTimeRecursive),
IoMergedRecursive: copyBlkioEntry(cs.BlkioStats.IoMergedRecursive),
IoTimeRecursive: copyBlkioEntry(cs.BlkioStats.IoTimeRecursive),
SectorsRecursive: copyBlkioEntry(cs.BlkioStats.SectorsRecursive),
}
cpu := cs.CpuStats
s.CPUStats = types.CPUStats{
CPUUsage: types.CPUUsage{
TotalUsage: cpu.CpuUsage.TotalUsage,
PercpuUsage: cpu.CpuUsage.PercpuUsage,
UsageInKernelmode: cpu.CpuUsage.UsageInKernelmode,
UsageInUsermode: cpu.CpuUsage.UsageInUsermode,
},
ThrottlingData: types.ThrottlingData{
Periods: cpu.ThrottlingData.Periods,
ThrottledPeriods: cpu.ThrottlingData.ThrottledPeriods,
ThrottledTime: cpu.ThrottlingData.ThrottledTime,
},
}
mem := cs.MemoryStats
s.MemoryStats = types.MemoryStats{
Usage: mem.Usage.Usage,
MaxUsage: mem.Usage.MaxUsage,
Stats: mem.Stats,
Failcnt: mem.Usage.Failcnt,
}
pids := cs.PidsStats
s.PidsStats = types.PidsStats{
Current: pids.Current,
}
}
return s
}
func copyBlkioEntry(entries []cgroups.BlkioStatEntry) []types.BlkioStatEntry {
out := make([]types.BlkioStatEntry, len(entries))
for i, re := range entries {
out[i] = types.BlkioStatEntry{
Major: re.Major,
Minor: re.Minor,
Op: re.Op,
Value: re.Value,
}
}
return out
}

View file

@ -1,14 +0,0 @@
package daemon
import (
"github.com/docker/engine-api/types"
"github.com/opencontainers/runc/libcontainer"
)
// convertStatsToAPITypes converts the libcontainer.Stats to the api specific
// structs. This is done to preserve API compatibility and versioning.
func convertStatsToAPITypes(ls *libcontainer.Stats) *types.StatsJSON {
// TODO Windows. Refactor accordingly to fill in stats.
s := &types.StatsJSON{}
return s
}

View file

@ -33,7 +33,8 @@ func (daemon *Daemon) ContainerTop(name string, psArgs string) (*types.Container
if container.IsRestarting() {
return nil, errContainerIsRestarting(container.ID)
}
pids, err := daemon.ExecutionDriver().GetPidsForContainer(container.ID)
pids, err := daemon.containerd.GetPidsForContainer(container.ID)
if err != nil {
return nil, err
}

View file

@ -35,11 +35,9 @@ func (daemon *Daemon) containerUnpause(container *container.Container) error {
return fmt.Errorf("Container %s is not paused", container.ID)
}
if err := daemon.execDriver.Unpause(container.Command); err != nil {
if err := daemon.containerd.Resume(container.ID); err != nil {
return fmt.Errorf("Cannot unpause container %s: %s", container.ID, err)
}
container.Paused = false
daemon.LogContainerEvent(container, "unpause")
return nil
}

View file

@ -84,7 +84,7 @@ func (daemon *Daemon) update(name string, hostConfig *container.HostConfig) erro
// If container is running (including paused), we need to update configs
// to the real world.
if container.IsRunning() && !container.IsRestarting() {
if err := daemon.execDriver.Update(container.Command); err != nil {
if err := daemon.containerd.UpdateResources(container.ID, toContainerdResources(hostConfig.Resources)); err != nil {
restoreConfig = true
return errCannotUpdate(container.ID, err)
}

25
daemon/update_linux.go Normal file
View file

@ -0,0 +1,25 @@
// +build linux
package daemon
import (
"github.com/docker/docker/libcontainerd"
"github.com/docker/engine-api/types/container"
)
func toContainerdResources(resources container.Resources) libcontainerd.Resources {
var r libcontainerd.Resources
r.BlkioWeight = uint32(resources.BlkioWeight)
r.CpuShares = uint32(resources.CPUShares)
r.CpuPeriod = uint32(resources.CPUPeriod)
r.CpuQuota = uint32(resources.CPUQuota)
r.CpusetCpus = resources.CpusetCpus
r.CpusetMems = resources.CpusetMems
r.MemoryLimit = uint32(resources.Memory)
if resources.MemorySwap > 0 {
r.MemorySwap = uint32(resources.MemorySwap)
}
r.MemoryReservation = uint32(resources.MemoryReservation)
r.KernelMemoryLimit = uint32(resources.KernelMemory)
return r
}

13
daemon/update_windows.go Normal file
View file

@ -0,0 +1,13 @@
// +build windows
package daemon
import (
"github.com/docker/docker/libcontainerd"
"github.com/docker/engine-api/types/container"
)
func toContainerdResources(resources container.Resources) libcontainerd.Resources {
var r libcontainerd.Resources
return r
}

View file

@ -8,7 +8,6 @@ import (
"strings"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/volume"
"github.com/docker/engine-api/types"
containertypes "github.com/docker/engine-api/types/container"
@ -21,7 +20,7 @@ var (
ErrVolumeReadonly = errors.New("mounted volume is marked read-only")
)
type mounts []execdriver.Mount
type mounts []container.Mount
// volumeToAPIType converts a volume.Volume to the type used by the remote API
func volumeToAPIType(v volume.Volume) *types.Volume {

View file

@ -8,25 +8,24 @@ import (
"strconv"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/volume"
)
// setupMounts iterates through each of the mount points for a container and
// calls Setup() on each. It also looks to see if is a network mount such as
// /etc/resolv.conf, and if it is not, appends it to the array of mounts.
func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.Mount, error) {
var mounts []execdriver.Mount
for _, m := range container.MountPoints {
if err := daemon.lazyInitializeVolume(container.ID, m); err != nil {
func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) {
var mounts []container.Mount
for _, m := range c.MountPoints {
if err := daemon.lazyInitializeVolume(c.ID, m); err != nil {
return nil, err
}
path, err := m.Setup()
if err != nil {
return nil, err
}
if !container.TrySetNetworkMount(m.Destination, path) {
mnt := execdriver.Mount{
if !c.TrySetNetworkMount(m.Destination, path) {
mnt := container.Mount{
Source: path,
Destination: m.Destination,
Writable: m.RW,
@ -35,7 +34,7 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.
if m.Volume != nil {
attributes := map[string]string{
"driver": m.Volume.DriverName(),
"container": container.ID,
"container": c.ID,
"destination": m.Destination,
"read/write": strconv.FormatBool(m.RW),
"propagation": m.Propagation,
@ -47,7 +46,7 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.
}
mounts = sortMounts(mounts)
netMounts := container.NetworkMounts()
netMounts := c.NetworkMounts()
// if we are going to mount any of the network files from container
// metadata, the ownership must be set properly for potential container
// remapped root (user namespaces)
@ -63,7 +62,7 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.
// sortMounts sorts an array of mounts in lexicographic order. This ensure that
// when mounting, the mounts don't shadow other mounts. For example, if mounting
// /etc and /etc/resolv.conf, /etc/resolv.conf must not be mounted first.
func sortMounts(m []execdriver.Mount) []execdriver.Mount {
func sortMounts(m []container.Mount) []container.Mount {
sort.Sort(mounts(m))
return m
}

View file

@ -7,18 +7,22 @@ import (
"sort"
"github.com/docker/docker/container"
"github.com/docker/docker/daemon/execdriver"
"github.com/docker/docker/volume"
)
// setupMounts configures the mount points for a container by appending each
// of the configured mounts on the container to the execdriver mount structure
// of the configured mounts on the container to the oci mount structure
// which will ultimately be passed into the exec driver during container creation.
// It also ensures each of the mounts are lexographically sorted.
func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.Mount, error) {
var mnts []execdriver.Mount
for _, mount := range container.MountPoints { // type is volume.MountPoint
if err := daemon.lazyInitializeVolume(container.ID, mount); err != nil {
// BUGBUG TODO Windows containerd. This would be much better if it returned
// an array of windowsoci mounts, not container mounts. Then no need to
// do multiple transitions.
func (daemon *Daemon) setupMounts(c *container.Container) ([]container.Mount, error) {
var mnts []container.Mount
for _, mount := range c.MountPoints { // type is volume.MountPoint
if err := daemon.lazyInitializeVolume(c.ID, mount); err != nil {
return nil, err
}
// If there is no source, take it from the volume path
@ -29,7 +33,7 @@ func (daemon *Daemon) setupMounts(container *container.Container) ([]execdriver.
if s == "" {
return nil, fmt.Errorf("No source for mount name '%s' driver %q destination '%s'", mount.Name, mount.Driver, mount.Destination)
}
mnts = append(mnts, execdriver.Mount{
mnts = append(mnts, container.Mount{
Source: s,
Destination: mount.Destination,
Writable: mount.RW,

View file

@ -112,12 +112,13 @@ func (ls *mockLayerStore) CreateRWLayer(string, layer.ChainID, string, layer.Mou
func (ls *mockLayerStore) GetRWLayer(string) (layer.RWLayer, error) {
return nil, errors.New("not implemented")
}
func (ls *mockLayerStore) ReleaseRWLayer(layer.RWLayer) ([]layer.Metadata, error) {
return nil, errors.New("not implemented")
}
func (ls *mockLayerStore) GetMountID(string) (string, error) {
return "", errors.New("not implemented")
}
func (ls *mockLayerStore) Cleanup() error {

View file

@ -29,6 +29,7 @@ import (
"github.com/docker/docker/daemon/logger"
"github.com/docker/docker/docker/listeners"
"github.com/docker/docker/dockerversion"
"github.com/docker/docker/libcontainerd"
"github.com/docker/docker/opts"
"github.com/docker/docker/pkg/jsonlog"
flag "github.com/docker/docker/pkg/mflag"
@ -264,7 +265,13 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
cli.TrustKeyPath = commonFlags.TrustKey
registryService := registry.NewService(cli.Config.ServiceOptions)
d, err := daemon.NewDaemon(cli.Config, registryService)
containerdRemote, err := libcontainerd.New(filepath.Join(cli.Config.ExecRoot, "libcontainerd"), cli.getPlatformRemoteOptions()...)
if err != nil {
logrus.Fatal(err)
}
d, err := daemon.NewDaemon(cli.Config, registryService, containerdRemote)
if err != nil {
if pfile != nil {
if err := pfile.Remove(); err != nil {
@ -279,7 +286,6 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
logrus.WithFields(logrus.Fields{
"version": dockerversion.Version,
"commit": dockerversion.GitCommit,
"execdriver": d.ExecutionDriver().Name(),
"graphdriver": d.GraphDriverName(),
}).Info("Docker daemon")
@ -330,6 +336,7 @@ func (cli *DaemonCli) CmdDaemon(args ...string) error {
// Wait for serve API to complete
errAPI := <-serveAPIWait
shutdownDaemon(d, 15)
containerdRemote.Cleanup()
if errAPI != nil {
if pfile != nil {
if err := pfile.Remove(); err != nil {

Some files were not shown because too many files have changed in this diff Show more