mirror of
https://github.com/containers/podman.git
synced 2025-07-01 08:07:03 +08:00
podman unshare: add --rootless-cni to join the ns
Add a new --rootless-cni option to podman unshare to also join the rootless-cni network namespace. This is useful if you want to connect to a rootless container via IP address. This is only possible from the rootless-cni namespace and not from the host namespace. This option also helps to debug problems in the rootless-cni namespace. Signed-off-by: Paul Holzinger <paul.holzinger@web.de>
This commit is contained in:
@ -12,9 +12,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
unshareOptions = entities.SystemUnshareOptions{}
|
||||||
unshareDescription = "Runs a command in a modified user namespace."
|
unshareDescription = "Runs a command in a modified user namespace."
|
||||||
unshareCommand = &cobra.Command{
|
unshareCommand = &cobra.Command{
|
||||||
Use: "unshare [COMMAND [ARG...]]",
|
Use: "unshare [options] [COMMAND [ARG...]]",
|
||||||
DisableFlagsInUseLine: true,
|
DisableFlagsInUseLine: true,
|
||||||
Short: "Run a command in a modified user namespace",
|
Short: "Run a command in a modified user namespace",
|
||||||
Long: unshareDescription,
|
Long: unshareDescription,
|
||||||
@ -33,6 +34,7 @@ func init() {
|
|||||||
})
|
})
|
||||||
flags := unshareCommand.Flags()
|
flags := unshareCommand.Flags()
|
||||||
flags.SetInterspersed(false)
|
flags.SetInterspersed(false)
|
||||||
|
flags.BoolVar(&unshareOptions.RootlessCNI, "rootless-cni", false, "Join the rootless network namespace used for CNI networking")
|
||||||
}
|
}
|
||||||
|
|
||||||
func unshare(cmd *cobra.Command, args []string) error {
|
func unshare(cmd *cobra.Command, args []string) error {
|
||||||
@ -49,5 +51,5 @@ func unshare(cmd *cobra.Command, args []string) error {
|
|||||||
args = []string{shell}
|
args = []string{shell}
|
||||||
}
|
}
|
||||||
|
|
||||||
return registry.ContainerEngine().Unshare(registry.Context(), args)
|
return registry.ContainerEngine().Unshare(registry.Context(), args, unshareOptions)
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,19 @@ The unshare session defines two environment variables:
|
|||||||
- **CONTAINERS_GRAPHROOT**: the path to the persistent container's data.
|
- **CONTAINERS_GRAPHROOT**: the path to the persistent container's data.
|
||||||
- **CONTAINERS_RUNROOT**: the path to the volatile container's data.
|
- **CONTAINERS_RUNROOT**: the path to the volatile container's data.
|
||||||
|
|
||||||
|
## OPTIONS
|
||||||
|
|
||||||
|
#### **\-\-help**, **-h**
|
||||||
|
|
||||||
|
Print usage statement
|
||||||
|
|
||||||
|
#### **\-\-rootless-cni**
|
||||||
|
|
||||||
|
Join the rootless network namespace used for CNI networking. It can be used to
|
||||||
|
connect to a rootless container via IP address (CNI networking). This is otherwise
|
||||||
|
not possible from the host network namespace.
|
||||||
|
_Note: Using this option with more than one unshare session can have unexpected results._
|
||||||
|
|
||||||
## EXAMPLE
|
## EXAMPLE
|
||||||
|
|
||||||
```
|
```
|
||||||
@ -35,6 +48,30 @@ $ podman unshare cat /proc/self/uid_map /proc/self/gid_map
|
|||||||
1 10000 65536
|
1 10000 65536
|
||||||
0 1000 1
|
0 1000 1
|
||||||
1 10000 65536
|
1 10000 65536
|
||||||
|
|
||||||
|
$ podman unshare --rootless-cni ip addr
|
||||||
|
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN group default qlen 1000
|
||||||
|
link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
|
||||||
|
inet 127.0.0.1/8 scope host lo
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
inet6 ::1/128 scope host
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
2: tap0: <BROADCAST,UP,LOWER_UP> mtu 65520 qdisc fq_codel state UNKNOWN group default qlen 1000
|
||||||
|
link/ether 36:0e:4a:c7:45:7e brd ff:ff:ff:ff:ff:ff
|
||||||
|
inet 10.0.2.100/24 brd 10.0.2.255 scope global tap0
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
inet6 fe80::340e:4aff:fec7:457e/64 scope link
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
3: cni-podman2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP group default qlen 1000
|
||||||
|
link/ether 5e:3a:71:d2:b4:3a brd ff:ff:ff:ff:ff:ff
|
||||||
|
inet 10.89.1.1/24 brd 10.89.1.255 scope global cni-podman2
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
inet6 fe80::5c3a:71ff:fed2:b43a/64 scope link
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
|
4: vethd4ba3a2f@if3: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue master cni-podman2 state UP group default
|
||||||
|
link/ether 8a:c9:56:32:17:0c brd ff:ff:ff:ff:ff:ff link-netnsid 0
|
||||||
|
inet6 fe80::88c9:56ff:fe32:170c/64 scope link
|
||||||
|
valid_lft forever preferred_lft forever
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
@ -105,13 +105,13 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port
|
|||||||
return ctrNetwork
|
return ctrNetwork
|
||||||
}
|
}
|
||||||
|
|
||||||
type rootlessCNI struct {
|
type RootlessCNI struct {
|
||||||
ns ns.NetNS
|
ns ns.NetNS
|
||||||
dir string
|
dir string
|
||||||
lock lockfile.Locker
|
lock lockfile.Locker
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *rootlessCNI) Do(toRun func() error) error {
|
func (r *RootlessCNI) Do(toRun func() error) error {
|
||||||
err := r.ns.Do(func(_ ns.NetNS) error {
|
err := r.ns.Do(func(_ ns.NetNS) error {
|
||||||
// before we can run the given function
|
// before we can run the given function
|
||||||
// we have to setup all mounts correctly
|
// we have to setup all mounts correctly
|
||||||
@ -174,9 +174,14 @@ func (r *rootlessCNI) Do(toRun func() error) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// cleanup the rootless cni namespace if needed
|
// Cleanup the rootless cni namespace if needed
|
||||||
// check if we have running containers with the bridge network mode
|
// check if we have running containers with the bridge network mode
|
||||||
func (r *rootlessCNI) cleanup(runtime *Runtime) error {
|
func (r *RootlessCNI) Cleanup(runtime *Runtime) error {
|
||||||
|
_, err := os.Stat(r.dir)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
// the directory does not exists no need for cleanup
|
||||||
|
return nil
|
||||||
|
}
|
||||||
r.lock.Lock()
|
r.lock.Lock()
|
||||||
defer r.lock.Unlock()
|
defer r.lock.Unlock()
|
||||||
running := func(c *Container) bool {
|
running := func(c *Container) bool {
|
||||||
@ -234,10 +239,10 @@ func (r *rootlessCNI) cleanup(runtime *Runtime) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getRootlessCNINetNs returns the rootless cni object. If create is set to true
|
// GetRootlessCNINetNs returns the rootless cni object. If create is set to true
|
||||||
// the rootless cni namespace will be created if it does not exists already.
|
// the rootless cni namespace will be created if it does not exists already.
|
||||||
func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) {
|
func (r *Runtime) GetRootlessCNINetNs(new bool) (*RootlessCNI, error) {
|
||||||
var rootlessCNINS *rootlessCNI
|
var rootlessCNINS *RootlessCNI
|
||||||
if rootless.IsRootless() {
|
if rootless.IsRootless() {
|
||||||
runDir, err := util.GetRuntimeDir()
|
runDir, err := util.GetRuntimeDir()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -421,7 +426,7 @@ func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) {
|
|||||||
os.Setenv("PATH", path)
|
os.Setenv("PATH", path)
|
||||||
}
|
}
|
||||||
|
|
||||||
rootlessCNINS = &rootlessCNI{
|
rootlessCNINS = &RootlessCNI{
|
||||||
ns: ns,
|
ns: ns,
|
||||||
dir: cniDir,
|
dir: cniDir,
|
||||||
lock: lock,
|
lock: lock,
|
||||||
@ -433,7 +438,7 @@ func (r *Runtime) getRootlessCNINetNs(new bool) (*rootlessCNI, error) {
|
|||||||
// setUpOCICNIPod will set up the cni networks, on error it will also tear down the cni
|
// setUpOCICNIPod will set up the cni networks, on error it will also tear down the cni
|
||||||
// networks. If rootless it will join/create the rootless cni namespace.
|
// networks. If rootless it will join/create the rootless cni namespace.
|
||||||
func (r *Runtime) setUpOCICNIPod(podNetwork ocicni.PodNetwork) ([]ocicni.NetResult, error) {
|
func (r *Runtime) setUpOCICNIPod(podNetwork ocicni.PodNetwork) ([]ocicni.NetResult, error) {
|
||||||
rootlessCNINS, err := r.getRootlessCNINetNs(true)
|
rootlessCNINS, err := r.GetRootlessCNINetNs(true)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -651,7 +656,7 @@ func (r *Runtime) closeNetNS(ctr *Container) error {
|
|||||||
// Tear down a container's CNI network configuration and joins the
|
// Tear down a container's CNI network configuration and joins the
|
||||||
// rootless net ns as rootless user
|
// rootless net ns as rootless user
|
||||||
func (r *Runtime) teardownOCICNIPod(podNetwork ocicni.PodNetwork) error {
|
func (r *Runtime) teardownOCICNIPod(podNetwork ocicni.PodNetwork) error {
|
||||||
rootlessCNINS, err := r.getRootlessCNINetNs(false)
|
rootlessCNINS, err := r.GetRootlessCNINetNs(false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -665,7 +670,7 @@ func (r *Runtime) teardownOCICNIPod(podNetwork ocicni.PodNetwork) error {
|
|||||||
// execute the cni setup in the rootless net ns
|
// execute the cni setup in the rootless net ns
|
||||||
err = rootlessCNINS.Do(tearDownPod)
|
err = rootlessCNINS.Do(tearDownPod)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = rootlessCNINS.cleanup(r)
|
err = rootlessCNINS.Cleanup(r)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
err = tearDownPod()
|
err = tearDownPod()
|
||||||
|
@ -88,7 +88,7 @@ type ContainerEngine interface {
|
|||||||
SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error)
|
SecretRm(ctx context.Context, nameOrID []string, opts SecretRmOptions) ([]*SecretRmReport, error)
|
||||||
Shutdown(ctx context.Context)
|
Shutdown(ctx context.Context)
|
||||||
SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error)
|
SystemDf(ctx context.Context, options SystemDfOptions) (*SystemDfReport, error)
|
||||||
Unshare(ctx context.Context, args []string) error
|
Unshare(ctx context.Context, args []string, options SystemUnshareOptions) error
|
||||||
Version(ctx context.Context) (*SystemVersionReport, error)
|
Version(ctx context.Context) (*SystemVersionReport, error)
|
||||||
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error)
|
VolumeCreate(ctx context.Context, opts VolumeCreateOptions) (*IDOrNameResponse, error)
|
||||||
VolumeExists(ctx context.Context, namesOrID string) (*BoolReport, error)
|
VolumeExists(ctx context.Context, namesOrID string) (*BoolReport, error)
|
||||||
|
@ -98,6 +98,11 @@ type SystemVersionReport struct {
|
|||||||
Server *define.Version `json:",omitempty"`
|
Server *define.Version `json:",omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SystemUnshareOptions describes the options for the unshare command
|
||||||
|
type SystemUnshareOptions struct {
|
||||||
|
RootlessCNI bool
|
||||||
|
}
|
||||||
|
|
||||||
type ComponentVersion struct {
|
type ComponentVersion struct {
|
||||||
types.Version
|
types.Version
|
||||||
}
|
}
|
||||||
|
@ -390,13 +390,25 @@ func unshareEnv(graphroot, runroot string) []string {
|
|||||||
fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot))
|
fmt.Sprintf("CONTAINERS_RUNROOT=%s", runroot))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error {
|
func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error {
|
||||||
cmd := exec.Command(args[0], args[1:]...)
|
unshare := func() error {
|
||||||
cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot)
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
cmd.Stdin = os.Stdin
|
cmd.Env = unshareEnv(ic.Libpod.StorageConfig().GraphRoot, ic.Libpod.StorageConfig().RunRoot)
|
||||||
cmd.Stdout = os.Stdout
|
cmd.Stdin = os.Stdin
|
||||||
cmd.Stderr = os.Stderr
|
cmd.Stdout = os.Stdout
|
||||||
return cmd.Run()
|
cmd.Stderr = os.Stderr
|
||||||
|
return cmd.Run()
|
||||||
|
}
|
||||||
|
|
||||||
|
if options.RootlessCNI {
|
||||||
|
rootlesscni, err := ic.Libpod.GetRootlessCNINetNs(true)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer rootlesscni.Cleanup(ic.Libpod)
|
||||||
|
return rootlesscni.Do(unshare)
|
||||||
|
}
|
||||||
|
return unshare()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) {
|
func (ic ContainerEngine) Version(ctx context.Context) (*entities.SystemVersionReport, error) {
|
||||||
|
@ -28,7 +28,7 @@ func (ic *ContainerEngine) SystemDf(ctx context.Context, options entities.System
|
|||||||
return system.DiskUsage(ic.ClientCtx, nil)
|
return system.DiskUsage(ic.ClientCtx, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (ic *ContainerEngine) Unshare(ctx context.Context, args []string) error {
|
func (ic *ContainerEngine) Unshare(ctx context.Context, args []string, options entities.SystemUnshareOptions) error {
|
||||||
return errors.New("unshare is not supported on remote clients")
|
return errors.New("unshare is not supported on remote clients")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -49,4 +49,11 @@ var _ = Describe("Podman unshare", func() {
|
|||||||
ok, _ := session.GrepString(userNS)
|
ok, _ := session.GrepString(userNS)
|
||||||
Expect(ok).To(BeFalse())
|
Expect(ok).To(BeFalse())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
It("podman unshare --rootles-cni", func() {
|
||||||
|
session := podmanTest.Podman([]string{"unshare", "--rootless-cni", "ip", "addr"})
|
||||||
|
session.WaitWithDefaultTimeout()
|
||||||
|
Expect(session.ExitCode()).To(Equal(0))
|
||||||
|
Expect(session.OutputToString()).To(ContainSubstring("tap0"))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Reference in New Issue
Block a user