diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/container_api.go | 13 | ||||
-rw-r--r-- | libpod/container_internal.go | 41 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 88 | ||||
-rw-r--r-- | libpod/container_internal_unsupported.go | 4 | ||||
-rw-r--r-- | libpod/define/exec_codes.go | 20 | ||||
-rw-r--r-- | libpod/image/image.go | 4 | ||||
-rw-r--r-- | libpod/image/image_test.go | 8 | ||||
-rw-r--r-- | libpod/networking_linux.go | 73 | ||||
-rw-r--r-- | libpod/oci_attach_linux.go | 7 | ||||
-rw-r--r-- | libpod/oci_internal_linux.go | 87 | ||||
-rw-r--r-- | libpod/oci_linux.go | 15 | ||||
-rw-r--r-- | libpod/pod_api.go | 12 | ||||
-rw-r--r-- | libpod/runtime.go | 51 | ||||
-rw-r--r-- | libpod/runtime_cstorage.go | 6 | ||||
-rw-r--r-- | libpod/runtime_pod_infra_linux.go | 4 |
15 files changed, 340 insertions, 93 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go index 9bf97c5d4..4f0d5301c 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -216,8 +216,8 @@ func (c *Container) Kill(signal uint) error { } // Exec starts a new process inside the container -// Returns an exit code and an error. If Exec was not able to exec in the container before a failure, an exit code of 126 is returned. -// If another generic error happens, an exit code of 125 is returned. +// Returns an exit code and an error. If Exec was not able to exec in the container before a failure, an exit code of define.ExecErrorCodeCannotInvoke is returned. +// If another generic error happens, an exit code of define.ExecErrorCodeGeneric is returned. // Sometimes, the $RUNTIME exec call errors, and if that is the case, the exit code is the exit code of the call. // Otherwise, the exit code will be the exit code of the executed call inside of the container. // TODO investigate allowing exec without attaching @@ -821,3 +821,12 @@ func (c *Container) Restore(ctx context.Context, options ContainerCheckpointOpti defer c.newContainerEvent(events.Restore) return c.restore(ctx, options) } + +// AutoRemove indicates whether the container will be removed after it is executed +func (c *Container) AutoRemove() bool { + spec := c.config.Spec + if spec.Annotations == nil { + return false + } + return c.Spec().Annotations[InspectAnnotationAutoremove] == InspectResponseTrue +} diff --git a/libpod/container_internal.go b/libpod/container_internal.go index ac565fdad..f1456548b 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -14,6 +14,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/ctime" "github.com/containers/libpod/pkg/hooks" "github.com/containers/libpod/pkg/hooks/exec" @@ -163,7 +164,15 @@ func (c *Container) createExecBundle(sessionID string) (err error) { // cleanup an exec session after its done func (c *Container) cleanupExecBundle(sessionID string) error { - return os.RemoveAll(c.execBundlePath(sessionID)) + if err := os.RemoveAll(c.execBundlePath(sessionID)); err != nil && !os.IsNotExist(err) { + return err + } + // Clean up the sockets dir. Issue #3962 + // Also ignore if it doesn't exist for some reason; hence the conditional return below + if err := os.RemoveAll(filepath.Join(c.ociRuntime.socketsDir, sessionID)); err != nil && !os.IsNotExist(err) { + return err + } + return nil } // the path to a containers exec session bundle @@ -613,7 +622,7 @@ func (c *Container) refresh() error { return err } - return nil + return c.refreshCNI() } // Remove conmon attach socket and terminal resize FIFO @@ -637,14 +646,8 @@ func (c *Container) removeConmonFiles() error { // Remove the exit file so we don't leak memory in tmpfs exitFile := filepath.Join(c.ociRuntime.exitsDir, c.ID()) - if _, err := os.Stat(exitFile); err != nil { - if !os.IsNotExist(err) { - return errors.Wrapf(err, "error running stat on container %s exit file", c.ID()) - } - } else { - if err := os.Remove(exitFile); err != nil { - return errors.Wrapf(err, "error removing container %s exit file", c.ID()) - } + if err := os.Remove(exitFile); err != nil && !os.IsNotExist(err) { + return errors.Wrapf(err, "error removing container %s exit file", c.ID()) } return nil @@ -913,6 +916,12 @@ func (c *Container) init(ctx context.Context, retainRetries bool) error { span.SetTag("struct", "container") defer span.Finish() + // Unconditionally remove conmon temporary files. + // We've been running into far too many issues where they block startup. + if err := c.removeConmonFiles(); err != nil { + return err + } + // Generate the OCI newSpec newSpec, err := c.generateSpec(ctx) if err != nil { @@ -1124,6 +1133,16 @@ func (c *Container) pause() error { return errors.Wrapf(define.ErrNoCgroups, "cannot pause without using CGroups") } + if rootless.IsRootless() { + cgroupv2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return errors.Wrap(err, "failed to determine cgroupversion") + } + if !cgroupv2 { + return errors.Wrap(define.ErrNoCgroups, "can not pause containers on rootless containers with cgroup V1") + } + } + if err := c.ociRuntime.pauseContainer(c); err != nil { return err } @@ -1349,7 +1368,7 @@ func (c *Container) cleanupStorage() error { // error // We still want to be able to kick the container out of the // state - if errors.Cause(err) == storage.ErrNotAContainer || errors.Cause(err) == storage.ErrContainerUnknown { + if errors.Cause(err) == storage.ErrNotAContainer || errors.Cause(err) == storage.ErrContainerUnknown || errors.Cause(err) == storage.ErrLayerNotMounted { logrus.Errorf("Storage for container %s has been removed", c.ID()) } else { if cleanupErr != nil { diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 9f16389e6..2636fdb6c 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -78,15 +78,21 @@ func (c *Container) prepare() (Err error) { // Set up network namespace if not already set up if c.config.CreateNetNS && c.state.NetNS == nil && !c.config.PostConfigureNetNS { netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c) + if createNetNSErr != nil { + return + } tmpStateLock.Lock() defer tmpStateLock.Unlock() // Assign NetNS attributes to container - if createNetNSErr == nil { - c.state.NetNS = netNS - c.state.NetworkStatus = networkStatus - } + c.state.NetNS = netNS + c.state.NetworkStatus = networkStatus + } + + // handle rootless network namespace setup + if c.state.NetNS != nil && c.config.NetMode == "slirp4netns" && !c.config.PostConfigureNetNS { + createNetNSErr = c.runtime.setupRootlessNetNS(c) } }() // Mount storage if not mounted @@ -181,6 +187,30 @@ func (c *Container) cleanupNetwork() error { return nil } +func (c *Container) getUserOverrides() *lookup.Overrides { + var hasPasswdFile, hasGroupFile bool + overrides := lookup.Overrides{} + for _, m := range c.config.Spec.Mounts { + if m.Destination == "/etc/passwd" { + overrides.ContainerEtcPasswdPath = m.Source + hasPasswdFile = true + } + if m.Destination == "/etc/group" { + overrides.ContainerEtcGroupPath = m.Source + hasGroupFile = true + } + if m.Destination == "/etc" { + if !hasPasswdFile { + overrides.ContainerEtcPasswdPath = filepath.Join(m.Source, "passwd") + } + if !hasGroupFile { + overrides.ContainerEtcGroupPath = filepath.Join(m.Source, "group") + } + } + } + return &overrides +} + // Generate spec for a container // Accepts a map of the container's dependencies func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { @@ -188,7 +218,8 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { span.SetTag("type", "container") defer span.Finish() - execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, nil) + overrides := c.getUserOverrides() + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, c.config.User, overrides) if err != nil { return nil, err } @@ -279,6 +310,17 @@ func (c *Container) generateSpec(ctx context.Context) (*spec.Spec, error) { } } + hasHomeSet := false + for _, s := range c.config.Spec.Process.Env { + if strings.HasPrefix(s, "HOME=") { + hasHomeSet = true + break + } + } + if !hasHomeSet { + c.config.Spec.Process.Env = append(c.config.Spec.Process.Env, fmt.Sprintf("HOME=%s", execUser.Home)) + } + if c.config.User != "" { // User and Group must go together g.SetProcessUID(uint32(execUser.Uid)) @@ -491,12 +533,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 { @@ -1266,3 +1325,10 @@ func (c *Container) copyOwnerAndPerms(source, dest string) error { } return nil } + +// Teardown CNI config on refresh +func (c *Container) refreshCNI() error { + // Let's try and delete any lingering network config... + podNetwork := c.runtime.getPodNetwork(c.ID(), c.config.Name, "", c.config.Networks, c.config.PortMappings, c.config.StaticIP) + return c.runtime.netPlugin.TearDownPod(podNetwork) +} diff --git a/libpod/container_internal_unsupported.go b/libpod/container_internal_unsupported.go index 6fa19a778..05a587c59 100644 --- a/libpod/container_internal_unsupported.go +++ b/libpod/container_internal_unsupported.go @@ -40,3 +40,7 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti func (c *Container) copyOwnerAndPerms(source, dest string) error { return nil } + +func (c *Container) refreshCNI() error { + return define.ErrNotImplemented +} diff --git a/libpod/define/exec_codes.go b/libpod/define/exec_codes.go index 7184f1e59..f94616b33 100644 --- a/libpod/define/exec_codes.go +++ b/libpod/define/exec_codes.go @@ -1,7 +1,10 @@ package define import ( + "strings" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) const ( @@ -28,3 +31,20 @@ func TranslateExecErrorToExitCode(originalEC int, err error) int { } return originalEC } + +// ExitCode reads the error message when failing to executing container process +// and then returns 0 if no error, ExecErrorCodeNotFound if command does not exist, or ExecErrorCodeCannotInvoke for +// all other errors +func ExitCode(err error) int { + if err == nil { + return 0 + } + e := strings.ToLower(err.Error()) + logrus.Debugf("ExitCode msg: %q", e) + if strings.Contains(e, "not found") || + strings.Contains(e, "no such file") { + return ExecErrorCodeNotFound + } + + return ExecErrorCodeCannotInvoke +} diff --git a/libpod/image/image.go b/libpod/image/image.go index 0be6eeeb9..855da8611 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -29,7 +29,6 @@ import ( "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" - "github.com/containers/storage/pkg/reexec" "github.com/opencontainers/go-digest" imgspecv1 "github.com/opencontainers/image-spec/specs-go/v1" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" @@ -85,9 +84,6 @@ func NewImageRuntimeFromStore(store storage.Store) *Runtime { // NewImageRuntimeFromOptions creates an Image Runtime including the store given // store options func NewImageRuntimeFromOptions(options storage.StoreOptions) (*Runtime, error) { - if reexec.Init() { - return nil, errors.Errorf("unable to reexec") - } store, err := setStore(options) if err != nil { return nil, err diff --git a/libpod/image/image_test.go b/libpod/image/image_test.go index 5a6d095f6..ef39d09c3 100644 --- a/libpod/image/image_test.go +++ b/libpod/image/image_test.go @@ -11,6 +11,7 @@ import ( "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/util" "github.com/containers/storage" + "github.com/containers/storage/pkg/reexec" "github.com/opencontainers/go-digest" "github.com/stretchr/testify/assert" ) @@ -70,6 +71,13 @@ func makeLocalMatrix(b, bg *Image) ([]localImageTest, error) { } +func TestMain(m *testing.M) { + if reexec.Init() { + return + } + os.Exit(m.Run()) +} + // TestImage_NewFromLocal tests finding the image locally by various names, // tags, and aliases func TestImage_NewFromLocal(t *testing.T) { diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index fd14b2f73..d854a2de6 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -90,9 +90,6 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re // Create and configure a new network namespace for a container func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, err error) { - if rootless.IsRootless() { - return nil, nil, errors.New("cannot configure a new network namespace in rootless mode, only --network=slirp4netns is supported") - } ctrNS, err := netns.NewNS() if err != nil { return nil, nil, errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID()) @@ -110,7 +107,10 @@ func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, logrus.Debugf("Made network namespace at %s for container %s", ctrNS.Path(), ctr.ID()) - networkStatus, err := r.configureNetNS(ctr, ctrNS) + networkStatus := []*cnitypes.Result{} + if !rootless.IsRootless() { + networkStatus, err = r.configureNetNS(ctr, ctrNS) + } return ctrNS, networkStatus, err } @@ -127,20 +127,17 @@ type slirp4netnsCmd struct { Args slirp4netnsCmdArg `json:"arguments"` } -func checkSlirpFlags(path string) (bool, bool, error) { +func checkSlirpFlags(path string) (bool, bool, bool, error) { cmd := exec.Command(path, "--help") out, err := cmd.CombinedOutput() if err != nil { - return false, false, err + return false, false, false, err } - return strings.Contains(string(out), "--disable-host-loopback"), strings.Contains(string(out), "--mtu"), nil + return strings.Contains(string(out), "--disable-host-loopback"), strings.Contains(string(out), "--mtu"), strings.Contains(string(out), "--enable-sandbox"), nil } // Configure the network namespace for a rootless container func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { - defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR) - defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW) - path := r.config.NetworkCmdPath if path == "" { @@ -164,9 +161,9 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { cmdArgs := []string{} if havePortMapping { - cmdArgs = append(cmdArgs, "--api-socket", apiSocket, fmt.Sprintf("%d", ctr.state.PID)) + cmdArgs = append(cmdArgs, "--api-socket", apiSocket) } - dhp, mtu, err := checkSlirpFlags(path) + dhp, mtu, sandbox, err := checkSlirpFlags(path) if err != nil { return errors.Wrapf(err, "error checking slirp4netns binary %s", path) } @@ -176,13 +173,35 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) { if mtu { cmdArgs = append(cmdArgs, "--mtu", "65520") } - cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4", fmt.Sprintf("%d", ctr.state.PID), "tap0") + if sandbox { + cmdArgs = append(cmdArgs, "--enable-sandbox") + } - cmd := exec.Command(path, cmdArgs...) + // the slirp4netns arguments being passed are describes as follows: + // from the slirp4netns documentation: https://github.com/rootless-containers/slirp4netns + // -c, --configure Brings up the tap interface + // -e, --exit-fd=FD specify the FD for terminating slirp4netns + // -r, --ready-fd=FD specify the FD to write to when the initialization steps are finished + cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4") + if !ctr.config.PostConfigureNetNS { + ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() + if err != nil { + return errors.Wrapf(err, "failed to create rootless network sync pipe") + } + cmdArgs = append(cmdArgs, "--netns-type=path", ctr.state.NetNS.Path(), "tap0") + } else { + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR) + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW) + cmdArgs = append(cmdArgs, fmt.Sprintf("%d", ctr.state.PID), "tap0") + } + cmd := exec.Command(path, cmdArgs...) + logrus.Debugf("slirp4netns command: %s", strings.Join(cmd.Args, " ")) cmd.SysProcAttr = &syscall.SysProcAttr{ Setpgid: true, } + + // Leak one end of the pipe in slirp4netns, the other will be sent to conmon cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncR, syncW) if err := cmd.Start(); err != nil { @@ -385,20 +404,22 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) - var requestedIP net.IP - if ctr.requestedIP != nil { - requestedIP = ctr.requestedIP - // cancel request for a specific IP in case the container is reused later - ctr.requestedIP = nil - } else { - requestedIP = ctr.config.StaticIP - } + // rootless containers do not use the CNI plugin + if !rootless.IsRootless() { + var requestedIP net.IP + if ctr.requestedIP != nil { + requestedIP = ctr.requestedIP + // cancel request for a specific IP in case the container is reused later + ctr.requestedIP = nil + } else { + requestedIP = ctr.config.StaticIP + } - podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP) - // The network may have already been torn down, so don't fail here, just log - if err := r.netPlugin.TearDownPod(podNetwork); err != nil { - return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID()) + if err := r.netPlugin.TearDownPod(podNetwork); err != nil { + return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID()) + } } // First unmount the namespace diff --git a/libpod/oci_attach_linux.go b/libpod/oci_attach_linux.go index 22afa7416..6cada0801 100644 --- a/libpod/oci_attach_linux.go +++ b/libpod/oci_attach_linux.go @@ -107,8 +107,6 @@ func (c *Container) attachToExec(streams *AttachStreams, keys string, resize <-c logrus.Debugf("Attaching to container %s exec session %s", c.ID(), sessionID) - registerResizeFunc(resize, c.execBundlePath(sessionID)) - // set up the socket path, such that it is the correct length and location for exec socketPath := buildSocketPath(c.execAttachSocketPath(sessionID)) @@ -116,6 +114,7 @@ func (c *Container) attachToExec(streams *AttachStreams, keys string, resize <-c if _, err := readConmonPipeData(attachFd, ""); err != nil { return err } + // 2: then attach conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: socketPath, Net: "unixpacket"}) if err != nil { @@ -127,6 +126,10 @@ func (c *Container) attachToExec(streams *AttachStreams, keys string, resize <-c } }() + // Register the resize func after we've read the attach socket, as we know at this point the + // 'ctl' file has been created in conmon + registerResizeFunc(resize, c.execBundlePath(sessionID)) + // start listening on stdio of the process receiveStdoutError, stdinDone := setupStdioChannels(streams, conn, detachKeys) diff --git a/libpod/oci_internal_linux.go b/libpod/oci_internal_linux.go index f9e935d86..a5cce795b 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" @@ -130,9 +131,14 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Containe } if ctr.config.NetMode.IsSlirp4netns() { - ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() - if err != nil { - return errors.Wrapf(err, "failed to create rootless network sync pipe") + if ctr.config.PostConfigureNetNS { + ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe() + if err != nil { + return errors.Wrapf(err, "failed to create rootless network sync pipe") + } + } else { + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR) + defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW) } // Leak one end in conmon, the other one will be leaked into slirp4netns cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW) @@ -199,13 +205,16 @@ func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, se pspec.Cwd = cwd } + + overrides := c.getUserOverrides() + execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, overrides) + if err != nil { + return nil, err + } + // If user was set, look it up in the container to get a UID to use on // the host if user != "" { - execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, nil) - if err != nil { - return nil, err - } sgids := make([]uint32, 0, len(execUser.Sgids)) for _, sgid := range execUser.Sgids { sgids = append(sgids, uint32(sgid)) @@ -219,6 +228,17 @@ func prepareProcessExec(c *Container, cmd, env []string, tty bool, cwd, user, se pspec.User = processUser } + hasHomeSet := false + for _, s := range pspec.Env { + if strings.HasPrefix(s, "HOME=") { + hasHomeSet = true + break + } + } + if !hasHomeSet { + pspec.Env = append(pspec.Env, fmt.Sprintf("HOME=%s", execUser.Home)) + } + processJSON, err := json.Marshal(pspec) if err != nil { return nil, err @@ -359,35 +379,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 { + mustCreateCgroup := true // If cgroup creation is disabled - just signal. if ctr.config.NoCgroups { - return writeConmonPipeData(startFd) + mustCreateCgroup = false } - 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] + if rootless.IsRootless() { + ownsCgroup, err := cgroups.UserOwnsCurrentSystemdCgroup() + if err != nil { + return err } + mustCreateCgroup = !ownsCgroup + } - 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 err != nil { - logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + 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 091b6d155..9ec074704 100644 --- a/libpod/oci_linux.go +++ b/libpod/oci_linux.go @@ -8,6 +8,7 @@ import ( "os/exec" "path/filepath" "runtime" + "strconv" "strings" "syscall" "time" @@ -199,7 +200,7 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty args := r.sharedConmonArgs(c, sessionID, c.execBundlePath(sessionID), c.execPidPath(sessionID), c.execLogPath(sessionID), c.execExitFileDir(sessionID), ociLog) if preserveFDs > 0 { - args = append(args, formatRuntimeOpts("--preserve-fds", string(preserveFDs))...) + args = append(args, formatRuntimeOpts("--preserve-fds", strconv.Itoa(preserveFDs))...) } for _, capability := range capAdd { @@ -236,6 +237,12 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty return -1, nil, err } + if preserveFDs > 0 { + for fd := 3; fd < 3+preserveFDs; fd++ { + execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))) + } + } + // we don't want to step on users fds they asked to preserve // Since 0-2 are used for stdio, start the fds we pass in at preserveFDs+3 execCmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", preserveFDs+3), fmt.Sprintf("_OCI_STARTPIPE=%d", preserveFDs+4), fmt.Sprintf("_OCI_ATTACHPIPE=%d", preserveFDs+5)) @@ -248,12 +255,6 @@ func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty Setpgid: true, } - if preserveFDs > 0 { - for fd := 3; fd < 3+preserveFDs; fd++ { - execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))) - } - } - err = startCommandGivenSelinux(execCmd) // We don't need children pipes on the parent side diff --git a/libpod/pod_api.go b/libpod/pod_api.go index e2448e92a..7c786b835 100644 --- a/libpod/pod_api.go +++ b/libpod/pod_api.go @@ -5,6 +5,8 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" + "github.com/containers/libpod/pkg/cgroups" + "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -163,6 +165,16 @@ func (p *Pod) Pause() (map[string]error, error) { return nil, define.ErrPodRemoved } + if rootless.IsRootless() { + cgroupv2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return nil, errors.Wrap(err, "failed to determine cgroupversion") + } + if !cgroupv2 { + return nil, errors.Wrap(define.ErrNoCgroups, "can not pause pods containing rootless containers with cgroup V1") + } + } + allCtrs, err := p.runtime.state.PodContainers(p) if err != nil { return nil, err diff --git a/libpod/runtime.go b/libpod/runtime.go index 80b58654e..675c92b7a 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -23,6 +23,7 @@ import ( "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/lock" + "github.com/containers/libpod/pkg/cgroups" sysreg "github.com/containers/libpod/pkg/registries" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" @@ -247,13 +248,15 @@ type RuntimeConfig struct { // EventsLogger determines where events should be logged EventsLogger string `toml:"events_logger"` // EventsLogFilePath is where the events log is stored. - EventsLogFilePath string `toml:"-events_logfile_path"` + EventsLogFilePath string `toml:"events_logfile_path"` //DetachKeys is the sequence of keys used to detach a container DetachKeys string `toml:"detach_keys"` // SDNotify tells Libpod to allow containers to notify the host // systemd of readiness using the SD_NOTIFY mechanism SDNotify bool + // CgroupCheck verifies if the cgroup check for correct OCI runtime has been done. + CgroupCheck bool `toml:"cgroup_check,omitempty"` } // runtimeConfiguredFrom is a struct used during early runtime init to help @@ -575,6 +578,10 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. configPath) } + if err := cgroupV2Check(configPath, tmpConfig); err != nil { + return nil, err + } + if tmpConfig.StaticDir != "" { runtime.configuredFrom.libpodStaticDirSet = true } @@ -664,6 +671,14 @@ func newRuntimeFromConfig(ctx context.Context, userConfigPath string, options .. runtime.config.OCIRuntime = tmpConfig.OCIRuntime } + cgroupsV2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return nil, err + } + if cgroupsV2 { + runtime.config.CgroupCheck = true + } + break } } @@ -1451,3 +1466,37 @@ func (r *Runtime) ImageRuntime() *image.Runtime { func (r *Runtime) SystemContext() *types.SystemContext { return r.imageContext } + +// Since runc does not currently support cgroupV2 +// Change to default crun on first running of libpod.conf +// TODO Once runc has support for cgroups, this function should be removed. +func cgroupV2Check(configPath string, tmpConfig *RuntimeConfig) error { + if !tmpConfig.CgroupCheck && rootless.IsRootless() { + cgroupsV2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil { + return err + } + if cgroupsV2 { + path, err := exec.LookPath("crun") + if err != nil { + logrus.Warnf("Can not find crun package on the host, containers might fail to run on cgroup V2 systems without crun: %q", err) + // Can't find crun path so do nothing + return nil + } + tmpConfig.CgroupCheck = true + tmpConfig.OCIRuntime = path + file, err := os.OpenFile(configPath, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + return errors.Wrapf(err, "cannot open file %s", configPath) + } + defer file.Close() + enc := toml.NewEncoder(file) + if err := enc.Encode(tmpConfig); err != nil { + if removeErr := os.Remove(configPath); removeErr != nil { + logrus.Debugf("unable to remove %s: %q", configPath, err) + } + } + } + } + return nil +} diff --git a/libpod/runtime_cstorage.go b/libpod/runtime_cstorage.go index 586db5a1e..1e84aef4b 100644 --- a/libpod/runtime_cstorage.go +++ b/libpod/runtime_cstorage.go @@ -1,6 +1,8 @@ package libpod import ( + "time" + "github.com/containers/libpod/libpod/define" "github.com/containers/storage" "github.com/pkg/errors" @@ -12,6 +14,8 @@ import ( type StorageContainer struct { ID string Names []string + Image string + CreateTime time.Time PresentInLibpod bool } @@ -31,6 +35,8 @@ func (r *Runtime) ListStorageContainers() ([]*StorageContainer, error) { storageCtr := new(StorageContainer) storageCtr.ID = ctr.ID storageCtr.Names = ctr.Names + storageCtr.Image = ctr.ImageID + storageCtr.CreateTime = ctr.Created // Look up if container is in state hasCtr, err := r.state.HasContainer(ctr.ID) diff --git a/libpod/runtime_pod_infra_linux.go b/libpod/runtime_pod_infra_linux.go index ad6662f03..6a27c2800 100644 --- a/libpod/runtime_pod_infra_linux.go +++ b/libpod/runtime_pod_infra_linux.go @@ -99,7 +99,9 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID if isRootless { netmode = "slirp4netns" } - options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, netmode, networks)) + // PostConfigureNetNS should not be set since user namespace sharing is not implemented + // and rootless networking no longer supports post configuration setup + options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, false, netmode, networks)) return r.newContainer(ctx, g.Config, options...) } |