summaryrefslogtreecommitdiff
path: root/libpod
diff options
context:
space:
mode:
Diffstat (limited to 'libpod')
-rw-r--r--libpod/boltdb_state.go1
-rw-r--r--libpod/container.go3
-rw-r--r--libpod/container_inspect.go10
-rw-r--r--libpod/container_internal.go108
-rw-r--r--libpod/container_internal_linux.go43
-rw-r--r--libpod/container_top_linux.go4
-rw-r--r--libpod/define/errors.go4
-rw-r--r--libpod/image/prune.go6
-rw-r--r--libpod/oci.go30
-rw-r--r--libpod/oci_internal_linux.go63
-rw-r--r--libpod/oci_linux.go4
-rw-r--r--libpod/options.go29
-rw-r--r--libpod/runtime.go86
-rw-r--r--libpod/runtime_ctr.go139
-rw-r--r--libpod/stats.go4
-rw-r--r--libpod/volume.go7
-rw-r--r--libpod/volume_internal.go2
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
}