diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/boltdb_state.go | 1 | ||||
-rw-r--r-- | libpod/container.go | 3 | ||||
-rw-r--r-- | libpod/container_inspect.go | 10 | ||||
-rw-r--r-- | libpod/container_internal.go | 108 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 43 | ||||
-rw-r--r-- | libpod/container_top_linux.go | 4 | ||||
-rw-r--r-- | libpod/define/errors.go | 4 | ||||
-rw-r--r-- | libpod/image/prune.go | 6 | ||||
-rw-r--r-- | libpod/oci.go | 30 | ||||
-rw-r--r-- | libpod/oci_internal_linux.go | 63 | ||||
-rw-r--r-- | libpod/oci_linux.go | 4 | ||||
-rw-r--r-- | libpod/options.go | 29 | ||||
-rw-r--r-- | libpod/runtime.go | 86 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 139 | ||||
-rw-r--r-- | libpod/stats.go | 4 | ||||
-rw-r--r-- | libpod/volume.go | 7 | ||||
-rw-r--r-- | libpod/volume_internal.go | 2 |
17 files changed, 369 insertions, 174 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 4e7f78f13..a6fd9a7d8 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -1749,6 +1749,7 @@ func (s *BoltState) LookupVolume(name string) (*Volume, error) { volume := new(Volume) volume.config = new(VolumeConfig) + volume.state = new(VolumeState) db, err := s.getDBCon() if err != nil { diff --git a/libpod/container.go b/libpod/container.go index 9c01d2adf..3d8e58375 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -356,6 +356,9 @@ type ContainerConfig struct { StopTimeout uint `json:"stopTimeout,omitempty"` // Time container was created CreatedTime time.Time `json:"createdTime"` + // NoCgroups indicates that the container will not create CGroups. It is + // incompatible with CgroupParent. + NoCgroups bool `json:"noCgroups,omitempty"` // Cgroup parent of the container CgroupParent string `json:"cgroupParent"` // LogPath log location diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index 1b6dd829c..3c32a2f45 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -268,6 +268,11 @@ type InspectContainerHostConfig struct { // populated. // TODO. Cgroup string `json:"Cgroup"` + // Cgroups contains the container's CGroup mode. + // Allowed values are "default" (container is creating CGroups) and + // "disabled" (container is not creating CGroups). + // This is Libpod-specific and not included in `docker inspect`. + Cgroups string `json:"Cgroups"` // Links is unused, and provided purely for Docker compatibility. Links []string `json:"Links"` // OOMScoreAdj is an adjustment that will be made to the container's OOM @@ -958,6 +963,11 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named restartPolicy.Name = c.config.RestartPolicy restartPolicy.MaximumRetryCount = c.config.RestartRetries hostConfig.RestartPolicy = restartPolicy + if c.config.NoCgroups { + hostConfig.Cgroups = "disabled" + } else { + hostConfig.Cgroups = "default" + } hostConfig.Dns = make([]string, 0, len(c.config.DNSServer)) for _, dns := range c.config.DNSServer { diff --git a/libpod/container_internal.go b/libpod/container_internal.go index ffc6c11ee..ac565fdad 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -21,6 +21,7 @@ import ( "github.com/containers/storage" "github.com/containers/storage/pkg/archive" "github.com/containers/storage/pkg/mount" + "github.com/cyphar/filepath-securejoin" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" @@ -1119,6 +1120,10 @@ func (c *Container) stop(timeout uint) error { // Internal, non-locking function to pause a container func (c *Container) pause() error { + if c.config.NoCgroups { + return errors.Wrapf(define.ErrNoCgroups, "cannot pause without using CGroups") + } + if err := c.ociRuntime.pauseContainer(c); err != nil { return err } @@ -1132,6 +1137,10 @@ func (c *Container) pause() error { // Internal, non-locking function to unpause a container func (c *Container) unpause() error { + if c.config.NoCgroups { + return errors.Wrapf(define.ErrNoCgroups, "cannot unpause without using CGroups") + } + if err := c.ociRuntime.unpauseContainer(c); err != nil { return err } @@ -1234,43 +1243,82 @@ func (c *Container) mountStorage() (_ string, Err error) { }() } + // We need to mount the container before volumes - to ensure the copyup + // works properly. + mountPoint := c.config.Rootfs + if mountPoint == "" { + mountPoint, err = c.mount() + if err != nil { + return "", err + } + defer func() { + if Err != nil { + if err := c.unmount(false); err != nil { + logrus.Errorf("Error unmounting container %s after mount error: %v", c.ID(), err) + } + } + }() + } + // Request a mount of all named volumes for _, v := range c.config.NamedVolumes { - vol, err := c.runtime.state.Volume(v.Name) + vol, err := c.mountNamedVolume(v, mountPoint) if err != nil { - return "", errors.Wrapf(err, "error retrieving named volume %s for container %s", v.Name, c.ID()) + return "", err } - - if vol.needsMount() { + defer func() { + if Err == nil { + return + } vol.lock.Lock() - if err := vol.mount(); err != nil { - vol.lock.Unlock() - return "", errors.Wrapf(err, "error mounting volume %s for container %s", vol.Name(), c.ID()) + if err := vol.unmount(false); err != nil { + logrus.Errorf("Error unmounting volume %s after error mounting container %s: %v", vol.Name(), c.ID(), err) } vol.lock.Unlock() - defer func() { - if Err == nil { - return - } - vol.lock.Lock() - if err := vol.unmount(false); err != nil { - logrus.Errorf("Error unmounting volume %s after error mounting container %s: %v", vol.Name(), c.ID(), err) - } - vol.lock.Unlock() - }() - } + }() } - // TODO: generalize this mount code so it will mount every mount in ctr.config.Mounts - mountPoint := c.config.Rootfs - if mountPoint == "" { - mountPoint, err = c.mount() - if err != nil { - return "", err + return mountPoint, nil +} + +// Mount a single named volume into the container. +// If necessary, copy up image contents into the volume. +// Does not verify that the name volume given is actually present in container +// config. +// Returns the volume that was mounted. +func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string) (*Volume, error) { + vol, err := c.runtime.state.Volume(v.Name) + if err != nil { + return nil, errors.Wrapf(err, "error retrieving named volume %s for container %s", v.Name, c.ID()) + } + + vol.lock.Lock() + defer vol.lock.Unlock() + if vol.needsMount() { + if err := vol.mount(); err != nil { + return nil, errors.Wrapf(err, "error mounting volume %s for container %s", vol.Name(), c.ID()) } } + // The volume may need a copy-up. Check the state. + if err := vol.update(); err != nil { + return nil, err + } + if vol.state.NeedsCopyUp { + logrus.Debugf("Copying up contents from container %s to volume %s", c.ID(), vol.Name()) + srcDir, err := securejoin.SecureJoin(mountpoint, v.Dest) + if err != nil { + return nil, errors.Wrapf(err, "error calculating destination path to copy up container %s volume %s", c.ID(), vol.Name()) + } + if err := c.copyWithTarFromImage(srcDir, vol.MountPoint()); err != nil && !os.IsNotExist(err) { + return nil, errors.Wrapf(err, "error copying content from container %s into volume %s", c.ID(), vol.Name()) + } - return mountPoint, nil + vol.state.NeedsCopyUp = false + if err := vol.save(); err != nil { + return nil, err + } + } + return vol, nil } // cleanupStorage unmounts and cleans up the container's root filesystem @@ -1614,15 +1662,11 @@ func (c *Container) unmount(force bool) error { } // this should be from chrootarchive. -func (c *Container) copyWithTarFromImage(src, dest string) error { - mountpoint, err := c.mount() - if err != nil { - return err - } +// Container MUST be mounted before calling. +func (c *Container) copyWithTarFromImage(source, dest string) error { a := archive.NewDefaultArchiver() - source := filepath.Join(mountpoint, src) - if err = c.copyOwnerAndPerms(source, dest); err != nil { + if err := c.copyOwnerAndPerms(source, dest); err != nil { return err } return a.CopyWithTar(source, dest) diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index e96af8536..4bbbef5db 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -21,7 +21,7 @@ import ( "github.com/containernetworking/plugins/pkg/ns" "github.com/containers/buildah/pkg/secrets" "github.com/containers/libpod/libpod/define" - crioAnnotations "github.com/containers/libpod/pkg/annotations" + "github.com/containers/libpod/pkg/annotations" "github.com/containers/libpod/pkg/apparmor" "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/criu" @@ -115,7 +115,9 @@ func (c *Container) prepare() (Err error) { createErr = createNetNSErr } if mountStorageErr != nil { - logrus.Errorf("Error preparing container %s: %v", c.ID(), createErr) + if createErr != nil { + logrus.Errorf("Error preparing container %s: %v", c.ID(), createErr) + } createErr = mountStorageErr } @@ -347,9 +349,13 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } g.SetRootPath(c.state.Mountpoint) - g.AddAnnotation(crioAnnotations.Created, c.config.CreatedTime.Format(time.RFC3339Nano)) + g.AddAnnotation(annotations.Created, c.config.CreatedTime.Format(time.RFC3339Nano)) g.AddAnnotation("org.opencontainers.image.stopSignal", fmt.Sprintf("%d", c.config.StopSignal)) + if _, exists := g.Config.Annotations[annotations.ContainerManager]; !exists { + g.AddAnnotation(annotations.ContainerManager, annotations.ContainerManagerLibpod) + } + for _, i := range c.config.Spec.Linux.Namespaces { if i.Type == spec.UTSNamespace { hostname := c.Hostname() @@ -375,7 +381,7 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { if err != nil { return nil, err } - if rootless.IsRootless() && !unified { + if (rootless.IsRootless() && !unified) || c.config.NoCgroups { g.SetLinuxCgroupsPath("") } else if c.runtime.config.CgroupManager == SystemdCgroupsManager { // When runc is set to use Systemd as a cgroup manager, it @@ -485,12 +491,29 @@ func (c *Container) setupSystemd(mounts []spec.Mount, g generate.Generator) erro if unified { g.RemoveMount("/sys/fs/cgroup") - sourcePath := filepath.Join("/sys/fs/cgroup") - systemdMnt := spec.Mount{ - Destination: "/sys/fs/cgroup", - Type: "bind", - Source: sourcePath, - Options: []string{"bind", "private", "rw"}, + hasCgroupNs := false + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.CgroupNamespace { + hasCgroupNs = true + break + } + } + + var systemdMnt spec.Mount + if hasCgroupNs { + systemdMnt = spec.Mount{ + Destination: "/sys/fs/cgroup", + Type: "cgroup", + Source: "cgroup", + Options: []string{"private", "rw"}, + } + } else { + systemdMnt = spec.Mount{ + Destination: "/sys/fs/cgroup", + Type: "bind", + Source: "/sys/fs/cgroup", + Options: []string{"bind", "private", "rw"}, + } } g.AddMount(systemdMnt) } else { diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go index ce471838d..5f4f28130 100644 --- a/libpod/container_top_linux.go +++ b/libpod/container_top_linux.go @@ -15,6 +15,10 @@ import ( // Top gathers statistics about the running processes in a container. It returns a // []string for output func (c *Container) Top(descriptors []string) ([]string, error) { + if c.config.NoCgroups { + return nil, errors.Wrapf(define.ErrNoCgroups, "cannot run top on container %s as it did not create a cgroup", c.ID()) + } + conStat, err := c.State() if err != nil { return nil, errors.Wrapf(err, "unable to look up state for %s", c.ID()) diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 9d532263c..004acd58f 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -61,6 +61,10 @@ var ( // the user. ErrDetach = utils.ErrDetach + // ErrNoCgroups indicates that the container does not have its own + // CGroup. + ErrNoCgroups = errors.New("this container does not have a cgroup") + // ErrRuntimeStopped indicates that the runtime has already been shut // down and no further operations can be performed on it ErrRuntimeStopped = errors.New("runtime has already been stopped") diff --git a/libpod/image/prune.go b/libpod/image/prune.go index 6ef5d321f..006cbdf22 100644 --- a/libpod/image/prune.go +++ b/libpod/image/prune.go @@ -4,7 +4,9 @@ import ( "context" "github.com/containers/libpod/libpod/events" + "github.com/containers/storage" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) // GetPruneImages returns a slice of images that have no names/unused @@ -44,6 +46,10 @@ func (ir *Runtime) PruneImages(ctx context.Context, all bool) ([]string, error) } for _, p := range pruneImages { if err := p.Remove(ctx, true); err != nil { + if errors.Cause(err) == storage.ErrImageUsedByContainer { + logrus.Warnf("Failed to prune image %s as it is in use: %v", p.ID(), err) + continue + } return nil, errors.Wrap(err, "failed to prune image") } defer p.newImageEvent(events.Prune) diff --git a/libpod/oci.go b/libpod/oci.go index 8a873ca5b..9879fa90e 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -48,19 +48,20 @@ const ( // OCIRuntime represents an OCI-compatible runtime that libpod can call into // to perform container operations type OCIRuntime struct { - name string - path string - conmonPath string - conmonEnv []string - cgroupManager string - tmpDir string - exitsDir string - socketsDir string - logSizeMax int64 - noPivot bool - reservePorts bool - supportsJSON bool - sdNotify bool + name string + path string + conmonPath string + conmonEnv []string + cgroupManager string + tmpDir string + exitsDir string + socketsDir string + logSizeMax int64 + noPivot bool + reservePorts bool + supportsJSON bool + supportsNoCgroups bool + sdNotify bool } // ociError is used to parse the OCI runtime JSON log. It is not part of the @@ -73,7 +74,7 @@ type ociError struct { // Make a new OCI runtime with provided options. // The first path that points to a valid executable will be used. -func newOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *RuntimeConfig, supportsJSON bool) (*OCIRuntime, error) { +func newOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *RuntimeConfig, supportsJSON, supportsNoCgroups bool) (*OCIRuntime, error) { if name == "" { return nil, errors.Wrapf(define.ErrInvalidArg, "the OCI runtime must be provided a non-empty name") } @@ -93,6 +94,7 @@ func newOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *R // TODO: probe OCI runtime for feature and enable automatically if // available. runtime.supportsJSON = supportsJSON + runtime.supportsNoCgroups = supportsNoCgroups foundPath := false for _, path := range paths { diff --git a/libpod/oci_internal_linux.go b/libpod/oci_internal_linux.go index 48b7370e0..4df1e4010 100644 --- a/libpod/oci_internal_linux.go +++ b/libpod/oci_internal_linux.go @@ -21,6 +21,7 @@ import ( "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/errorhandling" "github.com/containers/libpod/pkg/lookup" + "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" "github.com/coreos/go-systemd/activation" @@ -263,7 +264,7 @@ func (r *OCIRuntime) configureConmonEnv(runtimeDir string) ([]string, []*os.File func (r *OCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath, logPath, exitDir, ociLogPath string) []string { // set the conmon API version to be able to use the correct sync struct keys args := []string{"--api-version", "1"} - if r.cgroupManager == SystemdCgroupsManager { + if r.cgroupManager == SystemdCgroupsManager && !ctr.config.NoCgroups { args = append(args, "-s") } args = append(args, "-c", ctr.ID()) @@ -307,6 +308,10 @@ func (r *OCIRuntime) sharedConmonArgs(ctr *Container, cuuid, bundlePath, pidPath if ociLogPath != "" { args = append(args, "--runtime-arg", "--log-format=json", "--runtime-arg", "--log", fmt.Sprintf("--runtime-arg=%s", ociLogPath)) } + if ctr.config.NoCgroups { + logrus.Debugf("Running with no CGroups") + args = append(args, "--runtime-arg", "--cgroup-manager", "--runtime-arg", "disabled") + } return args } @@ -355,30 +360,46 @@ func startCommandGivenSelinux(cmd *exec.Cmd) error { // moveConmonToCgroupAndSignal gets a container's cgroupParent and moves the conmon process to that cgroup // it then signals for conmon to start by sending nonse data down the start fd func (r *OCIRuntime) moveConmonToCgroupAndSignal(ctr *Container, cmd *exec.Cmd, startFd *os.File, uuid string) error { - cgroupParent := ctr.CgroupParent() - if r.cgroupManager == SystemdCgroupsManager { - unitName := createUnitName("libpod-conmon", ctr.ID()) - - realCgroupParent := cgroupParent - splitParent := strings.Split(cgroupParent, "/") - if strings.HasSuffix(cgroupParent, ".slice") && len(splitParent) > 1 { - realCgroupParent = splitParent[len(splitParent)-1] - } + mustCreateCgroup := true + // If cgroup creation is disabled - just signal. + if ctr.config.NoCgroups { + mustCreateCgroup = false + } - logrus.Infof("Running conmon under slice %s and unitName %s", realCgroupParent, unitName) - if err := utils.RunUnderSystemdScope(cmd.Process.Pid, realCgroupParent, unitName); err != nil { - logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err) - } - } else { - cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon") - control, err := cgroups.New(cgroupPath, &spec.LinuxResources{}) + if rootless.IsRootless() { + ownsCgroup, err := cgroups.UserOwnsCurrentSystemdCgroup() if err != nil { - logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + return err + } + mustCreateCgroup = !ownsCgroup + } + + if mustCreateCgroup { + cgroupParent := ctr.CgroupParent() + if r.cgroupManager == SystemdCgroupsManager { + unitName := createUnitName("libpod-conmon", ctr.ID()) + + realCgroupParent := cgroupParent + splitParent := strings.Split(cgroupParent, "/") + if strings.HasSuffix(cgroupParent, ".slice") && len(splitParent) > 1 { + realCgroupParent = splitParent[len(splitParent)-1] + } + + logrus.Infof("Running conmon under slice %s and unitName %s", realCgroupParent, unitName) + if err := utils.RunUnderSystemdScope(cmd.Process.Pid, realCgroupParent, unitName); err != nil { + logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err) + } } else { - // we need to remove this defer and delete the cgroup once conmon exits - // maybe need a conmon monitor? - if err := control.AddPid(cmd.Process.Pid); err != nil { + cgroupPath := filepath.Join(ctr.config.CgroupParent, "conmon") + control, err := cgroups.New(cgroupPath, &spec.LinuxResources{}) + if err != nil { logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + } else { + // we need to remove this defer and delete the cgroup once conmon exits + // maybe need a conmon monitor? + if err := control.AddPid(cmd.Process.Pid); err != nil { + logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + } } } } diff --git a/libpod/oci_linux.go b/libpod/oci_linux.go index 6dba1260c..091b6d155 100644 --- a/libpod/oci_linux.go +++ b/libpod/oci_linux.go @@ -402,10 +402,12 @@ func (r *OCIRuntime) stopContainer(ctr *Container, timeout uint) error { } var args []string - if rootless.IsRootless() { + if rootless.IsRootless() || ctr.config.NoCgroups { // we don't use --all for rootless containers as the OCI runtime might use // the cgroups to determine the PIDs, but for rootless containers there is // not any. + // Same logic for NoCgroups - we can't use cgroups as the user + // explicitly requested none be created. args = []string{"kill", ctr.ID(), "KILL"} } else { args = []string{"kill", "--all", ctr.ID(), "KILL"} diff --git a/libpod/options.go b/libpod/options.go index 6df1ca5be..d28cb3d8c 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -847,6 +847,10 @@ func WithPIDNSFrom(nsCtr *Container) CtrCreateOption { return errors.Wrapf(define.ErrInvalidArg, "container has joined pod %s and dependency container %s is not a member of the pod", ctr.config.Pod, nsCtr.ID()) } + if ctr.config.NoCgroups { + return errors.Wrapf(define.ErrInvalidArg, "container has disabled creation of CGroups, which is incompatible with sharing a PID namespace") + } + ctr.config.PIDNsCtr = nsCtr.ID() return nil @@ -1056,6 +1060,27 @@ func WithLogPath(path string) CtrCreateOption { } } +// WithNoCgroups disables the creation of CGroups for the new container. +func WithNoCgroups() CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + if ctr.config.CgroupParent != "" { + return errors.Wrapf(define.ErrInvalidArg, "NoCgroups conflicts with CgroupParent") + } + + if ctr.config.PIDNsCtr != "" { + return errors.Wrapf(define.ErrInvalidArg, "NoCgroups requires a private PID namespace and cannot be used when PID namespace is shared with another container") + } + + ctr.config.NoCgroups = true + + return nil + } +} + // WithCgroupParent sets the Cgroup Parent of the new container. func WithCgroupParent(parent string) CtrCreateOption { return func(ctr *Container) error { @@ -1067,6 +1092,10 @@ func WithCgroupParent(parent string) CtrCreateOption { return errors.Wrapf(define.ErrInvalidArg, "cgroup parent cannot be empty") } + if ctr.config.NoCgroups { + return errors.Wrapf(define.ErrInvalidArg, "CgroupParent conflicts with NoCgroups") + } + ctr.config.CgroupParent = parent return nil diff --git a/libpod/runtime.go b/libpod/runtime.go index 323a46266..80b58654e 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -157,8 +157,12 @@ type RuntimeConfig struct { OCIRuntime string `toml:"runtime"` // OCIRuntimes are the set of configured OCI runtimes (default is runc) OCIRuntimes map[string][]string `toml:"runtimes"` - // RuntimeSupportsJSON is the list of the OCI runtimes that support --format=json + // RuntimeSupportsJSON is the list of the OCI runtimes that support + // --format=json. RuntimeSupportsJSON []string `toml:"runtime_supports_json"` + // RuntimeSupportsNoCgroups is a list of OCI runtimes that support + // running containers without CGroups. + RuntimeSupportsNoCgroups []string `toml:"runtime_supports_nocgroups"` // RuntimePath is the path to OCI runtime binary for launching // containers. // The first path pointing to a valid file will be used @@ -259,21 +263,22 @@ type RuntimeConfig struct { // If they were not, we may override them with information from the database, // if it exists and differs from what is present in the system already. type runtimeConfiguredFrom struct { - storageGraphDriverSet bool - storageGraphRootSet bool - storageRunRootSet bool - libpodStaticDirSet bool - libpodTmpDirSet bool - volPathSet bool - conmonPath bool - conmonEnvVars bool - initPath bool - ociRuntimes bool - runtimePath bool - cniPluginDir bool - noPivotRoot bool - runtimeSupportsJSON bool - ociRuntime bool + storageGraphDriverSet bool + storageGraphRootSet bool + storageRunRootSet bool + libpodStaticDirSet bool + libpodTmpDirSet bool + volPathSet bool + conmonPath bool + conmonEnvVars bool + initPath bool + ociRuntimes bool + runtimePath bool + cniPluginDir bool + noPivotRoot bool + runtimeSupportsJSON bool + runtimeSupportsNoCgroups bool + ociRuntime bool } func defaultRuntimeConfig() (RuntimeConfig, error) { @@ -603,6 +608,9 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. if tmpConfig.RuntimeSupportsJSON != nil { runtime.configuredFrom.runtimeSupportsJSON = true } + if tmpConfig.RuntimeSupportsNoCgroups != nil { + runtime.configuredFrom.runtimeSupportsNoCgroups = true + } if tmpConfig.OCIRuntime != "" { runtime.configuredFrom.ociRuntime = true } @@ -649,6 +657,9 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. if !runtime.configuredFrom.runtimeSupportsJSON { runtime.config.RuntimeSupportsJSON = tmpConfig.RuntimeSupportsJSON } + if !runtime.configuredFrom.runtimeSupportsNoCgroups { + runtime.config.RuntimeSupportsNoCgroups = tmpConfig.RuntimeSupportsNoCgroups + } if !runtime.configuredFrom.ociRuntime { runtime.config.OCIRuntime = tmpConfig.OCIRuntime } @@ -1009,6 +1020,16 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { } } + // Make lookup tables for runtime support + supportsJSON := make(map[string]bool) + supportsNoCgroups := make(map[string]bool) + for _, r := range runtime.config.RuntimeSupportsJSON { + supportsJSON[r] = true + } + for _, r := range runtime.config.RuntimeSupportsNoCgroups { + supportsNoCgroups[r] = true + } + // Get us at least one working OCI runtime. runtime.ociRuntimes = make(map[string]*OCIRuntime) @@ -1026,15 +1047,10 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { name := filepath.Base(runtime.config.RuntimePath[0]) - supportsJSON := false - for _, r := range runtime.config.RuntimeSupportsJSON { - if r == name { - supportsJSON = true - break - } - } + json := supportsJSON[name] + nocgroups := supportsNoCgroups[name] - ociRuntime, err := newOCIRuntime(name, runtime.config.RuntimePath, runtime.conmonPath, runtime.config, supportsJSON) + ociRuntime, err := newOCIRuntime(name, runtime.config.RuntimePath, runtime.conmonPath, runtime.config, json, nocgroups) if err != nil { return err } @@ -1045,15 +1061,10 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { // Initialize remaining OCI runtimes for name, paths := range runtime.config.OCIRuntimes { - supportsJSON := false - for _, r := range runtime.config.RuntimeSupportsJSON { - if r == name { - supportsJSON = true - break - } - } + json := supportsJSON[name] + nocgroups := supportsNoCgroups[name] - ociRuntime, err := newOCIRuntime(name, paths, runtime.conmonPath, runtime.config, supportsJSON) + ociRuntime, err := newOCIRuntime(name, paths, runtime.conmonPath, runtime.config, json, nocgroups) if err != nil { // Don't fatally error. // This will allow us to ship configs including optional @@ -1073,15 +1084,10 @@ func makeRuntime(ctx context.Context, runtime *Runtime) (err error) { if strings.HasPrefix(runtime.config.OCIRuntime, "/") { name := filepath.Base(runtime.config.OCIRuntime) - supportsJSON := false - for _, r := range runtime.config.RuntimeSupportsJSON { - if r == name { - supportsJSON = true - break - } - } + json := supportsJSON[name] + nocgroups := supportsNoCgroups[name] - ociRuntime, err := newOCIRuntime(name, []string{runtime.config.OCIRuntime}, runtime.conmonPath, runtime.config, supportsJSON) + ociRuntime, err := newOCIRuntime(name, []string{runtime.config.OCIRuntime}, runtime.conmonPath, runtime.config, json, nocgroups) if err != nil { return err } diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index acd317d20..bffce7bca 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -8,7 +8,7 @@ import ( "strings" "time" - config2 "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage/pkg/stringid" @@ -35,7 +35,7 @@ func (r *Runtime) NewContainer(ctx context.Context, rSpec *spec.Spec, options .. r.lock.Lock() defer r.lock.Unlock() if !r.valid { - return nil, config2.ErrRuntimeStopped + return nil, define.ErrRuntimeStopped } return r.newContainer(ctx, rSpec, options...) } @@ -45,7 +45,7 @@ func (r *Runtime) RestoreContainer(ctx context.Context, rSpec *spec.Spec, config r.lock.Lock() defer r.lock.Unlock() if !r.valid { - return nil, config2.ErrRuntimeStopped + return nil, define.ErrRuntimeStopped } ctr, err := r.initContainerVariables(rSpec, config) @@ -67,7 +67,7 @@ func (r *Runtime) RestoreContainer(ctx context.Context, rSpec *spec.Spec, config func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConfig) (c *Container, err error) { if rSpec == nil { - return nil, errors.Wrapf(config2.ErrInvalidArg, "must provide a valid runtime spec to create container") + return nil, errors.Wrapf(define.ErrInvalidArg, "must provide a valid runtime spec to create container") } ctr := new(Container) ctr.config = new(ContainerConfig) @@ -100,7 +100,7 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf ctr.state.BindMounts = make(map[string]string) - ctr.config.StopTimeout = config2.CtrRemoveTimeout + ctr.config.StopTimeout = define.CtrRemoveTimeout ctr.config.OCIRuntime = r.defaultOCIRuntime.name @@ -152,7 +152,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai }() ctr.valid = true - ctr.state.State = config2.ContainerStateConfigured + ctr.state.State = define.ContainerStateConfigured ctr.runtime = r if ctr.config.OCIRuntime == "" { @@ -160,11 +160,18 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai } else { ociRuntime, ok := r.ociRuntimes[ctr.config.OCIRuntime] if !ok { - return nil, errors.Wrapf(config2.ErrInvalidArg, "requested OCI runtime %s is not available", ctr.config.OCIRuntime) + return nil, errors.Wrapf(define.ErrInvalidArg, "requested OCI runtime %s is not available", ctr.config.OCIRuntime) } ctr.ociRuntime = ociRuntime } + // Check NoCgroups support + if ctr.config.NoCgroups { + if !ctr.ociRuntime.supportsNoCgroups { + return nil, errors.Wrapf(define.ErrInvalidArg, "requested OCI runtime %s is not compatible with NoCgroups", ctr.ociRuntime.name) + } + } + var pod *Pod if ctr.config.Pod != "" { // Get the pod from state @@ -183,43 +190,67 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai ctr.config.Name = name } - // Check CGroup parent sanity, and set it if it was not set - switch r.config.CgroupManager { - case CgroupfsCgroupsManager: - if ctr.config.CgroupParent == "" { - if pod != nil && pod.config.UsePodCgroup { - podCgroup, err := pod.CgroupPath() - if err != nil { - return nil, errors.Wrapf(err, "error retrieving pod %s cgroup", pod.ID()) + // If CGroups are disabled, we MUST create a PID namespace. + // Otherwise, the OCI runtime won't be able to stop our container. + if ctr.config.NoCgroups { + if ctr.config.Spec.Linux == nil { + return nil, errors.Wrapf(define.ErrInvalidArg, "must provide Linux namespace configuration in OCI spec when using NoCgroups") + } + foundPid := false + for _, ns := range ctr.config.Spec.Linux.Namespaces { + if ns.Type == spec.PIDNamespace { + foundPid = true + if ns.Path != "" { + return nil, errors.Wrapf(define.ErrInvalidArg, "containers not creating CGroups must create a private PID namespace - cannot use another") } - if podCgroup == "" { - return nil, errors.Wrapf(config2.ErrInternal, "pod %s cgroup is not set", pod.ID()) + break + } + } + if !foundPid { + return nil, errors.Wrapf(define.ErrInvalidArg, "containers not creating CGroups must create a private PID namespace") + } + } + + // Check CGroup parent sanity, and set it if it was not set. + // Only if we're actually configuring CGroups. + if !ctr.config.NoCgroups { + switch r.config.CgroupManager { + case CgroupfsCgroupsManager: + if ctr.config.CgroupParent == "" { + if pod != nil && pod.config.UsePodCgroup { + podCgroup, err := pod.CgroupPath() + if err != nil { + return nil, errors.Wrapf(err, "error retrieving pod %s cgroup", pod.ID()) + } + if podCgroup == "" { + return nil, errors.Wrapf(define.ErrInternal, "pod %s cgroup is not set", pod.ID()) + } + ctr.config.CgroupParent = podCgroup + } else { + ctr.config.CgroupParent = CgroupfsDefaultCgroupParent } - ctr.config.CgroupParent = podCgroup - } else { - ctr.config.CgroupParent = CgroupfsDefaultCgroupParent + } else if strings.HasSuffix(path.Base(ctr.config.CgroupParent), ".slice") { + return nil, errors.Wrapf(define.ErrInvalidArg, "systemd slice received as cgroup parent when using cgroupfs") } - } else if strings.HasSuffix(path.Base(ctr.config.CgroupParent), ".slice") { - return nil, errors.Wrapf(config2.ErrInvalidArg, "systemd slice received as cgroup parent when using cgroupfs") - } - case SystemdCgroupsManager: - if ctr.config.CgroupParent == "" { - if pod != nil && pod.config.UsePodCgroup { - podCgroup, err := pod.CgroupPath() - if err != nil { - return nil, errors.Wrapf(err, "error retrieving pod %s cgroup", pod.ID()) + case SystemdCgroupsManager: + if ctr.config.CgroupParent == "" { + if pod != nil && pod.config.UsePodCgroup { + podCgroup, err := pod.CgroupPath() + if err != nil { + return nil, errors.Wrapf(err, "error retrieving pod %s cgroup", pod.ID()) + } + ctr.config.CgroupParent = podCgroup + } else if rootless.IsRootless() { + ctr.config.CgroupParent = SystemdDefaultRootlessCgroupParent + } else { + ctr.config.CgroupParent = SystemdDefaultCgroupParent } - ctr.config.CgroupParent = podCgroup - } else if rootless.IsRootless() { - ctr.config.CgroupParent = SystemdDefaultRootlessCgroupParent - } else { - ctr.config.CgroupParent = SystemdDefaultCgroupParent + } else if len(ctr.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(ctr.config.CgroupParent), ".slice") { + return nil, errors.Wrapf(define.ErrInvalidArg, "did not receive systemd slice as cgroup parent when using systemd to manage cgroups") } - } else if len(ctr.config.CgroupParent) < 6 || !strings.HasSuffix(path.Base(ctr.config.CgroupParent), ".slice") { - return nil, errors.Wrapf(config2.ErrInvalidArg, "did not receive systemd slice as cgroup parent when using systemd to manage cgroups") + default: + return nil, errors.Wrapf(define.ErrInvalidArg, "unsupported CGroup manager: %s - cannot validate cgroup parent", r.config.CgroupManager) } - default: - return nil, errors.Wrapf(config2.ErrInvalidArg, "unsupported CGroup manager: %s - cannot validate cgroup parent", r.config.CgroupManager) } if ctr.restoreFromCheckpoint { @@ -262,7 +293,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai ctrNamedVolumes = append(ctrNamedVolumes, dbVol) // The volume exists, we're good continue - } else if errors.Cause(err) != config2.ErrNoSuchVolume { + } else if errors.Cause(err) != define.ErrNoSuchVolume { return nil, errors.Wrapf(err, "error retrieving named volume %s for new container", vol.Name) } @@ -275,10 +306,6 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (c *Contai return nil, errors.Wrapf(err, "error creating named volume %q", vol.Name) } - if err := ctr.copyWithTarFromImage(vol.Dest, newVol.MountPoint()); err != nil && !os.IsNotExist(err) { - return nil, errors.Wrapf(err, "Failed to copy content into new volume mount %q", vol.Name) - } - ctrNamedVolumes = append(ctrNamedVolumes, newVol) } @@ -386,7 +413,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, } if !r.valid { - return config2.ErrRuntimeStopped + return define.ErrRuntimeStopped } // Update the container to get current state @@ -402,7 +429,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, } } - if c.state.State == config2.ContainerStatePaused { + if c.state.State == define.ContainerStatePaused { if err := c.ociRuntime.killContainer(c, 9); err != nil { return err } @@ -416,7 +443,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, } // Check that the container's in a good state to be removed - if c.state.State == config2.ContainerStateRunning { + if c.state.State == define.ContainerStateRunning { if err := c.stop(c.StopTimeout()); err != nil { return errors.Wrapf(err, "cannot remove container %s as it could not be stopped", c.ID()) } @@ -439,7 +466,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, } if len(deps) != 0 { depsStr := strings.Join(deps, ", ") - return errors.Wrapf(config2.ErrCtrExists, "container %s has dependent containers which must be removed before it: %s", c.ID(), depsStr) + return errors.Wrapf(define.ErrCtrExists, "container %s has dependent containers which must be removed before it: %s", c.ID(), depsStr) } } @@ -483,8 +510,8 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, // Delete the container. // Not needed in Configured and Exited states, where the container // doesn't exist in the runtime - if c.state.State != config2.ContainerStateConfigured && - c.state.State != config2.ContainerStateExited { + if c.state.State != define.ContainerStateConfigured && + c.state.State != define.ContainerStateExited { if err := c.delete(ctx); err != nil { if cleanupErr == nil { cleanupErr = err @@ -514,7 +541,7 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, if !volume.IsCtrSpecific() { continue } - if err := runtime.removeVolume(ctx, volume, false); err != nil && err != config2.ErrNoSuchVolume && err != config2.ErrVolumeBeingUsed { + if err := runtime.removeVolume(ctx, volume, false); err != nil && err != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed { logrus.Errorf("cleanup volume (%s): %v", v, err) } } @@ -529,7 +556,7 @@ func (r *Runtime) GetContainer(id string) (*Container, error) { defer r.lock.RUnlock() if !r.valid { - return nil, config2.ErrRuntimeStopped + return nil, define.ErrRuntimeStopped } return r.state.Container(id) @@ -541,7 +568,7 @@ func (r *Runtime) HasContainer(id string) (bool, error) { defer r.lock.RUnlock() if !r.valid { - return false, config2.ErrRuntimeStopped + return false, define.ErrRuntimeStopped } return r.state.HasContainer(id) @@ -554,7 +581,7 @@ func (r *Runtime) LookupContainer(idOrName string) (*Container, error) { defer r.lock.RUnlock() if !r.valid { - return nil, config2.ErrRuntimeStopped + return nil, define.ErrRuntimeStopped } return r.state.LookupContainer(idOrName) } @@ -568,7 +595,7 @@ func (r *Runtime) GetContainers(filters ...ContainerFilter) ([]*Container, error defer r.lock.RUnlock() if !r.valid { - return nil, config2.ErrRuntimeStopped + return nil, define.ErrRuntimeStopped } ctrs, err := r.state.AllContainers() @@ -601,7 +628,7 @@ func (r *Runtime) GetAllContainers() ([]*Container, error) { func (r *Runtime) GetRunningContainers() ([]*Container, error) { running := func(c *Container) bool { state, _ := c.State() - return state == config2.ContainerStateRunning + return state == define.ContainerStateRunning } return r.GetContainers(running) } @@ -629,7 +656,7 @@ func (r *Runtime) GetLatestContainer() (*Container, error) { return nil, errors.Wrapf(err, "unable to find latest container") } if len(ctrs) == 0 { - return nil, config2.ErrNoSuchCtr + return nil, define.ErrNoSuchCtr } for containerIndex, ctr := range ctrs { createdTime := ctr.config.CreatedTime diff --git a/libpod/stats.go b/libpod/stats.go index 776870bd2..5513abce5 100644 --- a/libpod/stats.go +++ b/libpod/stats.go @@ -19,6 +19,10 @@ func (c *Container) GetContainerStats(previousStats *ContainerStats) (*Container stats.ContainerID = c.ID() stats.Name = c.Name() + if c.config.NoCgroups { + return nil, errors.Wrapf(define.ErrNoCgroups, "cannot run top on container %s as it did not create a cgroup", c.ID()) + } + if !c.batched { c.lock.Lock() defer c.lock.Unlock() diff --git a/libpod/volume.go b/libpod/volume.go index b4de3aedc..c4771bbb8 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -57,6 +57,13 @@ type VolumeState struct { // On incrementing from 0, the volume will be mounted on the host. // On decrementing to 0, the volume will be unmounted on the host. MountCount uint `json:"mountCount"` + // NeedsCopyUp indicates that the next time the volume is mounted into + // a container, the container will "copy up" the contents of the + // mountpoint into the volume. + // This should only be done once. As such, this is set at container + // create time, then cleared after the copy up is done and never set + // again. + NeedsCopyUp bool `json:"notYetMounted,omitempty"` } // Name retrieves the volume's name diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go index 2e886e1b0..42b935e7c 100644 --- a/libpod/volume_internal.go +++ b/libpod/volume_internal.go @@ -11,9 +11,11 @@ import ( func newVolume(runtime *Runtime) (*Volume, error) { volume := new(Volume) volume.config = new(VolumeConfig) + volume.state = new(VolumeState) volume.runtime = runtime volume.config.Labels = make(map[string]string) volume.config.Options = make(map[string]string) + volume.state.NeedsCopyUp = true return volume, nil } |