rootless: Rearrange setup of rootless containers
In order to run Podman with VM-based runtimes unprivileged, the
network must be set up prior to the container creation. Therefore
this commit modifies Podman to run rootless containers by:
1. create a network namespace
2. pass the netns persistent mount path to the slirp4netns
to create the tap inferface
3. pass the netns path to the OCI spec, so the runtime can
enter the netns
Closes #2897
Signed-off-by: Gabi Beyer <gabrielle.n.beyer@intel.com>
This commit is contained in:
parent
6ce8d05a5b
commit
5813c8246e
|
|
@ -78,15 +78,21 @@ func (c *Container) prepare() (Err error) {
|
||||||
// Set up network namespace if not already set up
|
// Set up network namespace if not already set up
|
||||||
if c.config.CreateNetNS && c.state.NetNS == nil && !c.config.PostConfigureNetNS {
|
if c.config.CreateNetNS && c.state.NetNS == nil && !c.config.PostConfigureNetNS {
|
||||||
netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
|
netNS, networkStatus, createNetNSErr = c.runtime.createNetNS(c)
|
||||||
|
if createNetNSErr != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
tmpStateLock.Lock()
|
tmpStateLock.Lock()
|
||||||
defer tmpStateLock.Unlock()
|
defer tmpStateLock.Unlock()
|
||||||
|
|
||||||
// Assign NetNS attributes to container
|
// Assign NetNS attributes to container
|
||||||
if createNetNSErr == nil {
|
c.state.NetNS = netNS
|
||||||
c.state.NetNS = netNS
|
c.state.NetworkStatus = networkStatus
|
||||||
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
|
// Mount storage if not mounted
|
||||||
|
|
|
||||||
|
|
@ -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
|
// Create and configure a new network namespace for a container
|
||||||
func (r *Runtime) createNetNS(ctr *Container) (n ns.NetNS, q []*cnitypes.Result, err error) {
|
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()
|
ctrNS, err := netns.NewNS()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, errors.Wrapf(err, "error creating network namespace for container %s", ctr.ID())
|
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())
|
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
|
return ctrNS, networkStatus, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -138,9 +138,6 @@ func checkSlirpFlags(path string) (bool, bool, bool, error) {
|
||||||
|
|
||||||
// Configure the network namespace for a rootless container
|
// Configure the network namespace for a rootless container
|
||||||
func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
|
func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
|
||||||
defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncR)
|
|
||||||
defer errorhandling.CloseQuiet(ctr.rootlessSlirpSyncW)
|
|
||||||
|
|
||||||
path := r.config.NetworkCmdPath
|
path := r.config.NetworkCmdPath
|
||||||
|
|
||||||
if path == "" {
|
if path == "" {
|
||||||
|
|
@ -164,7 +161,7 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
|
||||||
|
|
||||||
cmdArgs := []string{}
|
cmdArgs := []string{}
|
||||||
if havePortMapping {
|
if havePortMapping {
|
||||||
cmdArgs = append(cmdArgs, "--api-socket", apiSocket, fmt.Sprintf("%d", ctr.state.PID))
|
cmdArgs = append(cmdArgs, "--api-socket", apiSocket)
|
||||||
}
|
}
|
||||||
dhp, mtu, sandbox, err := checkSlirpFlags(path)
|
dhp, mtu, sandbox, err := checkSlirpFlags(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -179,13 +176,32 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) (err error) {
|
||||||
if sandbox {
|
if sandbox {
|
||||||
cmdArgs = append(cmdArgs, "--enable-sandbox")
|
cmdArgs = append(cmdArgs, "--enable-sandbox")
|
||||||
}
|
}
|
||||||
cmdArgs = append(cmdArgs, "-c", "-e", "3", "-r", "4", fmt.Sprintf("%d", ctr.state.PID), "tap0")
|
|
||||||
|
// 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...)
|
cmd := exec.Command(path, cmdArgs...)
|
||||||
|
logrus.Debugf("slirp4netns command: %s", strings.Join(cmd.Args, " "))
|
||||||
cmd.SysProcAttr = &syscall.SysProcAttr{
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
||||||
Setpgid: true,
|
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)
|
cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncR, syncW)
|
||||||
|
|
||||||
if err := cmd.Start(); err != nil {
|
if err := cmd.Start(); err != nil {
|
||||||
|
|
@ -388,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())
|
logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID())
|
||||||
|
|
||||||
var requestedIP net.IP
|
// rootless containers do not use the CNI plugin
|
||||||
if ctr.requestedIP != nil {
|
if !rootless.IsRootless() {
|
||||||
requestedIP = ctr.requestedIP
|
var requestedIP net.IP
|
||||||
// cancel request for a specific IP in case the container is reused later
|
if ctr.requestedIP != nil {
|
||||||
ctr.requestedIP = nil
|
requestedIP = ctr.requestedIP
|
||||||
} else {
|
// cancel request for a specific IP in case the container is reused later
|
||||||
requestedIP = ctr.config.StaticIP
|
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 {
|
||||||
if err := r.netPlugin.TearDownPod(podNetwork); err != nil {
|
return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID())
|
||||||
return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID())
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// First unmount the namespace
|
// First unmount the namespace
|
||||||
|
|
|
||||||
|
|
@ -131,9 +131,14 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Containe
|
||||||
}
|
}
|
||||||
|
|
||||||
if ctr.config.NetMode.IsSlirp4netns() {
|
if ctr.config.NetMode.IsSlirp4netns() {
|
||||||
ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
|
if ctr.config.PostConfigureNetNS {
|
||||||
if err != nil {
|
ctr.rootlessSlirpSyncR, ctr.rootlessSlirpSyncW, err = os.Pipe()
|
||||||
return errors.Wrapf(err, "failed to create rootless network sync 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
|
// Leak one end in conmon, the other one will be leaked into slirp4netns
|
||||||
cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW)
|
cmd.ExtraFiles = append(cmd.ExtraFiles, ctr.rootlessSlirpSyncW)
|
||||||
|
|
|
||||||
|
|
@ -99,7 +99,9 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID
|
||||||
if isRootless {
|
if isRootless {
|
||||||
netmode = "slirp4netns"
|
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...)
|
return r.newContainer(ctx, g.Config, options...)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,23 +23,42 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"github.com/containernetworking/plugins/pkg/ns"
|
"github.com/containernetworking/plugins/pkg/ns"
|
||||||
|
"github.com/containers/libpod/pkg/rootless"
|
||||||
|
"github.com/containers/libpod/pkg/util"
|
||||||
"github.com/sirupsen/logrus"
|
"github.com/sirupsen/logrus"
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
|
|
||||||
const nsRunDir = "/var/run/netns"
|
// get NSRunDir returns the dir of where to create the netNS. When running
|
||||||
|
// rootless, it needs to be at a location writable by user.
|
||||||
|
func getNSRunDir() (string, error) {
|
||||||
|
if rootless.IsRootless() {
|
||||||
|
rootlessDir, err := util.GetRuntimeDir()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return filepath.Join(rootlessDir, "netns"), nil
|
||||||
|
}
|
||||||
|
return "/var/run/netns", nil
|
||||||
|
}
|
||||||
|
|
||||||
// NewNS creates a new persistent (bind-mounted) network namespace and returns
|
// NewNS creates a new persistent (bind-mounted) network namespace and returns
|
||||||
// an object representing that namespace, without switching to it.
|
// an object representing that namespace, without switching to it.
|
||||||
func NewNS() (ns.NetNS, error) {
|
func NewNS() (ns.NetNS, error) {
|
||||||
|
|
||||||
|
nsRunDir, err := getNSRunDir()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
b := make([]byte, 16)
|
b := make([]byte, 16)
|
||||||
_, err := rand.Reader.Read(b)
|
_, err = rand.Reader.Read(b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
|
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -127,7 +146,7 @@ func NewNS() (ns.NetNS, error) {
|
||||||
// Put this thread back to the orig ns, since it might get reused (pre go1.10)
|
// Put this thread back to the orig ns, since it might get reused (pre go1.10)
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := origNS.Set(); err != nil {
|
if err := origNS.Set(); err != nil {
|
||||||
logrus.Errorf("unable to set namespace: %q", err)
|
logrus.Warnf("unable to set namespace: %q", err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
|
@ -150,6 +169,11 @@ func NewNS() (ns.NetNS, error) {
|
||||||
|
|
||||||
// UnmountNS unmounts the NS held by the netns object
|
// UnmountNS unmounts the NS held by the netns object
|
||||||
func UnmountNS(ns ns.NetNS) error {
|
func UnmountNS(ns ns.NetNS) error {
|
||||||
|
nsRunDir, err := getNSRunDir()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
nsPath := ns.Path()
|
nsPath := ns.Path()
|
||||||
// Only unmount if it's been bind-mounted (don't touch namespaces in /proc...)
|
// Only unmount if it's been bind-mounted (don't touch namespaces in /proc...)
|
||||||
if strings.HasPrefix(nsPath, nsRunDir) {
|
if strings.HasPrefix(nsPath, nsRunDir) {
|
||||||
|
|
|
||||||
|
|
@ -275,7 +275,7 @@ func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *l
|
||||||
options = append(options, libpod.WithNetNSFrom(connectedCtr))
|
options = append(options, libpod.WithNetNSFrom(connectedCtr))
|
||||||
} else if !c.NetMode.IsHost() && !c.NetMode.IsNone() {
|
} else if !c.NetMode.IsHost() && !c.NetMode.IsNone() {
|
||||||
hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0
|
hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0
|
||||||
postConfigureNetNS := c.NetMode.IsSlirp4netns() || (hasUserns && !c.UsernsMode.IsHost())
|
postConfigureNetNS := hasUserns && !c.UsernsMode.IsHost()
|
||||||
options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks))
|
options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue