2015-08-07 16:33:29 +00:00
// +build linux freebsd
2015-05-15 23:34:26 +00:00
2018-02-05 21:05:59 +00:00
package daemon // import "github.com/docker/docker/daemon"
2015-05-15 23:34:26 +00:00
import (
2016-10-04 19:35:56 +00:00
"bufio"
2017-09-22 13:52:41 +00:00
"context"
2015-05-15 23:34:26 +00:00
"fmt"
2015-12-02 10:26:30 +00:00
"io/ioutil"
2015-05-15 23:34:26 +00:00
"net"
"os"
"path/filepath"
2016-01-08 03:43:11 +00:00
"runtime"
2015-12-02 10:26:30 +00:00
"runtime/debug"
2015-10-10 16:43:03 +00:00
"strconv"
2015-05-15 23:34:26 +00:00
"strings"
2016-11-01 17:12:29 +00:00
"time"
2015-05-15 23:34:26 +00:00
2017-09-22 13:52:41 +00:00
containerd_cgroups "github.com/containerd/cgroups"
2016-09-06 18:18:12 +00:00
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/blkiodev"
pblkiodev "github.com/docker/docker/api/types/blkiodev"
containertypes "github.com/docker/docker/api/types/container"
2015-11-12 19:55:17 +00:00
"github.com/docker/docker/container"
2017-01-23 11:23:07 +00:00
"github.com/docker/docker/daemon/config"
2018-02-13 19:29:14 +00:00
"github.com/docker/docker/daemon/initlayer"
2019-09-02 21:39:24 +00:00
"github.com/docker/docker/errdefs"
2016-12-23 19:09:12 +00:00
"github.com/docker/docker/opts"
2017-08-04 00:22:00 +00:00
"github.com/docker/docker/pkg/containerfs"
2015-10-08 15:51:41 +00:00
"github.com/docker/docker/pkg/idtools"
2017-09-22 13:52:41 +00:00
"github.com/docker/docker/pkg/ioutils"
2018-01-23 19:08:55 +00:00
"github.com/docker/docker/pkg/mount"
2016-01-23 02:15:09 +00:00
"github.com/docker/docker/pkg/parsers"
2015-05-15 23:34:26 +00:00
"github.com/docker/docker/pkg/parsers/kernel"
2015-08-06 11:54:48 +00:00
"github.com/docker/docker/pkg/sysinfo"
2015-05-15 23:34:26 +00:00
"github.com/docker/docker/runconfig"
2018-04-17 20:50:28 +00:00
volumemounts "github.com/docker/docker/volume/mounts"
2015-05-15 23:34:26 +00:00
"github.com/docker/libnetwork"
2015-05-20 12:20:19 +00:00
nwconfig "github.com/docker/libnetwork/config"
2015-10-10 16:43:03 +00:00
"github.com/docker/libnetwork/drivers/bridge"
2015-05-15 23:34:26 +00:00
"github.com/docker/libnetwork/netlabel"
2016-05-08 07:33:16 +00:00
"github.com/docker/libnetwork/netutils"
2015-05-15 23:34:26 +00:00
"github.com/docker/libnetwork/options"
2016-03-18 18:50:19 +00:00
lntypes "github.com/docker/libnetwork/types"
2016-06-07 19:05:43 +00:00
"github.com/opencontainers/runc/libcontainer/cgroups"
2016-09-21 07:36:36 +00:00
rsystem "github.com/opencontainers/runc/libcontainer/system"
2018-05-19 11:38:54 +00:00
"github.com/opencontainers/runtime-spec/specs-go"
2017-04-18 13:26:36 +00:00
"github.com/opencontainers/selinux/go-selinux/label"
2016-09-17 05:46:20 +00:00
"github.com/pkg/errors"
2017-07-26 21:42:13 +00:00
"github.com/sirupsen/logrus"
2016-09-27 20:16:00 +00:00
"github.com/vishvananda/netlink"
2017-07-27 07:51:23 +00:00
"golang.org/x/sys/unix"
2015-05-15 23:34:26 +00:00
)
2015-08-06 00:15:14 +00:00
const (
2017-09-22 13:52:41 +00:00
// DefaultShimBinary is the default shim to be used by containerd if none
// is specified
2018-09-21 22:58:34 +00:00
DefaultShimBinary = "containerd-shim"
2017-09-22 13:52:41 +00:00
// DefaultRuntimeBinary is the default runtime to be used by
// containerd if none is specified
2018-09-21 22:58:34 +00:00
DefaultRuntimeBinary = "runc"
2017-09-22 13:52:41 +00:00
2015-08-06 00:15:14 +00:00
// See https://git.kernel.org/cgit/linux/kernel/git/tip/tip.git/tree/kernel/sched/sched.h?id=8cd9234c64c584432f6992fe944ca9e46ca8ea76#n269
linuxMinCPUShares = 2
linuxMaxCPUShares = 262144
2015-08-07 16:33:29 +00:00
platformSupported = true
2015-12-09 06:26:41 +00:00
// It's not kernel limit, we want this 4M limit to supply a reasonable functional container
linuxMinMemory = 4194304
2016-01-08 03:43:11 +00:00
// constants for remapped root settings
2018-05-19 11:38:54 +00:00
defaultIDSpecifier = "default"
defaultRemappedID = "dockremap"
2016-03-18 19:43:17 +00:00
// constant for cgroup drivers
cgroupFsDriver = "cgroupfs"
cgroupSystemdDriver = "systemd"
2019-06-02 15:03:27 +00:00
cgroupNoneDriver = "none"
2017-09-22 13:52:41 +00:00
// DefaultRuntimeName is the default runtime to be used by
// containerd if none is specified
2018-09-21 22:58:34 +00:00
DefaultRuntimeName = "runc"
2015-08-06 00:15:14 +00:00
)
2017-08-01 19:04:37 +00:00
type containerGetter interface {
GetContainer ( string ) ( * container . Container , error )
}
2017-04-27 21:52:47 +00:00
func getMemoryResources ( config containertypes . Resources ) * specs . LinuxMemory {
memory := specs . LinuxMemory { }
2016-03-18 18:50:19 +00:00
if config . Memory > 0 {
2017-08-01 15:51:24 +00:00
memory . Limit = & config . Memory
2016-03-18 18:50:19 +00:00
}
if config . MemoryReservation > 0 {
2017-08-01 15:51:24 +00:00
memory . Reservation = & config . MemoryReservation
2016-03-18 18:50:19 +00:00
}
2017-04-27 21:52:47 +00:00
if config . MemorySwap > 0 {
2017-08-01 15:51:24 +00:00
memory . Swap = & config . MemorySwap
2016-03-18 18:50:19 +00:00
}
if config . MemorySwappiness != nil {
swappiness := uint64 ( * config . MemorySwappiness )
memory . Swappiness = & swappiness
}
2018-02-05 02:05:57 +00:00
if config . OomKillDisable != nil {
memory . DisableOOMKiller = config . OomKillDisable
}
2016-03-18 18:50:19 +00:00
if config . KernelMemory != 0 {
2017-08-01 15:51:24 +00:00
memory . Kernel = & config . KernelMemory
2016-03-18 18:50:19 +00:00
}
2018-05-11 19:46:11 +00:00
if config . KernelMemoryTCP != 0 {
memory . KernelTCP = & config . KernelMemoryTCP
}
2016-03-18 18:50:19 +00:00
return & memory
}
2017-04-11 11:28:13 +00:00
func getPidsLimit ( config containertypes . Resources ) * specs . LinuxPids {
2019-02-24 14:36:45 +00:00
if config . PidsLimit == nil {
return nil
}
if * config . PidsLimit <= 0 {
// docker API allows 0 and negative values to unset this to be consistent
// with default values. When updating values, runc requires -1 to unset
// the previous limit.
return & specs . LinuxPids { Limit : - 1 }
2017-04-11 11:28:13 +00:00
}
2019-02-24 14:36:45 +00:00
return & specs . LinuxPids { Limit : * config . PidsLimit }
2017-04-11 11:28:13 +00:00
}
2017-04-27 21:52:47 +00:00
func getCPUResources ( config containertypes . Resources ) ( * specs . LinuxCPU , error ) {
cpu := specs . LinuxCPU { }
2016-03-18 18:50:19 +00:00
2017-04-27 21:52:47 +00:00
if config . CPUShares < 0 {
return nil , fmt . Errorf ( "shares: invalid argument" )
}
if config . CPUShares >= 0 {
2016-03-18 18:50:19 +00:00
shares := uint64 ( config . CPUShares )
cpu . Shares = & shares
}
if config . CpusetCpus != "" {
2017-04-27 21:52:47 +00:00
cpu . Cpus = config . CpusetCpus
2016-03-18 18:50:19 +00:00
}
if config . CpusetMems != "" {
2017-04-27 21:52:47 +00:00
cpu . Mems = config . CpusetMems
2016-03-18 18:50:19 +00:00
}
2016-11-01 17:12:29 +00:00
if config . NanoCPUs > 0 {
// https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
2016-11-18 00:39:47 +00:00
period := uint64 ( 100 * time . Millisecond / time . Microsecond )
2017-04-27 21:52:47 +00:00
quota := config . NanoCPUs * int64 ( period ) / 1e9
2016-11-01 17:12:29 +00:00
cpu . Period = & period
cpu . Quota = & quota
}
2016-03-18 18:50:19 +00:00
if config . CPUPeriod != 0 {
period := uint64 ( config . CPUPeriod )
cpu . Period = & period
}
if config . CPUQuota != 0 {
2017-04-27 21:52:47 +00:00
q := config . CPUQuota
cpu . Quota = & q
2016-03-18 18:50:19 +00:00
}
2016-06-07 19:05:43 +00:00
if config . CPURealtimePeriod != 0 {
period := uint64 ( config . CPURealtimePeriod )
cpu . RealtimePeriod = & period
}
if config . CPURealtimeRuntime != 0 {
2017-04-27 21:52:47 +00:00
c := config . CPURealtimeRuntime
cpu . RealtimeRuntime = & c
2016-06-07 19:05:43 +00:00
}
2017-04-27 21:52:47 +00:00
return & cpu , nil
2016-03-18 18:50:19 +00:00
}
2017-04-27 21:52:47 +00:00
func getBlkioWeightDevices ( config containertypes . Resources ) ( [ ] specs . LinuxWeightDevice , error ) {
2017-07-27 07:51:23 +00:00
var stat unix . Stat_t
2017-04-27 21:52:47 +00:00
var blkioWeightDevices [ ] specs . LinuxWeightDevice
2015-06-12 00:34:20 +00:00
for _ , weightDevice := range config . BlkioWeightDevice {
2017-07-27 07:51:23 +00:00
if err := unix . Stat ( weightDevice . Path , & stat ) ; err != nil {
2015-06-12 00:34:20 +00:00
return nil , err
}
2016-03-18 18:50:19 +00:00
weight := weightDevice . Weight
2017-04-27 21:52:47 +00:00
d := specs . LinuxWeightDevice { Weight : & weight }
2019-05-14 07:21:55 +00:00
d . Major = int64 ( unix . Major ( stat . Rdev ) )
d . Minor = int64 ( unix . Minor ( stat . Rdev ) )
2016-03-18 18:50:19 +00:00
blkioWeightDevices = append ( blkioWeightDevices , d )
2015-06-12 00:34:20 +00:00
}
2015-12-15 01:50:16 +00:00
return blkioWeightDevices , nil
2015-06-12 00:34:20 +00:00
}
2017-01-09 01:22:05 +00:00
func ( daemon * Daemon ) parseSecurityOpt ( container * container . Container , hostConfig * containertypes . HostConfig ) error {
container . NoNewPrivileges = daemon . configStore . NoNewPrivileges
return parseSecurityOpt ( container , hostConfig )
}
2015-12-18 18:36:17 +00:00
func parseSecurityOpt ( container * container . Container , config * containertypes . HostConfig ) error {
2015-05-15 23:34:26 +00:00
var (
labelOpts [ ] string
err error
)
for _ , opt := range config . SecurityOpt {
2016-03-15 22:34:29 +00:00
if opt == "no-new-privileges" {
container . NoNewPrivileges = true
2016-11-01 02:35:18 +00:00
continue
}
2017-04-27 21:52:47 +00:00
if opt == "disable" {
labelOpts = append ( labelOpts , "disable" )
continue
}
2016-11-01 02:35:18 +00:00
var con [ ] string
if strings . Contains ( opt , "=" ) {
con = strings . SplitN ( opt , "=" , 2 )
} else if strings . Contains ( opt , ":" ) {
con = strings . SplitN ( opt , ":" , 2 )
2017-02-16 07:22:50 +00:00
logrus . Warn ( "Security options with `:` as a separator are deprecated and will be completely unsupported in 17.04, use `=` instead." )
2016-11-01 02:35:18 +00:00
}
if len ( con ) != 2 {
return fmt . Errorf ( "invalid --security-opt 1: %q" , opt )
}
switch con [ 0 ] {
case "label" :
labelOpts = append ( labelOpts , con [ 1 ] )
case "apparmor" :
container . AppArmorProfile = con [ 1 ]
case "seccomp" :
container . SeccompProfile = con [ 1 ]
2017-01-09 01:22:05 +00:00
case "no-new-privileges" :
noNewPrivileges , err := strconv . ParseBool ( con [ 1 ] )
if err != nil {
return fmt . Errorf ( "invalid --security-opt 2: %q" , opt )
}
container . NoNewPrivileges = noNewPrivileges
2016-11-01 02:35:18 +00:00
default :
return fmt . Errorf ( "invalid --security-opt 2: %q" , opt )
2015-05-15 23:34:26 +00:00
}
}
container . ProcessLabel , container . MountLabel , err = label . InitLabels ( labelOpts )
return err
}
2017-04-27 21:52:47 +00:00
func getBlkioThrottleDevices ( devs [ ] * blkiodev . ThrottleDevice ) ( [ ] specs . LinuxThrottleDevice , error ) {
var throttleDevices [ ] specs . LinuxThrottleDevice
2017-07-27 07:51:23 +00:00
var stat unix . Stat_t
2015-07-08 11:06:48 +00:00
2016-04-29 20:39:04 +00:00
for _ , d := range devs {
2017-07-27 07:51:23 +00:00
if err := unix . Stat ( d . Path , & stat ) ; err != nil {
2015-07-08 11:06:48 +00:00
return nil , err
}
2017-04-27 21:52:47 +00:00
d := specs . LinuxThrottleDevice { Rate : d . Rate }
2019-05-14 07:21:55 +00:00
d . Major = int64 ( unix . Major ( stat . Rdev ) )
d . Minor = int64 ( unix . Minor ( stat . Rdev ) )
2016-04-29 20:39:04 +00:00
throttleDevices = append ( throttleDevices , d )
2015-07-08 11:06:48 +00:00
}
2016-04-29 20:39:04 +00:00
return throttleDevices , nil
2015-07-08 11:06:48 +00:00
}
2018-12-04 16:44:45 +00:00
// adjustParallelLimit takes a number of objects and a proposed limit and
// figures out if it's reasonable (and adjusts it accordingly). This is only
// used for daemon startup, which does a lot of parallel loading of containers
// (and if we exceed RLIMIT_NOFILE then we're in trouble).
func adjustParallelLimit ( n int , limit int ) int {
// Rule-of-thumb overhead factor (how many files will each goroutine open
// simultaneously). Yes, this is ugly but to be frank this whole thing is
// ugly.
const overhead = 2
// On Linux, we need to ensure that parallelStartupJobs doesn't cause us to
// exceed RLIMIT_NOFILE. If parallelStartupJobs is too large, we reduce it
// and give a warning (since in theory the user should increase their
// ulimits to the largest possible value for dockerd).
var rlim unix . Rlimit
if err := unix . Getrlimit ( unix . RLIMIT_NOFILE , & rlim ) ; err != nil {
logrus . Warnf ( "Couldn't find dockerd's RLIMIT_NOFILE to double-check startup parallelism factor: %v" , err )
return limit
}
softRlimit := int ( rlim . Cur )
// Much fewer containers than RLIMIT_NOFILE. No need to adjust anything.
if softRlimit > overhead * n {
return limit
}
// RLIMIT_NOFILE big enough, no need to adjust anything.
if softRlimit > overhead * limit {
return limit
}
logrus . Warnf ( "Found dockerd's open file ulimit (%v) is far too small -- consider increasing it significantly (at least %v)" , softRlimit , overhead * limit )
return softRlimit / overhead
}
2015-05-15 23:34:26 +00:00
func checkKernel ( ) error {
// Check for unsupported kernel versions
// FIXME: it would be cleaner to not test for specific versions, but rather
// test for specific functionalities.
// Unfortunately we can't test for the feature "does not cause a kernel panic"
// without actually causing a kernel panic, so we need this workaround until
// the circumstances of pre-3.10 crashes are clearer.
// For details see https://github.com/docker/docker/issues/407
2016-04-15 12:33:56 +00:00
// Docker 1.11 and above doesn't actually run on kernels older than 3.4,
// due to containerd-shim usage of PR_SET_CHILD_SUBREAPER (introduced in 3.4).
2016-08-02 05:17:20 +00:00
if ! kernel . CheckKernelVersion ( 3 , 10 , 0 ) {
2015-08-19 02:40:22 +00:00
v , _ := kernel . GetKernelVersion ( )
if os . Getenv ( "DOCKER_NOWARN_KERNEL_VERSION" ) == "" {
2016-04-15 12:33:56 +00:00
logrus . Fatalf ( "Your Linux kernel version %s is not supported for running docker. Please upgrade your kernel to 3.10.0 or newer." , v . String ( ) )
2015-05-15 23:34:26 +00:00
}
}
return nil
}
2015-07-30 22:28:11 +00:00
// adaptContainerSettings is called during container creation to modify any
// settings necessary in the HostConfig structure.
2015-12-18 18:36:17 +00:00
func ( daemon * Daemon ) adaptContainerSettings ( hostConfig * containertypes . HostConfig , adjustCPUShares bool ) error {
2015-08-06 00:15:14 +00:00
if adjustCPUShares && hostConfig . CPUShares > 0 {
// Handle unsupported CPUShares
if hostConfig . CPUShares < linuxMinCPUShares {
logrus . Warnf ( "Changing requested CPUShares of %d to minimum allowed of %d" , hostConfig . CPUShares , linuxMinCPUShares )
hostConfig . CPUShares = linuxMinCPUShares
} else if hostConfig . CPUShares > linuxMaxCPUShares {
logrus . Warnf ( "Changing requested CPUShares of %d to maximum allowed of %d" , hostConfig . CPUShares , linuxMaxCPUShares )
hostConfig . CPUShares = linuxMaxCPUShares
}
}
2015-07-13 07:17:43 +00:00
if hostConfig . Memory > 0 && hostConfig . MemorySwap == 0 {
// By default, MemorySwap is set to twice the size of Memory.
hostConfig . MemorySwap = hostConfig . Memory * 2
}
2015-12-29 20:49:17 +00:00
if hostConfig . ShmSize == 0 {
2017-01-23 11:23:07 +00:00
hostConfig . ShmSize = config . DefaultShmSize
2016-12-25 09:11:12 +00:00
if daemon . configStore != nil {
hostConfig . ShmSize = int64 ( daemon . configStore . ShmSize )
}
2015-11-26 12:14:09 +00:00
}
Implement none, private, and shareable ipc modes
Since the commit d88fe447df0e8 ("Add support for sharing /dev/shm/ and
/dev/mqueue between containers") container's /dev/shm is mounted on the
host first, then bind-mounted inside the container. This is done that
way in order to be able to share this container's IPC namespace
(and the /dev/shm mount point) with another container.
Unfortunately, this functionality breaks container checkpoint/restore
(even if IPC is not shared). Since /dev/shm is an external mount, its
contents is not saved by `criu checkpoint`, and so upon restore any
application that tries to access data under /dev/shm is severily
disappointed (which usually results in a fatal crash).
This commit solves the issue by introducing new IPC modes for containers
(in addition to 'host' and 'container:ID'). The new modes are:
- 'shareable': enables sharing this container's IPC with others
(this used to be the implicit default);
- 'private': disables sharing this container's IPC.
In 'private' mode, container's /dev/shm is truly mounted inside the
container, without any bind-mounting from the host, which solves the
issue.
While at it, let's also implement 'none' mode. The motivation, as
eloquently put by Justin Cormack, is:
> I wondered a while back about having a none shm mode, as currently it is
> not possible to have a totally unwriteable container as there is always
> a /dev/shm writeable mount. It is a bit of a niche case (and clearly
> should never be allowed to be daemon default) but it would be trivial to
> add now so maybe we should...
...so here's yet yet another mode:
- 'none': no /dev/shm mount inside the container (though it still
has its own private IPC namespace).
Now, to ultimately solve the abovementioned checkpoint/restore issue, we'd
need to make 'private' the default mode, but unfortunately it breaks the
backward compatibility. So, let's make the default container IPC mode
per-daemon configurable (with the built-in default set to 'shareable'
for now). The default can be changed either via a daemon CLI option
(--default-shm-mode) or a daemon.json configuration file parameter
of the same name.
Note one can only set either 'shareable' or 'private' IPC modes as a
daemon default (i.e. in this context 'host', 'container', or 'none'
do not make much sense).
Some other changes this patch introduces are:
1. A mount for /dev/shm is added to default OCI Linux spec.
2. IpcMode.Valid() is simplified to remove duplicated code that parsed
'container:ID' form. Note the old version used to check that ID does
not contain a semicolon -- this is no longer the case (tests are
modified accordingly). The motivation is we should either do a
proper check for container ID validity, or don't check it at all
(since it is checked in other places anyway). I chose the latter.
3. IpcMode.Container() is modified to not return container ID if the
mode value does not start with "container:", unifying the check to
be the same as in IpcMode.IsContainer().
3. IPC mode unit tests (runconfig/hostconfig_test.go) are modified
to add checks for newly added values.
[v2: addressed review at https://github.com/moby/moby/pull/34087#pullrequestreview-51345997]
[v3: addressed review at https://github.com/moby/moby/pull/34087#pullrequestreview-53902833]
[v4: addressed the case of upgrading from older daemon, in this case
container.HostConfig.IpcMode is unset and this is valid]
[v5: document old and new IpcMode values in api/swagger.yaml]
[v6: add the 'none' mode, changelog entry to docs/api/version-history.md]
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2017-06-27 21:58:50 +00:00
// Set default IPC mode, if unset for container
if hostConfig . IpcMode . IsEmpty ( ) {
m := config . DefaultIpcMode
if daemon . configStore != nil {
m = daemon . configStore . IpcMode
}
hostConfig . IpcMode = containertypes . IpcMode ( m )
}
2017-08-01 19:04:37 +00:00
adaptSharedNamespaceContainer ( daemon , hostConfig )
2015-11-30 05:10:18 +00:00
var err error
2017-02-01 21:52:36 +00:00
opts , err := daemon . generateSecurityOpt ( hostConfig )
2016-11-03 16:44:40 +00:00
if err != nil {
return err
2015-11-30 05:10:18 +00:00
}
2016-11-03 16:44:40 +00:00
hostConfig . SecurityOpt = append ( hostConfig . SecurityOpt , opts ... )
2015-12-31 06:17:18 +00:00
if hostConfig . OomKillDisable == nil {
defaultOomKillDisable := false
hostConfig . OomKillDisable = & defaultOomKillDisable
}
2015-11-30 05:10:18 +00:00
return nil
2015-07-13 07:17:43 +00:00
}
2017-08-01 19:04:37 +00:00
// adaptSharedNamespaceContainer replaces container name with its ID in hostConfig.
// To be more precisely, it modifies `container:name` to `container:ID` of PidMode, IpcMode
// and NetworkMode.
//
// When a container shares its namespace with another container, use ID can keep the namespace
// sharing connection between the two containers even the another container is renamed.
func adaptSharedNamespaceContainer ( daemon containerGetter , hostConfig * containertypes . HostConfig ) {
containerPrefix := "container:"
if hostConfig . PidMode . IsContainer ( ) {
pidContainer := hostConfig . PidMode . Container ( )
// if there is any error returned here, we just ignore it and leave it to be
// handled in the following logic
if c , err := daemon . GetContainer ( pidContainer ) ; err == nil {
hostConfig . PidMode = containertypes . PidMode ( containerPrefix + c . ID )
}
}
if hostConfig . IpcMode . IsContainer ( ) {
ipcContainer := hostConfig . IpcMode . Container ( )
if c , err := daemon . GetContainer ( ipcContainer ) ; err == nil {
hostConfig . IpcMode = containertypes . IpcMode ( containerPrefix + c . ID )
}
}
if hostConfig . NetworkMode . IsContainer ( ) {
netContainer := hostConfig . NetworkMode . ConnectedContainer ( )
if c , err := daemon . GetContainer ( netContainer ) ; err == nil {
hostConfig . NetworkMode = containertypes . NetworkMode ( containerPrefix + c . ID )
}
}
}
2018-12-18 22:41:52 +00:00
// verifyPlatformContainerResources performs platform-specific validation of the container's resource-configuration
func verifyPlatformContainerResources ( resources * containertypes . Resources , sysInfo * sysinfo . SysInfo , update bool ) ( warnings [ ] string , err error ) {
2017-06-30 17:34:40 +00:00
fixMemorySwappiness ( resources )
2015-05-15 23:34:26 +00:00
2015-08-06 11:55:56 +00:00
// memory subsystem checks and adjustments
2015-12-11 02:59:29 +00:00
if resources . Memory != 0 && resources . Memory < linuxMinMemory {
2015-05-15 23:34:26 +00:00
return warnings , fmt . Errorf ( "Minimum memory limit allowed is 4MB" )
}
2015-12-11 02:59:29 +00:00
if resources . Memory > 0 && ! sysInfo . MemoryLimit {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded." )
2015-12-11 02:59:29 +00:00
resources . Memory = 0
resources . MemorySwap = - 1
2015-05-15 23:34:26 +00:00
}
2015-12-11 02:59:29 +00:00
if resources . Memory > 0 && resources . MemorySwap != - 1 && ! sysInfo . SwapLimit {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support swap limit capabilities or the cgroup is not mounted. Memory limited without swap." )
2015-12-11 02:59:29 +00:00
resources . MemorySwap = - 1
2015-05-15 23:34:26 +00:00
}
2015-12-11 02:59:29 +00:00
if resources . Memory > 0 && resources . MemorySwap > 0 && resources . MemorySwap < resources . Memory {
2016-03-22 00:53:57 +00:00
return warnings , fmt . Errorf ( "Minimum memoryswap limit should be larger than memory limit, see usage" )
2015-05-15 23:34:26 +00:00
}
2016-02-24 05:36:47 +00:00
if resources . Memory == 0 && resources . MemorySwap > 0 && ! update {
2016-03-22 00:53:57 +00:00
return warnings , fmt . Errorf ( "You should always set the Memory limit when using Memoryswap limit, see usage" )
2015-05-15 23:34:26 +00:00
}
2017-06-30 17:34:40 +00:00
if resources . MemorySwappiness != nil && ! sysInfo . MemorySwappiness {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support memory swappiness capabilities or the cgroup is not mounted. Memory swappiness discarded." )
2015-12-11 02:59:29 +00:00
resources . MemorySwappiness = nil
2015-07-14 05:52:57 +00:00
}
2015-12-11 02:59:29 +00:00
if resources . MemorySwappiness != nil {
swappiness := * resources . MemorySwappiness
2017-06-30 17:34:40 +00:00
if swappiness < 0 || swappiness > 100 {
2016-03-22 00:53:57 +00:00
return warnings , fmt . Errorf ( "Invalid value: %v, valid memory swappiness range is 0-100" , swappiness )
2015-07-29 20:04:12 +00:00
}
2015-07-14 05:52:57 +00:00
}
2015-12-11 02:59:29 +00:00
if resources . MemoryReservation > 0 && ! sysInfo . MemoryReservation {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support memory soft limit capabilities or the cgroup is not mounted. Limitation discarded." )
2015-12-11 02:59:29 +00:00
resources . MemoryReservation = 0
2015-09-23 06:02:45 +00:00
}
2016-04-06 01:37:51 +00:00
if resources . MemoryReservation > 0 && resources . MemoryReservation < linuxMinMemory {
return warnings , fmt . Errorf ( "Minimum memory reservation allowed is 4MB" )
}
2015-12-11 02:59:29 +00:00
if resources . Memory > 0 && resources . MemoryReservation > 0 && resources . Memory < resources . MemoryReservation {
2016-07-11 10:29:17 +00:00
return warnings , fmt . Errorf ( "Minimum memory limit can not be less than memory reservation limit, see usage" )
2015-09-23 06:02:45 +00:00
}
2015-12-11 02:59:29 +00:00
if resources . KernelMemory > 0 && ! sysInfo . KernelMemory {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support kernel memory limit capabilities or the cgroup is not mounted. Limitation discarded." )
2015-12-11 02:59:29 +00:00
resources . KernelMemory = 0
2015-08-19 15:56:55 +00:00
}
2015-12-11 02:59:29 +00:00
if resources . KernelMemory > 0 && resources . KernelMemory < linuxMinMemory {
2015-12-09 06:26:41 +00:00
return warnings , fmt . Errorf ( "Minimum kernel memory limit allowed is 4MB" )
}
2016-08-02 05:17:20 +00:00
if resources . KernelMemory > 0 && ! kernel . CheckKernelVersion ( 4 , 0 , 0 ) {
2015-08-19 15:56:55 +00:00
warnings = append ( warnings , "You specified a kernel memory limit on a kernel older than 4.0. Kernel memory limits are experimental on older kernels, it won't work as expected and can cause your system to be unstable." )
}
2015-12-31 06:17:18 +00:00
if resources . OomKillDisable != nil && ! sysInfo . OomKillDisable {
2016-01-13 19:53:44 +00:00
// only produce warnings if the setting wasn't to *disable* the OOM Kill; no point
// warning the caller if they already wanted the feature to be off
if * resources . OomKillDisable {
2016-07-11 10:29:17 +00:00
warnings = append ( warnings , "Your kernel does not support OomKillDisable. OomKillDisable discarded." )
2016-01-13 19:53:44 +00:00
}
2015-12-31 06:17:18 +00:00
resources . OomKillDisable = nil
2015-12-22 08:08:04 +00:00
}
2018-12-17 10:23:41 +00:00
if resources . OomKillDisable != nil && * resources . OomKillDisable && resources . Memory == 0 {
warnings = append ( warnings , "OOM killer is disabled for the container, but no memory limit is set, this can result in the system running out of resources." )
}
2019-02-24 14:36:45 +00:00
if resources . PidsLimit != nil && ! sysInfo . PidsLimit {
if * resources . PidsLimit > 0 {
warnings = append ( warnings , "Your kernel does not support PIDs limit capabilities or the cgroup is not mounted. PIDs limit discarded." )
}
resources . PidsLimit = nil
2015-12-15 19:15:43 +00:00
}
2015-12-11 02:59:29 +00:00
// cpu subsystem checks and adjustments
2016-11-01 17:12:29 +00:00
if resources . NanoCPUs > 0 && resources . CPUPeriod > 0 {
return warnings , fmt . Errorf ( "Conflicting options: Nano CPUs and CPU Period cannot both be set" )
}
if resources . NanoCPUs > 0 && resources . CPUQuota > 0 {
return warnings , fmt . Errorf ( "Conflicting options: Nano CPUs and CPU Quota cannot both be set" )
}
if resources . NanoCPUs > 0 && ( ! sysInfo . CPUCfsPeriod || ! sysInfo . CPUCfsQuota ) {
return warnings , fmt . Errorf ( "NanoCPUs can not be set, as your kernel does not support CPU cfs period/quota or the cgroup is not mounted" )
}
2016-11-15 23:48:46 +00:00
// The highest precision we could get on Linux is 0.001, by setting
// cpu.cfs_period_us=1000ms
// cpu.cfs_quota=1ms
// See the following link for details:
// https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt
// Here we don't set the lower limit and it is up to the underlying platform (e.g., Linux) to return an error.
// The error message is 0.01 so that this is consistent with Windows
2016-11-01 17:12:29 +00:00
if resources . NanoCPUs < 0 || resources . NanoCPUs > int64 ( sysinfo . NumCPU ( ) ) * 1e9 {
2016-11-15 23:48:46 +00:00
return warnings , fmt . Errorf ( "Range of CPUs is from 0.01 to %d.00, as there are only %d CPUs available" , sysinfo . NumCPU ( ) , sysinfo . NumCPU ( ) )
2016-11-01 17:12:29 +00:00
}
2015-12-11 02:59:29 +00:00
if resources . CPUShares > 0 && ! sysInfo . CPUShares {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support CPU shares or the cgroup is not mounted. Shares discarded." )
2015-12-11 02:59:29 +00:00
resources . CPUShares = 0
2015-08-05 14:35:18 +00:00
}
2015-12-11 02:59:29 +00:00
if resources . CPUPeriod > 0 && ! sysInfo . CPUCfsPeriod {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support CPU cfs period or the cgroup is not mounted. Period discarded." )
2015-12-11 02:59:29 +00:00
resources . CPUPeriod = 0
2015-05-15 23:34:26 +00:00
}
2016-04-21 06:50:25 +00:00
if resources . CPUPeriod != 0 && ( resources . CPUPeriod < 1000 || resources . CPUPeriod > 1000000 ) {
2016-03-18 03:16:53 +00:00
return warnings , fmt . Errorf ( "CPU cfs period can not be less than 1ms (i.e. 1000) or larger than 1s (i.e. 1000000)" )
}
2015-12-11 02:59:29 +00:00
if resources . CPUQuota > 0 && ! sysInfo . CPUCfsQuota {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support CPU cfs quota or the cgroup is not mounted. Quota discarded." )
2015-12-11 02:59:29 +00:00
resources . CPUQuota = 0
2015-05-15 23:34:26 +00:00
}
2016-03-18 03:16:53 +00:00
if resources . CPUQuota > 0 && resources . CPUQuota < 1000 {
return warnings , fmt . Errorf ( "CPU cfs quota can not be less than 1ms (i.e. 1000)" )
}
2016-03-05 01:24:09 +00:00
if resources . CPUPercent > 0 {
2016-10-16 14:57:44 +00:00
warnings = append ( warnings , fmt . Sprintf ( "%s does not support CPU percent. Percent discarded." , runtime . GOOS ) )
2016-03-05 01:24:09 +00:00
resources . CPUPercent = 0
}
2015-12-11 02:59:29 +00:00
// cpuset subsystem checks and adjustments
if ( resources . CpusetCpus != "" || resources . CpusetMems != "" ) && ! sysInfo . Cpuset {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support cpuset or the cgroup is not mounted. Cpuset discarded." )
2015-12-11 02:59:29 +00:00
resources . CpusetCpus = ""
resources . CpusetMems = ""
2015-08-05 14:35:18 +00:00
}
2015-12-11 02:59:29 +00:00
cpusAvailable , err := sysInfo . IsCpusetCpusAvailable ( resources . CpusetCpus )
2015-09-08 18:40:55 +00:00
if err != nil {
2018-09-04 14:49:09 +00:00
return warnings , errors . Wrapf ( err , "Invalid value %s for cpuset cpus" , resources . CpusetCpus )
2015-09-08 18:40:55 +00:00
}
if ! cpusAvailable {
2016-03-22 00:53:57 +00:00
return warnings , fmt . Errorf ( "Requested CPUs are not available - requested %s, available: %s" , resources . CpusetCpus , sysInfo . Cpus )
2015-09-08 18:40:55 +00:00
}
2015-12-11 02:59:29 +00:00
memsAvailable , err := sysInfo . IsCpusetMemsAvailable ( resources . CpusetMems )
2015-09-08 18:40:55 +00:00
if err != nil {
2018-09-04 14:49:09 +00:00
return warnings , errors . Wrapf ( err , "Invalid value %s for cpuset mems" , resources . CpusetMems )
2015-09-08 18:40:55 +00:00
}
if ! memsAvailable {
2016-03-22 00:53:57 +00:00
return warnings , fmt . Errorf ( "Requested memory nodes are not available - requested %s, available: %s" , resources . CpusetMems , sysInfo . Mems )
2015-09-08 18:40:55 +00:00
}
2015-12-11 02:59:29 +00:00
// blkio subsystem checks and adjustments
if resources . BlkioWeight > 0 && ! sysInfo . BlkioWeight {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded." )
2015-12-11 02:59:29 +00:00
resources . BlkioWeight = 0
2015-08-05 14:35:18 +00:00
}
2015-12-11 02:59:29 +00:00
if resources . BlkioWeight > 0 && ( resources . BlkioWeight < 10 || resources . BlkioWeight > 1000 ) {
2016-03-22 00:53:57 +00:00
return warnings , fmt . Errorf ( "Range of blkio weight is from 10 to 1000" )
2015-05-15 23:34:26 +00:00
}
2016-02-25 01:51:46 +00:00
if resources . IOMaximumBandwidth != 0 || resources . IOMaximumIOps != 0 {
return warnings , fmt . Errorf ( "Invalid QoS settings: %s does not support Maximum IO Bandwidth or Maximum IO IOps" , runtime . GOOS )
}
2015-12-11 02:59:29 +00:00
if len ( resources . BlkioWeightDevice ) > 0 && ! sysInfo . BlkioWeightDevice {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support Block I/O weight_device or the cgroup is not mounted. Weight-device discarded." )
2015-12-11 02:59:29 +00:00
resources . BlkioWeightDevice = [ ] * pblkiodev . WeightDevice { }
2015-06-12 00:34:20 +00:00
}
2015-12-11 02:59:29 +00:00
if len ( resources . BlkioDeviceReadBps ) > 0 && ! sysInfo . BlkioReadBpsDevice {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support BPS Block I/O read limit or the cgroup is not mounted. Block I/O BPS read limit discarded." )
2015-12-11 02:59:29 +00:00
resources . BlkioDeviceReadBps = [ ] * pblkiodev . ThrottleDevice { }
2015-07-08 11:06:48 +00:00
}
2015-12-11 02:59:29 +00:00
if len ( resources . BlkioDeviceWriteBps ) > 0 && ! sysInfo . BlkioWriteBpsDevice {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support BPS Block I/O write limit or the cgroup is not mounted. Block I/O BPS write limit discarded." )
2015-12-11 02:59:29 +00:00
resources . BlkioDeviceWriteBps = [ ] * pblkiodev . ThrottleDevice { }
2017-07-19 14:20:13 +00:00
2015-07-08 11:06:48 +00:00
}
2015-07-08 11:06:48 +00:00
if len ( resources . BlkioDeviceReadIOps ) > 0 && ! sysInfo . BlkioReadIOpsDevice {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support IOPS Block read limit or the cgroup is not mounted. Block I/O IOPS read limit discarded." )
2015-07-08 11:06:48 +00:00
resources . BlkioDeviceReadIOps = [ ] * pblkiodev . ThrottleDevice { }
}
if len ( resources . BlkioDeviceWriteIOps ) > 0 && ! sysInfo . BlkioWriteIOpsDevice {
2016-07-18 20:56:41 +00:00
warnings = append ( warnings , "Your kernel does not support IOPS Block write limit or the cgroup is not mounted. Block I/O IOPS write limit discarded." )
2015-07-08 11:06:48 +00:00
resources . BlkioDeviceWriteIOps = [ ] * pblkiodev . ThrottleDevice { }
}
2015-12-11 02:59:29 +00:00
return warnings , nil
}
2016-02-18 10:10:31 +00:00
func ( daemon * Daemon ) getCgroupDriver ( ) string {
2019-06-02 15:03:27 +00:00
if daemon . Rootless ( ) {
return cgroupNoneDriver
}
2016-03-18 19:43:17 +00:00
cgroupDriver := cgroupFsDriver
2016-02-18 10:10:31 +00:00
2016-03-24 16:18:03 +00:00
if UsingSystemd ( daemon . configStore ) {
cgroupDriver = cgroupSystemdDriver
}
return cgroupDriver
}
// getCD gets the raw value of the native.cgroupdriver option, if set.
2017-01-23 11:23:07 +00:00
func getCD ( config * config . Config ) string {
2016-03-24 16:18:03 +00:00
for _ , option := range config . ExecOptions {
2016-01-23 02:15:09 +00:00
key , val , err := parsers . ParseKeyValueOpt ( option )
if err != nil || ! strings . EqualFold ( key , "native.cgroupdriver" ) {
continue
}
2016-03-24 16:18:03 +00:00
return val
2016-01-23 02:15:09 +00:00
}
2016-03-24 16:18:03 +00:00
return ""
2016-03-18 19:43:17 +00:00
}
2016-03-24 16:18:03 +00:00
// VerifyCgroupDriver validates native.cgroupdriver
2017-01-23 11:23:07 +00:00
func VerifyCgroupDriver ( config * config . Config ) error {
2016-03-24 16:18:03 +00:00
cd := getCD ( config )
if cd == "" || cd == cgroupFsDriver || cd == cgroupSystemdDriver {
return nil
}
2019-06-02 15:03:27 +00:00
if cd == cgroupNoneDriver {
return fmt . Errorf ( "native.cgroupdriver option %s is internally used and cannot be specified manually" , cd )
}
2016-03-24 16:18:03 +00:00
return fmt . Errorf ( "native.cgroupdriver option %s not supported" , cd )
2016-01-23 02:15:09 +00:00
}
2016-03-24 16:18:03 +00:00
// UsingSystemd returns true if cli option includes native.cgroupdriver=systemd
2017-01-23 11:23:07 +00:00
func UsingSystemd ( config * config . Config ) bool {
2016-03-24 16:18:03 +00:00
return getCD ( config ) == cgroupSystemdDriver
2016-01-23 02:15:09 +00:00
}
2015-12-11 02:59:29 +00:00
// verifyPlatformContainerSettings performs platform-specific validation of the
// hostconfig and config structures.
2018-12-18 22:20:17 +00:00
func verifyPlatformContainerSettings ( daemon * Daemon , hostConfig * containertypes . HostConfig , update bool ) ( warnings [ ] string , err error ) {
2018-12-19 00:28:08 +00:00
if hostConfig == nil {
return nil , nil
}
2015-12-11 02:59:29 +00:00
sysInfo := sysinfo . New ( true )
2018-12-18 22:41:52 +00:00
w , err := verifyPlatformContainerResources ( & hostConfig . Resources , sysInfo , update )
2016-08-31 16:23:56 +00:00
// no matter err is nil or not, w could have data in itself.
warnings = append ( warnings , w ... )
2015-12-11 02:59:29 +00:00
if err != nil {
return warnings , err
}
2015-12-29 20:49:17 +00:00
if hostConfig . ShmSize < 0 {
2016-07-11 10:29:17 +00:00
return warnings , fmt . Errorf ( "SHM size can not be less than 0" )
2015-12-11 02:59:29 +00:00
}
2015-10-13 09:26:27 +00:00
if hostConfig . OomScoreAdj < - 1000 || hostConfig . OomScoreAdj > 1000 {
2016-03-22 00:53:57 +00:00
return warnings , fmt . Errorf ( "Invalid value %d, range for oom score adj is [-1000, 1000]" , hostConfig . OomScoreAdj )
2015-10-13 09:26:27 +00:00
}
2016-05-18 18:10:31 +00:00
2016-05-20 15:39:05 +00:00
// ip-forwarding does not affect container with '--net=host' (or '--net=none')
if sysInfo . IPv4ForwardingDisabled && ! ( hostConfig . NetworkMode . IsHost ( ) || hostConfig . NetworkMode . IsNone ( ) ) {
2015-05-15 23:34:26 +00:00
warnings = append ( warnings , "IPv4 forwarding is disabled. Networking will not work." )
}
2018-12-18 21:42:57 +00:00
if hostConfig . NetworkMode . IsHost ( ) && len ( hostConfig . PortBindings ) > 0 {
warnings = append ( warnings , "Published ports are discarded when using host network mode" )
}
2016-01-08 14:03:17 +00:00
// check for various conflicting options with user namespaces
2016-02-08 14:23:24 +00:00
if daemon . configStore . RemappedRoot != "" && hostConfig . UsernsMode . IsPrivate ( ) {
2016-01-08 14:03:17 +00:00
if hostConfig . Privileged {
2017-08-17 19:16:30 +00:00
return warnings , fmt . Errorf ( "privileged mode is incompatible with user namespaces. You must run the container in the host namespace when running privileged mode" )
2016-01-08 14:03:17 +00:00
}
2016-08-16 21:16:14 +00:00
if hostConfig . NetworkMode . IsHost ( ) && ! hostConfig . UsernsMode . IsHost ( ) {
2017-08-17 19:16:30 +00:00
return warnings , fmt . Errorf ( "cannot share the host's network namespace when user namespaces are enabled" )
2016-01-08 14:03:17 +00:00
}
2016-08-16 21:16:14 +00:00
if hostConfig . PidMode . IsHost ( ) && ! hostConfig . UsernsMode . IsHost ( ) {
2017-08-17 19:16:30 +00:00
return warnings , fmt . Errorf ( "cannot share the host PID namespace when user namespaces are enabled" )
2016-01-08 14:03:17 +00:00
}
2016-01-08 03:43:11 +00:00
}
2016-03-24 16:18:03 +00:00
if hostConfig . CgroupParent != "" && UsingSystemd ( daemon . configStore ) {
2016-01-23 02:15:09 +00:00
// CgroupParent for systemd cgroup should be named as "xxx.slice"
if len ( hostConfig . CgroupParent ) <= 6 || ! strings . HasSuffix ( hostConfig . CgroupParent , ".slice" ) {
return warnings , fmt . Errorf ( "cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"" )
}
}
2016-05-23 21:49:50 +00:00
if hostConfig . Runtime == "" {
hostConfig . Runtime = daemon . configStore . GetDefaultRuntimeName ( )
}
if rt := daemon . configStore . GetRuntime ( hostConfig . Runtime ) ; rt == nil {
return warnings , fmt . Errorf ( "Unknown runtime specified %s" , hostConfig . Runtime )
}
2018-04-17 20:50:28 +00:00
parser := volumemounts . NewParser ( runtime . GOOS )
2017-01-16 09:52:43 +00:00
for dest := range hostConfig . Tmpfs {
2017-08-01 17:32:44 +00:00
if err := parser . ValidateTmpfsMountDestination ( dest ) ; err != nil {
2017-01-16 09:52:43 +00:00
return warnings , err
}
}
2015-05-15 23:34:26 +00:00
return warnings , nil
}
2017-09-22 13:52:41 +00:00
func ( daemon * Daemon ) loadRuntimes ( ) error {
return daemon . initRuntimes ( daemon . configStore . Runtimes )
}
func ( daemon * Daemon ) initRuntimes ( runtimes map [ string ] types . Runtime ) ( err error ) {
runtimeDir := filepath . Join ( daemon . configStore . Root , "runtimes" )
// Remove old temp directory if any
os . RemoveAll ( runtimeDir + "-old" )
tmpDir , err := ioutils . TempDir ( daemon . configStore . Root , "gen-runtimes" )
if err != nil {
2018-07-11 13:51:51 +00:00
return errors . Wrap ( err , "failed to get temp dir to generate runtime scripts" )
2017-09-22 13:52:41 +00:00
}
defer func ( ) {
if err != nil {
if err1 := os . RemoveAll ( tmpDir ) ; err1 != nil {
logrus . WithError ( err1 ) . WithField ( "dir" , tmpDir ) .
2018-07-11 13:51:51 +00:00
Warn ( "failed to remove tmp dir" )
2017-09-22 13:52:41 +00:00
}
return
}
if err = os . Rename ( runtimeDir , runtimeDir + "-old" ) ; err != nil {
return
}
if err = os . Rename ( tmpDir , runtimeDir ) ; err != nil {
2018-07-11 13:51:51 +00:00
err = errors . Wrap ( err , "failed to setup runtimes dir, new containers may not start" )
2017-09-22 13:52:41 +00:00
return
}
if err = os . RemoveAll ( runtimeDir + "-old" ) ; err != nil {
logrus . WithError ( err ) . WithField ( "dir" , tmpDir ) .
2018-07-11 13:51:51 +00:00
Warn ( "failed to remove old runtimes dir" )
2017-09-22 13:52:41 +00:00
}
} ( )
for name , rt := range runtimes {
if len ( rt . Args ) == 0 {
continue
}
script := filepath . Join ( tmpDir , name )
content := fmt . Sprintf ( "#!/bin/sh\n%s %s $@\n" , rt . Path , strings . Join ( rt . Args , " " ) )
if err := ioutil . WriteFile ( script , [ ] byte ( content ) , 0700 ) ; err != nil {
return err
}
}
return nil
}
2016-01-23 02:15:09 +00:00
// verifyDaemonSettings performs validation of daemon config struct
2017-01-23 11:23:07 +00:00
func verifyDaemonSettings ( conf * config . Config ) error {
2019-07-11 23:42:16 +00:00
if conf . ContainerdNamespace == conf . ContainerdPluginNamespace {
return errors . New ( "containers namespace and plugins namespace cannot be the same" )
}
2015-05-15 23:34:26 +00:00
// Check for mutually incompatible config options
2017-01-23 11:23:07 +00:00
if conf . BridgeConfig . Iface != "" && conf . BridgeConfig . IP != "" {
2016-02-03 14:56:34 +00:00
return fmt . Errorf ( "You specified -b & --bip, mutually exclusive options. Please specify only one" )
2015-05-15 23:34:26 +00:00
}
2017-01-23 11:23:07 +00:00
if ! conf . BridgeConfig . EnableIPTables && ! conf . BridgeConfig . InterContainerCommunication {
2016-02-03 14:56:34 +00:00
return fmt . Errorf ( "You specified --iptables=false with --icc=false. ICC=false uses iptables to function. Please set --icc or --iptables to true" )
2015-05-15 23:34:26 +00:00
}
2017-01-23 11:23:07 +00:00
if ! conf . BridgeConfig . EnableIPTables && conf . BridgeConfig . EnableIPMasq {
conf . BridgeConfig . EnableIPMasq = false
2015-05-15 23:34:26 +00:00
}
2017-01-23 11:23:07 +00:00
if err := VerifyCgroupDriver ( conf ) ; err != nil {
2016-03-24 16:18:03 +00:00
return err
}
2017-01-23 11:23:07 +00:00
if conf . CgroupParent != "" && UsingSystemd ( conf ) {
if len ( conf . CgroupParent ) <= 6 || ! strings . HasSuffix ( conf . CgroupParent , ".slice" ) {
2016-01-23 02:15:09 +00:00
return fmt . Errorf ( "cgroup-parent for systemd cgroup should be a valid slice named as \"xxx.slice\"" )
}
}
2016-05-23 21:49:50 +00:00
2017-01-23 11:23:07 +00:00
if conf . DefaultRuntime == "" {
conf . DefaultRuntime = config . StockRuntimeName
2016-05-23 21:49:50 +00:00
}
2017-01-23 11:23:07 +00:00
if conf . Runtimes == nil {
conf . Runtimes = make ( map [ string ] types . Runtime )
2016-05-23 21:49:50 +00:00
}
2017-09-22 13:52:41 +00:00
conf . Runtimes [ config . StockRuntimeName ] = types . Runtime { Path : DefaultRuntimeName }
2016-05-23 21:49:50 +00:00
2015-05-15 23:34:26 +00:00
return nil
}
2015-07-11 19:32:08 +00:00
// checkSystem validates platform-specific requirements
2015-05-15 23:34:26 +00:00
func checkSystem ( ) error {
2015-09-08 18:40:55 +00:00
return checkKernel ( )
2015-05-15 23:34:26 +00:00
}
2015-12-02 10:26:30 +00:00
// configureMaxThreads sets the Go runtime max threads threshold
// which is 90% of the kernel setting from /proc/sys/kernel/threads-max
2017-01-23 11:23:07 +00:00
func configureMaxThreads ( config * config . Config ) error {
2015-12-02 10:26:30 +00:00
mt , err := ioutil . ReadFile ( "/proc/sys/kernel/threads-max" )
if err != nil {
return err
}
mtint , err := strconv . Atoi ( strings . TrimSpace ( string ( mt ) ) )
if err != nil {
return err
}
maxThreads := ( mtint / 100 ) * 90
debug . SetMaxThreads ( maxThreads )
logrus . Debugf ( "Golang's threads limit set to %d" , maxThreads )
return nil
}
2016-10-04 19:35:56 +00:00
func overlaySupportsSelinux ( ) ( bool , error ) {
f , err := os . Open ( "/proc/kallsyms" )
if err != nil {
if os . IsNotExist ( err ) {
return false , nil
}
return false , err
}
defer f . Close ( )
var symAddr , symType , symName , text string
s := bufio . NewScanner ( f )
for s . Scan ( ) {
if err := s . Err ( ) ; err != nil {
return false , err
}
text = s . Text ( )
if _ , err := fmt . Sscanf ( text , "%s %s %s" , & symAddr , & symType , & symName ) ; err != nil {
return false , fmt . Errorf ( "Scanning '%s' failed: %s" , text , err )
}
// Check for presence of symbol security_inode_copy_up.
if symName == "security_inode_copy_up" {
return true , nil
}
}
return false , nil
}
2016-03-24 15:57:11 +00:00
// configureKernelSecuritySupport configures and validates security support for the kernel
2017-08-24 18:48:16 +00:00
func configureKernelSecuritySupport ( config * config . Config , driverName string ) error {
2015-05-15 23:34:26 +00:00
if config . EnableSelinuxSupport {
2016-08-11 13:41:26 +00:00
if ! selinuxEnabled ( ) {
2015-05-15 23:34:26 +00:00
logrus . Warn ( "Docker could not enable SELinux on the host system" )
2016-10-04 19:35:56 +00:00
return nil
}
2017-08-24 18:48:16 +00:00
if driverName == "overlay" || driverName == "overlay2" {
2016-10-04 19:35:56 +00:00
// If driver is overlay or overlay2, make sure kernel
// supports selinux with overlay.
supported , err := overlaySupportsSelinux ( )
if err != nil {
return err
}
if ! supported {
2017-08-24 18:48:16 +00:00
logrus . Warnf ( "SELinux is not supported with the %v graph driver on this kernel" , driverName )
2016-10-04 19:35:56 +00:00
}
2015-05-15 23:34:26 +00:00
}
} else {
selinuxSetDisabled ( )
}
return nil
}
2017-01-23 11:23:07 +00:00
func ( daemon * Daemon ) initNetworkController ( config * config . Config , activeSandboxes map [ string ] interface { } ) ( libnetwork . NetworkController , error ) {
2016-10-07 21:53:17 +00:00
netOptions , err := daemon . networkOptions ( config , daemon . PluginStore , activeSandboxes )
2015-05-20 12:20:19 +00:00
if err != nil {
return nil , err
}
controller , err := libnetwork . New ( netOptions ... )
2015-05-15 23:34:26 +00:00
if err != nil {
return nil , fmt . Errorf ( "error obtaining controller instance: %v" , err )
}
2016-06-14 16:13:53 +00:00
if len ( activeSandboxes ) > 0 {
2016-11-01 04:05:01 +00:00
logrus . Info ( "There are old running containers, the network config will not take affect" )
2016-06-14 16:13:53 +00:00
return controller , nil
}
2015-05-15 23:34:26 +00:00
// Initialize default network on "null"
2016-06-14 16:13:53 +00:00
if n , _ := controller . NetworkByName ( "none" ) ; n == nil {
if _ , err := controller . NewNetwork ( "null" , "none" , "" , libnetwork . NetworkOptionPersist ( true ) ) ; err != nil {
return nil , fmt . Errorf ( "Error creating default \"null\" network: %v" , err )
}
2015-05-15 23:34:26 +00:00
}
// Initialize default network on "host"
2016-06-14 16:13:53 +00:00
if n , _ := controller . NetworkByName ( "host" ) ; n == nil {
if _ , err := controller . NewNetwork ( "host" , "host" , "" , libnetwork . NetworkOptionPersist ( true ) ) ; err != nil {
return nil , fmt . Errorf ( "Error creating default \"host\" network: %v" , err )
}
2015-05-15 23:34:26 +00:00
}
2016-09-27 20:16:00 +00:00
// Clear stale bridge network
if n , err := controller . NetworkByName ( "bridge" ) ; err == nil {
if err = n . Delete ( ) ; err != nil {
return nil , fmt . Errorf ( "could not delete the default bridge network: %v" , err )
2016-12-13 23:04:59 +00:00
}
if len ( config . NetworkConfig . DefaultAddressPools . Value ( ) ) > 0 && ! daemon . configStore . LiveRestoreEnabled {
removeDefaultBridgeInterface ( )
2016-09-27 20:16:00 +00:00
}
}
2015-06-30 17:34:15 +00:00
if ! config . DisableBridge {
// Initialize default driver "bridge"
if err := initBridgeDriver ( controller , config ) ; err != nil {
return nil , err
}
2016-09-27 20:16:00 +00:00
} else {
removeDefaultBridgeInterface ( )
2015-06-30 17:34:15 +00:00
}
return controller , nil
}
2017-01-23 11:23:07 +00:00
func driverOptions ( config * config . Config ) [ ] nwconfig . Option {
2015-09-25 03:00:05 +00:00
bridgeConfig := options . Generic {
2017-01-23 11:23:07 +00:00
"EnableIPForwarding" : config . BridgeConfig . EnableIPForward ,
"EnableIPTables" : config . BridgeConfig . EnableIPTables ,
"EnableUserlandProxy" : config . BridgeConfig . EnableUserlandProxy ,
"UserlandProxyPath" : config . BridgeConfig . UserlandProxyPath }
2015-09-25 03:00:05 +00:00
bridgeOption := options . Generic { netlabel . GenericData : bridgeConfig }
2015-05-15 23:34:26 +00:00
2015-09-25 03:00:05 +00:00
dOptions := [ ] nwconfig . Option { }
dOptions = append ( dOptions , nwconfig . OptionDriverConfig ( "bridge" , bridgeOption ) )
return dOptions
}
2015-05-15 23:34:26 +00:00
2017-01-23 11:23:07 +00:00
func initBridgeDriver ( controller libnetwork . NetworkController , config * config . Config ) error {
2015-10-10 16:43:03 +00:00
bridgeName := bridge . DefaultBridgeName
2017-01-23 11:23:07 +00:00
if config . BridgeConfig . Iface != "" {
bridgeName = config . BridgeConfig . Iface
2015-10-10 16:43:03 +00:00
}
netOption := map [ string ] string {
bridge . BridgeName : bridgeName ,
bridge . DefaultBridge : strconv . FormatBool ( true ) ,
netlabel . DriverMTU : strconv . Itoa ( config . Mtu ) ,
2017-01-23 11:23:07 +00:00
bridge . EnableIPMasquerade : strconv . FormatBool ( config . BridgeConfig . EnableIPMasq ) ,
bridge . EnableICC : strconv . FormatBool ( config . BridgeConfig . InterContainerCommunication ) ,
2015-10-10 16:43:03 +00:00
}
// --ip processing
2017-01-23 11:23:07 +00:00
if config . BridgeConfig . DefaultIP != nil {
netOption [ bridge . DefaultBindingIP ] = config . BridgeConfig . DefaultIP . String ( )
2015-10-10 16:43:03 +00:00
}
2016-01-12 07:47:44 +00:00
var (
ipamV4Conf * libnetwork . IpamConf
ipamV6Conf * libnetwork . IpamConf
)
2015-10-10 16:43:03 +00:00
2016-01-12 07:47:44 +00:00
ipamV4Conf = & libnetwork . IpamConf { AuxAddresses : make ( map [ string ] string ) }
2015-10-10 16:43:03 +00:00
2016-09-17 05:46:20 +00:00
nwList , nw6List , err := netutils . ElectInterfaceAddresses ( bridgeName )
if err != nil {
return errors . Wrap ( err , "list bridge addresses failed" )
}
nw := nwList [ 0 ]
2017-01-23 11:23:07 +00:00
if len ( nwList ) > 1 && config . BridgeConfig . FixedCIDR != "" {
_ , fCIDR , err := net . ParseCIDR ( config . BridgeConfig . FixedCIDR )
2016-09-17 05:46:20 +00:00
if err != nil {
return errors . Wrap ( err , "parse CIDR failed" )
2015-10-10 16:43:03 +00:00
}
2016-09-17 05:46:20 +00:00
// Iterate through in case there are multiple addresses for the bridge
for _ , entry := range nwList {
if fCIDR . Contains ( entry . IP ) {
nw = entry
break
}
}
}
ipamV4Conf . PreferredPool = lntypes . GetIPNetCanonical ( nw ) . String ( )
hip , _ := lntypes . GetHostPartIP ( nw . IP , nw . Mask )
if hip . IsGlobalUnicast ( ) {
ipamV4Conf . Gateway = nw . IP . String ( )
2015-05-15 23:34:26 +00:00
}
2017-01-23 11:23:07 +00:00
if config . BridgeConfig . IP != "" {
ipamV4Conf . PreferredPool = config . BridgeConfig . IP
ip , _ , err := net . ParseCIDR ( config . BridgeConfig . IP )
2015-05-15 23:34:26 +00:00
if err != nil {
2015-06-30 17:34:15 +00:00
return err
2015-05-15 23:34:26 +00:00
}
2015-10-10 16:43:03 +00:00
ipamV4Conf . Gateway = ip . String ( )
2015-10-26 18:46:20 +00:00
} else if bridgeName == bridge . DefaultBridgeName && ipamV4Conf . PreferredPool != "" {
logrus . Infof ( "Default bridge (%s) is assigned with an IP address %s. Daemon option --bip can be used to set a preferred IP address" , bridgeName , ipamV4Conf . PreferredPool )
2015-05-15 23:34:26 +00:00
}
2017-01-23 11:23:07 +00:00
if config . BridgeConfig . FixedCIDR != "" {
_ , fCIDR , err := net . ParseCIDR ( config . BridgeConfig . FixedCIDR )
2015-05-15 23:34:26 +00:00
if err != nil {
2015-06-30 17:34:15 +00:00
return err
2015-05-15 23:34:26 +00:00
}
2015-10-10 16:43:03 +00:00
ipamV4Conf . SubPool = fCIDR . String ( )
2015-05-15 23:34:26 +00:00
}
2017-01-23 11:23:07 +00:00
if config . BridgeConfig . DefaultGatewayIPv4 != nil {
ipamV4Conf . AuxAddresses [ "DefaultGatewayIPv4" ] = config . BridgeConfig . DefaultGatewayIPv4 . String ( )
2015-10-10 16:43:03 +00:00
}
2016-01-12 07:47:44 +00:00
var deferIPv6Alloc bool
2017-01-23 11:23:07 +00:00
if config . BridgeConfig . FixedCIDRv6 != "" {
_ , fCIDRv6 , err := net . ParseCIDR ( config . BridgeConfig . FixedCIDRv6 )
2015-05-15 23:34:26 +00:00
if err != nil {
2015-06-30 17:34:15 +00:00
return err
2015-05-15 23:34:26 +00:00
}
2015-11-11 05:14:05 +00:00
// In case user has specified the daemon flag --fixed-cidr-v6 and the passed network has
// at least 48 host bits, we need to guarantee the current behavior where the containers'
// IPv6 addresses will be constructed based on the containers' interface MAC address.
// We do so by telling libnetwork to defer the IPv6 address allocation for the endpoints
// on this network until after the driver has created the endpoint and returned the
// constructed address. Libnetwork will then reserve this address with the ipam driver.
ones , _ := fCIDRv6 . Mask . Size ( )
deferIPv6Alloc = ones <= 80
2015-10-10 16:43:03 +00:00
if ipamV6Conf == nil {
2015-12-30 22:51:51 +00:00
ipamV6Conf = & libnetwork . IpamConf { AuxAddresses : make ( map [ string ] string ) }
2015-10-10 16:43:03 +00:00
}
ipamV6Conf . PreferredPool = fCIDRv6 . String ( )
2016-01-12 07:47:44 +00:00
// In case the --fixed-cidr-v6 is specified and the current docker0 bridge IPv6
// address belongs to the same network, we need to inform libnetwork about it, so
// that it can be reserved with IPAM and it will not be given away to somebody else
for _ , nw6 := range nw6List {
if fCIDRv6 . Contains ( nw6 . IP ) {
ipamV6Conf . Gateway = nw6 . IP . String ( )
break
}
}
2015-05-15 23:34:26 +00:00
}
2017-01-23 11:23:07 +00:00
if config . BridgeConfig . DefaultGatewayIPv6 != nil {
2015-10-10 16:43:03 +00:00
if ipamV6Conf == nil {
2015-12-30 22:51:51 +00:00
ipamV6Conf = & libnetwork . IpamConf { AuxAddresses : make ( map [ string ] string ) }
2015-10-10 16:43:03 +00:00
}
2017-01-23 11:23:07 +00:00
ipamV6Conf . AuxAddresses [ "DefaultGatewayIPv6" ] = config . BridgeConfig . DefaultGatewayIPv6 . String ( )
2015-05-15 23:34:26 +00:00
}
2016-01-12 07:47:44 +00:00
v4Conf := [ ] * libnetwork . IpamConf { ipamV4Conf }
2015-10-10 16:43:03 +00:00
v6Conf := [ ] * libnetwork . IpamConf { }
if ipamV6Conf != nil {
v6Conf = append ( v6Conf , ipamV6Conf )
2015-05-15 23:34:26 +00:00
}
// Initialize default network on "bridge" with the same name
2016-05-08 07:33:16 +00:00
_ , err = controller . NewNetwork ( "bridge" , "bridge" , "" ,
2017-01-23 11:23:07 +00:00
libnetwork . NetworkOptionEnableIPv6 ( config . BridgeConfig . EnableIPv6 ) ,
2015-12-10 14:02:50 +00:00
libnetwork . NetworkOptionDriverOpts ( netOption ) ,
2016-01-08 21:38:52 +00:00
libnetwork . NetworkOptionIpam ( "default" , "" , v4Conf , v6Conf , nil ) ,
2015-11-11 05:14:05 +00:00
libnetwork . NetworkOptionDeferIPv6Alloc ( deferIPv6Alloc ) )
2015-05-15 23:34:26 +00:00
if err != nil {
2015-06-30 17:34:15 +00:00
return fmt . Errorf ( "Error creating default \"bridge\" network: %v" , err )
2015-05-15 23:34:26 +00:00
}
2015-06-30 17:34:15 +00:00
return nil
2015-05-15 23:34:26 +00:00
}
2015-06-16 18:06:53 +00:00
2016-09-27 20:16:00 +00:00
// Remove default bridge interface if present (--bridge=none use case)
func removeDefaultBridgeInterface ( ) {
if lnk , err := netlink . LinkByName ( bridge . DefaultBridgeName ) ; err == nil {
if err := netlink . LinkDel ( lnk ) ; err != nil {
logrus . Warnf ( "Failed to remove bridge interface (%s): %v" , bridge . DefaultBridgeName , err )
}
}
}
2017-11-16 06:20:33 +00:00
func setupInitLayer ( idMapping * idtools . IdentityMapping ) func ( containerfs . ContainerFS ) error {
2018-02-13 19:29:14 +00:00
return func ( initPath containerfs . ContainerFS ) error {
2017-11-16 06:20:33 +00:00
return initlayer . Setup ( initPath , idMapping . RootPair ( ) )
2018-02-13 19:29:14 +00:00
}
2016-09-21 18:45:25 +00:00
}
2016-01-08 03:43:11 +00:00
// Parse the remapped root (user namespace) option, which can be one of:
// username - valid username from /etc/passwd
// username:groupname - valid username; valid groupname from /etc/group
// uid - 32-bit unsigned int valid Linux UID value
// uid:gid - uid value; 32-bit unsigned int Linux GID value
//
// If no groupname is specified, and a username is specified, an attempt
// will be made to lookup a gid for that username as a groupname
//
// If names are used, they are verified to exist in passwd/group
func parseRemappedRoot ( usergrp string ) ( string , string , error ) {
var (
userID , groupID int
username , groupname string
)
idparts := strings . Split ( usergrp , ":" )
if len ( idparts ) > 2 {
return "" , "" , fmt . Errorf ( "Invalid user/group specification in --userns-remap: %q" , usergrp )
}
if uid , err := strconv . ParseInt ( idparts [ 0 ] , 10 , 32 ) ; err == nil {
// must be a uid; take it as valid
userID = int ( uid )
2016-10-20 19:43:42 +00:00
luser , err := idtools . LookupUID ( userID )
2016-01-08 03:43:11 +00:00
if err != nil {
return "" , "" , fmt . Errorf ( "Uid %d has no entry in /etc/passwd: %v" , userID , err )
}
username = luser . Name
if len ( idparts ) == 1 {
// if the uid was numeric and no gid was specified, take the uid as the gid
groupID = userID
2016-10-20 19:43:42 +00:00
lgrp , err := idtools . LookupGID ( groupID )
2016-01-08 03:43:11 +00:00
if err != nil {
return "" , "" , fmt . Errorf ( "Gid %d has no entry in /etc/group: %v" , groupID , err )
}
groupname = lgrp . Name
}
} else {
lookupName := idparts [ 0 ]
// special case: if the user specified "default", they want Docker to create or
// use (after creation) the "dockremap" user/group for root remapping
if lookupName == defaultIDSpecifier {
lookupName = defaultRemappedID
}
2016-10-20 19:43:42 +00:00
luser , err := idtools . LookupUser ( lookupName )
2016-01-08 03:43:11 +00:00
if err != nil && idparts [ 0 ] != defaultIDSpecifier {
// error if the name requested isn't the special "dockremap" ID
return "" , "" , fmt . Errorf ( "Error during uid lookup for %q: %v" , lookupName , err )
} else if err != nil {
// special case-- if the username == "default", then we have been asked
// to create a new entry pair in /etc/{passwd,group} for which the /etc/sub{uid,gid}
// ranges will be used for the user and group mappings in user namespaced containers
_ , _ , err := idtools . AddNamespaceRangesUser ( defaultRemappedID )
if err == nil {
return defaultRemappedID , defaultRemappedID , nil
}
return "" , "" , fmt . Errorf ( "Error during %q user creation: %v" , defaultRemappedID , err )
}
username = luser . Name
if len ( idparts ) == 1 {
// we only have a string username, and no group specified; look up gid from username as group
2016-10-20 19:43:42 +00:00
group , err := idtools . LookupGroup ( lookupName )
2016-01-08 03:43:11 +00:00
if err != nil {
return "" , "" , fmt . Errorf ( "Error during gid lookup for %q: %v" , lookupName , err )
}
groupname = group . Name
}
}
if len ( idparts ) == 2 {
// groupname or gid is separately specified and must be resolved
2016-03-24 15:57:11 +00:00
// to an unsigned 32-bit gid
2016-01-08 03:43:11 +00:00
if gid , err := strconv . ParseInt ( idparts [ 1 ] , 10 , 32 ) ; err == nil {
// must be a gid, take it as valid
groupID = int ( gid )
2016-10-20 19:43:42 +00:00
lgrp , err := idtools . LookupGID ( groupID )
2016-01-08 03:43:11 +00:00
if err != nil {
return "" , "" , fmt . Errorf ( "Gid %d has no entry in /etc/passwd: %v" , groupID , err )
}
groupname = lgrp . Name
} else {
// not a number; attempt a lookup
2016-10-20 19:43:42 +00:00
if _ , err := idtools . LookupGroup ( idparts [ 1 ] ) ; err != nil {
2016-03-17 02:43:26 +00:00
return "" , "" , fmt . Errorf ( "Error during groupname lookup for %q: %v" , idparts [ 1 ] , err )
2016-01-08 03:43:11 +00:00
}
groupname = idparts [ 1 ]
}
}
return username , groupname , nil
}
2017-11-16 06:20:33 +00:00
func setupRemappedRoot ( config * config . Config ) ( * idtools . IdentityMapping , error ) {
2016-01-08 03:43:11 +00:00
if runtime . GOOS != "linux" && config . RemappedRoot != "" {
2017-05-19 22:06:46 +00:00
return nil , fmt . Errorf ( "User namespaces are only supported on Linux" )
2016-01-08 03:43:11 +00:00
}
// if the daemon was started with remapped root option, parse
// the config option to the int uid,gid values
if config . RemappedRoot != "" {
username , groupname , err := parseRemappedRoot ( config . RemappedRoot )
if err != nil {
2017-05-19 22:06:46 +00:00
return nil , err
2016-01-08 03:43:11 +00:00
}
if username == "root" {
// Cannot setup user namespaces with a 1-to-1 mapping; "--root=0:0" is a no-op
// effectively
2016-06-11 17:42:38 +00:00
logrus . Warn ( "User namespaces: root cannot be remapped with itself; user namespaces are OFF" )
2017-11-16 06:20:33 +00:00
return & idtools . IdentityMapping { } , nil
2016-01-08 03:43:11 +00:00
}
logrus . Infof ( "User namespaces: ID ranges will be mapped to subuid/subgid ranges of: %s:%s" , username , groupname )
// update remapped root setting now that we have resolved them to actual names
config . RemappedRoot = fmt . Sprintf ( "%s:%s" , username , groupname )
2017-11-16 06:20:33 +00:00
mappings , err := idtools . NewIdentityMapping ( username , groupname )
2016-01-08 03:43:11 +00:00
if err != nil {
2018-07-11 13:51:51 +00:00
return nil , errors . Wrap ( err , "Can't create ID mappings" )
2016-01-08 03:43:11 +00:00
}
2017-05-19 22:06:46 +00:00
return mappings , nil
2016-01-08 03:43:11 +00:00
}
2017-11-16 06:20:33 +00:00
return & idtools . IdentityMapping { } , nil
2016-01-08 03:43:11 +00:00
}
2017-11-16 06:20:33 +00:00
func setupDaemonRoot ( config * config . Config , rootDir string , rootIdentity idtools . Identity ) error {
2016-01-08 03:43:11 +00:00
config . Root = rootDir
2016-03-16 08:24:03 +00:00
// the docker root metadata directory needs to have execute permissions for all users (g+x,o+x)
2016-01-08 03:43:11 +00:00
// so that syscalls executing as non-root, operating on subdirectories of the graph root
// (e.g. mounted layers of a container) can traverse this path.
// The user namespace support will create subdirectories for the remapped root host uid:gid
// pair owned by that same uid:gid pair for proper write access to those needed metadata and
// layer content subtrees.
if _ , err := os . Stat ( rootDir ) ; err == nil {
// root current exists; verify the access bits are correct by setting them
2016-03-16 08:24:03 +00:00
if err = os . Chmod ( rootDir , 0711 ) ; err != nil {
2016-01-08 03:43:11 +00:00
return err
}
} else if os . IsNotExist ( err ) {
2016-03-16 08:24:03 +00:00
// no root exists yet, create it 0711 with root:root ownership
if err := os . MkdirAll ( rootDir , 0711 ) ; err != nil {
2016-01-08 03:43:11 +00:00
return err
}
}
// if user namespaces are enabled we will create a subtree underneath the specified root
// with any/all specified remapped root uid/gid options on the daemon creating
// a new subdirectory with ownership set to the remapped uid/gid (so as to allow
// `chdir()` to work for containers namespaced to that uid/gid)
if config . RemappedRoot != "" {
2017-11-16 06:20:33 +00:00
config . Root = filepath . Join ( rootDir , fmt . Sprintf ( "%d.%d" , rootIdentity . UID , rootIdentity . GID ) )
2016-01-08 03:43:11 +00:00
logrus . Debugf ( "Creating user namespaced daemon root: %s" , config . Root )
2016-03-24 15:57:11 +00:00
// Create the root directory if it doesn't exist
2017-11-16 06:20:33 +00:00
if err := idtools . MkdirAllAndChown ( config . Root , 0700 , rootIdentity ) ; err != nil {
2016-01-08 03:43:11 +00:00
return fmt . Errorf ( "Cannot create daemon root: %s: %v" , config . Root , err )
}
2016-08-23 16:49:13 +00:00
// we also need to verify that any pre-existing directories in the path to
// the graphroot won't block access to remapped root--if any pre-existing directory
// has strict permissions that don't allow "x", container start will fail, so
// better to warn and fail now
dirPath := config . Root
for {
dirPath = filepath . Dir ( dirPath )
if dirPath == "/" {
break
}
2017-11-16 06:20:33 +00:00
if ! idtools . CanAccess ( dirPath , rootIdentity ) {
2017-08-17 19:16:30 +00:00
return fmt . Errorf ( "a subdirectory in your graphroot path (%s) restricts access to the remapped root uid/gid; please fix by allowing 'o+x' permissions on existing directories" , config . Root )
2016-08-23 16:49:13 +00:00
}
}
2016-01-08 03:43:11 +00:00
}
2018-01-23 19:08:55 +00:00
2018-04-17 15:30:39 +00:00
if err := setupDaemonRootPropagation ( config ) ; err != nil {
logrus . WithError ( err ) . WithField ( "dir" , config . Root ) . Warn ( "Error while setting daemon root propagation, this is not generally critical but may cause some functionality to not work or fallback to less desirable behavior" )
}
return nil
}
func setupDaemonRootPropagation ( cfg * config . Config ) error {
rootParentMount , options , err := getSourceMount ( cfg . Root )
if err != nil {
return errors . Wrap ( err , "error getting daemon root's parent mount" )
}
var cleanupOldFile bool
cleanupFile := getUnmountOnShutdownPath ( cfg )
defer func ( ) {
if ! cleanupOldFile {
return
2018-01-23 19:08:55 +00:00
}
2018-04-17 15:30:39 +00:00
if err := os . Remove ( cleanupFile ) ; err != nil && ! os . IsNotExist ( err ) {
logrus . WithError ( err ) . WithField ( "file" , cleanupFile ) . Warn ( "could not clean up old root propagation unmount file" )
}
} ( )
if hasMountinfoOption ( options , sharedPropagationOption , slavePropagationOption ) {
cleanupOldFile = true
return nil
}
if err := mount . MakeShared ( cfg . Root ) ; err != nil {
return errors . Wrap ( err , "could not setup daemon root propagation to shared" )
}
// check the case where this may have already been a mount to itself.
// If so then the daemon only performed a remount and should not try to unmount this later.
if rootParentMount == cfg . Root {
cleanupOldFile = true
return nil
}
2019-07-11 20:30:36 +00:00
if err := os . MkdirAll ( filepath . Dir ( cleanupFile ) , 0700 ) ; err != nil {
return errors . Wrap ( err , "error creating dir to store mount cleanup file" )
}
2018-04-17 15:30:39 +00:00
if err := ioutil . WriteFile ( cleanupFile , nil , 0600 ) ; err != nil {
return errors . Wrap ( err , "error writing file to signal mount cleanup on shutdown" )
2018-01-23 19:08:55 +00:00
}
2016-01-08 03:43:11 +00:00
return nil
}
2018-04-17 15:30:39 +00:00
// getUnmountOnShutdownPath generates the path to used when writing the file that signals to the daemon that on shutdown
// the daemon root should be unmounted.
func getUnmountOnShutdownPath ( config * config . Config ) string {
return filepath . Join ( config . ExecRoot , "unmount-on-shutdown" )
}
2015-07-30 21:01:53 +00:00
// registerLinks writes the links to a file.
2015-12-18 18:36:17 +00:00
func ( daemon * Daemon ) registerLinks ( container * container . Container , hostConfig * containertypes . HostConfig ) error {
2016-01-05 19:20:47 +00:00
if hostConfig == nil || hostConfig . NetworkMode . IsUserDefined ( ) {
2015-06-23 17:13:42 +00:00
return nil
}
for _ , l := range hostConfig . Links {
2016-12-23 19:09:12 +00:00
name , alias , err := opts . ParseLink ( l )
2015-06-23 17:13:42 +00:00
if err != nil {
return err
}
2015-12-11 17:39:28 +00:00
child , err := daemon . GetContainer ( name )
2015-06-23 17:13:42 +00:00
if err != nil {
2019-09-02 21:39:24 +00:00
if errdefs . IsNotFound ( err ) {
// Trying to link to a non-existing container is not valid, and
// should return an "invalid parameter" error. Returning a "not
// found" error here would make the client report the container's
// image could not be found (see moby/moby#39823)
err = errdefs . InvalidParameter ( err )
}
2017-07-19 14:20:13 +00:00
return errors . Wrapf ( err , "could not get container for %s" , name )
2015-06-23 17:13:42 +00:00
}
2015-11-12 19:55:17 +00:00
for child . HostConfig . NetworkMode . IsContainer ( ) {
parts := strings . SplitN ( string ( child . HostConfig . NetworkMode ) , ":" , 2 )
2015-12-11 17:39:28 +00:00
child , err = daemon . GetContainer ( parts [ 1 ] )
2015-06-23 17:13:42 +00:00
if err != nil {
2019-09-02 21:39:24 +00:00
if errdefs . IsNotFound ( err ) {
// Trying to link to a non-existing container is not valid, and
// should return an "invalid parameter" error. Returning a "not
// found" error here would make the client report the container's
// image could not be found (see moby/moby#39823)
err = errdefs . InvalidParameter ( err )
}
2017-07-19 14:20:13 +00:00
return errors . Wrapf ( err , "Could not get container for %s" , parts [ 1 ] )
2015-06-23 17:13:42 +00:00
}
}
2015-11-12 19:55:17 +00:00
if child . HostConfig . NetworkMode . IsHost ( ) {
2015-06-23 17:13:42 +00:00
return runconfig . ErrConflictHostNetworkAndLinks
}
2015-07-30 21:01:53 +00:00
if err := daemon . registerLink ( container , child , alias ) ; err != nil {
2015-06-23 17:13:42 +00:00
return err
}
}
// After we load all the links into the daemon
// set them to nil on the hostconfig
2017-04-06 17:43:10 +00:00
_ , err := container . WriteHostConfig ( )
return err
2015-06-23 17:13:42 +00:00
}
2015-07-16 21:14:58 +00:00
2015-11-03 01:06:09 +00:00
// conditionalMountOnStart is a platform specific helper function during the
// container start to call mount.
2015-11-12 19:55:17 +00:00
func ( daemon * Daemon ) conditionalMountOnStart ( container * container . Container ) error {
2015-11-03 01:06:09 +00:00
return daemon . Mount ( container )
}
// conditionalUnmountOnCleanup is a platform specific helper function called
// during the cleanup of a container to unmount.
2016-03-18 18:50:19 +00:00
func ( daemon * Daemon ) conditionalUnmountOnCleanup ( container * container . Container ) error {
return daemon . Unmount ( container )
2015-11-03 01:06:09 +00:00
}
2017-09-22 13:52:41 +00:00
func copyBlkioEntry ( entries [ ] * containerd_cgroups . BlkIOEntry ) [ ] 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
}
2016-03-18 18:50:19 +00:00
func ( daemon * Daemon ) stats ( c * container . Container ) ( * types . StatsJSON , error ) {
if ! c . IsRunning ( ) {
2017-07-19 14:20:13 +00:00
return nil , errNotRunning ( c . ID )
2016-03-18 18:50:19 +00:00
}
2017-09-22 13:52:41 +00:00
cs , err := daemon . containerd . Stats ( context . Background ( ) , c . ID )
2016-03-18 18:50:19 +00:00
if err != nil {
2017-07-07 07:33:45 +00:00
if strings . Contains ( err . Error ( ) , "container not found" ) {
2017-07-19 14:20:13 +00:00
return nil , containerNotFound ( c . ID )
2017-07-07 07:33:45 +00:00
}
2016-03-18 18:50:19 +00:00
return nil , err
}
s := & types . StatsJSON { }
2017-09-22 13:52:41 +00:00
s . Read = cs . Read
stats := cs . Metrics
if stats . Blkio != nil {
2016-03-18 18:50:19 +00:00
s . BlkioStats = types . BlkioStats {
2017-09-22 13:52:41 +00:00
IoServiceBytesRecursive : copyBlkioEntry ( stats . Blkio . IoServiceBytesRecursive ) ,
IoServicedRecursive : copyBlkioEntry ( stats . Blkio . IoServicedRecursive ) ,
IoQueuedRecursive : copyBlkioEntry ( stats . Blkio . IoQueuedRecursive ) ,
IoServiceTimeRecursive : copyBlkioEntry ( stats . Blkio . IoServiceTimeRecursive ) ,
IoWaitTimeRecursive : copyBlkioEntry ( stats . Blkio . IoWaitTimeRecursive ) ,
IoMergedRecursive : copyBlkioEntry ( stats . Blkio . IoMergedRecursive ) ,
IoTimeRecursive : copyBlkioEntry ( stats . Blkio . IoTimeRecursive ) ,
SectorsRecursive : copyBlkioEntry ( stats . Blkio . SectorsRecursive ) ,
}
}
if stats . CPU != nil {
2016-03-18 18:50:19 +00:00
s . CPUStats = types . CPUStats {
CPUUsage : types . CPUUsage {
2017-09-22 13:52:41 +00:00
TotalUsage : stats . CPU . Usage . Total ,
PercpuUsage : stats . CPU . Usage . PerCPU ,
UsageInKernelmode : stats . CPU . Usage . Kernel ,
UsageInUsermode : stats . CPU . Usage . User ,
2016-03-18 18:50:19 +00:00
} ,
ThrottlingData : types . ThrottlingData {
2017-09-22 13:52:41 +00:00
Periods : stats . CPU . Throttling . Periods ,
ThrottledPeriods : stats . CPU . Throttling . ThrottledPeriods ,
ThrottledTime : stats . CPU . Throttling . ThrottledTime ,
2016-03-18 18:50:19 +00:00
} ,
}
2017-09-22 13:52:41 +00:00
}
if stats . Memory != nil {
raw := make ( map [ string ] uint64 )
raw [ "cache" ] = stats . Memory . Cache
raw [ "rss" ] = stats . Memory . RSS
raw [ "rss_huge" ] = stats . Memory . RSSHuge
raw [ "mapped_file" ] = stats . Memory . MappedFile
raw [ "dirty" ] = stats . Memory . Dirty
raw [ "writeback" ] = stats . Memory . Writeback
raw [ "pgpgin" ] = stats . Memory . PgPgIn
raw [ "pgpgout" ] = stats . Memory . PgPgOut
raw [ "pgfault" ] = stats . Memory . PgFault
raw [ "pgmajfault" ] = stats . Memory . PgMajFault
raw [ "inactive_anon" ] = stats . Memory . InactiveAnon
raw [ "active_anon" ] = stats . Memory . ActiveAnon
raw [ "inactive_file" ] = stats . Memory . InactiveFile
raw [ "active_file" ] = stats . Memory . ActiveFile
raw [ "unevictable" ] = stats . Memory . Unevictable
raw [ "hierarchical_memory_limit" ] = stats . Memory . HierarchicalMemoryLimit
raw [ "hierarchical_memsw_limit" ] = stats . Memory . HierarchicalSwapLimit
raw [ "total_cache" ] = stats . Memory . TotalCache
raw [ "total_rss" ] = stats . Memory . TotalRSS
raw [ "total_rss_huge" ] = stats . Memory . TotalRSSHuge
raw [ "total_mapped_file" ] = stats . Memory . TotalMappedFile
raw [ "total_dirty" ] = stats . Memory . TotalDirty
raw [ "total_writeback" ] = stats . Memory . TotalWriteback
raw [ "total_pgpgin" ] = stats . Memory . TotalPgPgIn
raw [ "total_pgpgout" ] = stats . Memory . TotalPgPgOut
raw [ "total_pgfault" ] = stats . Memory . TotalPgFault
raw [ "total_pgmajfault" ] = stats . Memory . TotalPgMajFault
raw [ "total_inactive_anon" ] = stats . Memory . TotalInactiveAnon
raw [ "total_active_anon" ] = stats . Memory . TotalActiveAnon
raw [ "total_inactive_file" ] = stats . Memory . TotalInactiveFile
raw [ "total_active_file" ] = stats . Memory . TotalActiveFile
raw [ "total_unevictable" ] = stats . Memory . TotalUnevictable
if stats . Memory . Usage != nil {
s . MemoryStats = types . MemoryStats {
Stats : raw ,
Usage : stats . Memory . Usage . Usage ,
MaxUsage : stats . Memory . Usage . Max ,
Limit : stats . Memory . Usage . Limit ,
Failcnt : stats . Memory . Usage . Failcnt ,
}
} else {
s . MemoryStats = types . MemoryStats {
Stats : raw ,
}
2016-04-08 02:09:07 +00:00
}
2017-09-22 13:52:41 +00:00
2016-04-08 02:09:07 +00:00
// if the container does not set memory limit, use the machineMemory
2017-09-22 13:52:41 +00:00
if s . MemoryStats . Limit > daemon . machineMemory && daemon . machineMemory > 0 {
2017-01-04 17:01:59 +00:00
s . MemoryStats . Limit = daemon . machineMemory
2016-03-18 18:50:19 +00:00
}
}
2017-09-22 13:52:41 +00:00
if stats . Pids != nil {
s . PidsStats = types . PidsStats {
Current : stats . Pids . Current ,
Limit : stats . Pids . Limit ,
}
2016-07-10 18:11:27 +00:00
}
2017-09-22 13:52:41 +00:00
2016-03-18 18:50:19 +00:00
return s , nil
}
2016-03-24 15:57:11 +00:00
// setDefaultIsolation determines the default isolation mode for the
2016-03-18 18:50:19 +00:00
// daemon to run in. This is only applicable on Windows
func ( daemon * Daemon ) setDefaultIsolation ( ) error {
return nil
}
2016-03-21 16:56:51 +00:00
2016-07-11 22:26:23 +00:00
// setupDaemonProcess sets various settings for the daemon's process
2017-01-23 11:23:07 +00:00
func setupDaemonProcess ( config * config . Config ) error {
2016-07-11 22:26:23 +00:00
// setup the daemons oom_score_adj
2017-09-18 13:26:34 +00:00
if err := setupOOMScoreAdj ( config . OOMScoreAdjust ) ; err != nil {
return err
}
2017-10-11 17:27:08 +00:00
if err := setMayDetachMounts ( ) ; err != nil {
logrus . WithError ( err ) . Warn ( "Could not set may_detach_mounts kernel parameter" )
}
return nil
2017-09-18 13:26:34 +00:00
}
// This is used to allow removal of mountpoints that may be mounted in other
// namespaces on RHEL based kernels starting from RHEL 7.4.
// Without this setting, removals on these RHEL based kernels may fail with
// "device or resource busy".
// This setting is not available in upstream kernels as it is not configurable,
// but has been in the upstream kernels since 3.15.
func setMayDetachMounts ( ) error {
f , err := os . OpenFile ( "/proc/sys/fs/may_detach_mounts" , os . O_WRONLY , 0 )
if err != nil {
if os . IsNotExist ( err ) {
return nil
}
return errors . Wrap ( err , "error opening may_detach_mounts kernel config file" )
}
defer f . Close ( )
_ , err = f . WriteString ( "1" )
if os . IsPermission ( err ) {
// Setting may_detach_mounts does not work in an
// unprivileged container. Ignore the error, but log
// it if we appear not to be in that situation.
if ! rsystem . RunningInUserNS ( ) {
logrus . Debugf ( "Permission denied writing %q to /proc/sys/fs/may_detach_mounts" , "1" )
}
return nil
}
return err
2016-07-11 22:26:23 +00:00
}
func setupOOMScoreAdj ( score int ) error {
f , err := os . OpenFile ( "/proc/self/oom_score_adj" , os . O_WRONLY , 0 )
if err != nil {
return err
}
2016-11-18 07:56:52 +00:00
defer f . Close ( )
2016-09-21 07:36:36 +00:00
stringScore := strconv . Itoa ( score )
_ , err = f . WriteString ( stringScore )
2016-09-19 02:27:10 +00:00
if os . IsPermission ( err ) {
// Setting oom_score_adj does not work in an
2016-09-21 07:36:36 +00:00
// unprivileged container. Ignore the error, but log
// it if we appear not to be in that situation.
if ! rsystem . RunningInUserNS ( ) {
logrus . Debugf ( "Permission denied writing %q to /proc/self/oom_score_adj" , stringScore )
}
2016-09-19 02:27:10 +00:00
return nil
}
2016-11-18 07:56:52 +00:00
2016-07-11 22:26:23 +00:00
return err
}
2016-06-07 19:05:43 +00:00
func ( daemon * Daemon ) initCgroupsPath ( path string ) error {
if path == "/" || path == "." {
return nil
}
2017-01-03 13:54:30 +00:00
if daemon . configStore . CPURealtimePeriod == 0 && daemon . configStore . CPURealtimeRuntime == 0 {
return nil
}
// Recursively create cgroup to ensure that the system and all parent cgroups have values set
// for the period and runtime as this limits what the children can be set to.
2016-06-07 19:05:43 +00:00
daemon . initCgroupsPath ( filepath . Dir ( path ) )
2018-12-06 18:49:51 +00:00
mnt , root , err := cgroups . FindCgroupMountpointAndRoot ( "" , "cpu" )
2016-06-07 19:05:43 +00:00
if err != nil {
return err
}
2017-03-14 20:09:09 +00:00
// When docker is run inside docker, the root is based of the host cgroup.
// Should this be handled in runc/libcontainer/cgroups ?
if strings . HasPrefix ( root , "/docker/" ) {
root = "/"
}
2016-06-07 19:05:43 +00:00
2017-03-14 20:09:09 +00:00
path = filepath . Join ( mnt , root , path )
2016-11-29 15:38:07 +00:00
sysinfo := sysinfo . New ( true )
2017-02-28 10:12:06 +00:00
if err := maybeCreateCPURealTimeFile ( sysinfo . CPURealtimePeriod , daemon . configStore . CPURealtimePeriod , "cpu.rt_period_us" , path ) ; err != nil {
return err
2016-06-07 19:05:43 +00:00
}
2018-01-14 23:42:25 +00:00
return maybeCreateCPURealTimeFile ( sysinfo . CPURealtimeRuntime , daemon . configStore . CPURealtimeRuntime , "cpu.rt_runtime_us" , path )
2017-02-28 10:12:06 +00:00
}
func maybeCreateCPURealTimeFile ( sysinfoPresent bool , configValue int64 , file string , path string ) error {
if sysinfoPresent && configValue != 0 {
Simplify/fix MkdirAll usage
This subtle bug keeps lurking in because error checking for `Mkdir()`
and `MkdirAll()` is slightly different wrt to `EEXIST`/`IsExist`:
- for `Mkdir()`, `IsExist` error should (usually) be ignored
(unless you want to make sure directory was not there before)
as it means "the destination directory was already there"
- for `MkdirAll()`, `IsExist` error should NEVER be ignored.
Mostly, this commit just removes ignoring the IsExist error, as it
should not be ignored.
Also, there are a couple of cases then IsExist is handled as
"directory already exist" which is wrong. As a result, some code
that never worked as intended is now removed.
NOTE that `idtools.MkdirAndChown()` behaves like `os.MkdirAll()`
rather than `os.Mkdir()` -- so its description is amended accordingly,
and its usage is handled as such (i.e. IsExist error is not ignored).
For more details, a quote from my runc commit 6f82d4b (July 2015):
TL;DR: check for IsExist(err) after a failed MkdirAll() is both
redundant and wrong -- so two reasons to remove it.
Quoting MkdirAll documentation:
> MkdirAll creates a directory named path, along with any necessary
> parents, and returns nil, or else returns an error. If path
> is already a directory, MkdirAll does nothing and returns nil.
This means two things:
1. If a directory to be created already exists, no error is
returned.
2. If the error returned is IsExist (EEXIST), it means there exists
a non-directory with the same name as MkdirAll need to use for
directory. Example: we want to MkdirAll("a/b"), but file "a"
(or "a/b") already exists, so MkdirAll fails.
The above is a theory, based on quoted documentation and my UNIX
knowledge.
3. In practice, though, current MkdirAll implementation [1] returns
ENOTDIR in most of cases described in #2, with the exception when
there is a race between MkdirAll and someone else creating the
last component of MkdirAll argument as a file. In this very case
MkdirAll() will indeed return EEXIST.
Because of #1, IsExist check after MkdirAll is not needed.
Because of #2 and #3, ignoring IsExist error is just plain wrong,
as directory we require is not created. It's cleaner to report
the error now.
Note this error is all over the tree, I guess due to copy-paste,
or trying to follow the same usage pattern as for Mkdir(),
or some not quite correct examples on the Internet.
[1] https://github.com/golang/go/blob/f9ed2f75/src/os/path.go
Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2017-09-25 19:39:36 +00:00
if err := os . MkdirAll ( path , 0755 ) ; err != nil {
2017-01-03 13:54:30 +00:00
return err
}
2017-02-28 10:12:06 +00:00
if err := ioutil . WriteFile ( filepath . Join ( path , file ) , [ ] byte ( strconv . FormatInt ( configValue , 10 ) ) , 0700 ) ; err != nil {
2016-06-07 19:05:43 +00:00
return err
}
}
2016-09-02 13:20:54 +00:00
return nil
}
2016-06-07 19:05:43 +00:00
2016-09-02 13:20:54 +00:00
func ( daemon * Daemon ) setupSeccompProfile ( ) error {
if daemon . configStore . SeccompProfile != "" {
daemon . seccompProfilePath = daemon . configStore . SeccompProfile
b , err := ioutil . ReadFile ( daemon . configStore . SeccompProfile )
if err != nil {
return fmt . Errorf ( "opening seccomp profile (%s) failed: %v" , daemon . configStore . SeccompProfile , err )
}
daemon . seccompProfile = b
}
2016-06-07 19:05:43 +00:00
return nil
}