diff options
31 files changed, 848 insertions, 232 deletions
diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 8a3f02036..62c9f4c9a 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -312,30 +312,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(logOptFlagName, AutocompleteLogOpt) - memoryReservationFlagName := "memory-reservation" - createFlags.StringVar( - &cf.MemoryReservation, - memoryReservationFlagName, "", - "Memory soft limit "+sizeWithUnitFormat, - ) - _ = cmd.RegisterFlagCompletionFunc(memoryReservationFlagName, completion.AutocompleteNone) - - memorySwapFlagName := "memory-swap" - createFlags.StringVar( - &cf.MemorySwap, - memorySwapFlagName, "", - "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", - ) - _ = cmd.RegisterFlagCompletionFunc(memorySwapFlagName, completion.AutocompleteNone) - - memorySwappinessFlagName := "memory-swappiness" - createFlags.Int64Var( - &cf.MemorySwappiness, - memorySwappinessFlagName, -1, - "Tune container memory swappiness (0 to 100, or -1 for system default)", - ) - _ = cmd.RegisterFlagCompletionFunc(memorySwappinessFlagName, completion.AutocompleteNone) - createFlags.BoolVar( &cf.NoHealthCheck, "no-healthcheck", false, @@ -630,6 +606,10 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(chrootDirsFlagName, completion.AutocompleteDefault) + passwdEntryName := "passwd-entry" + createFlags.StringVar(&cf.PasswdEntry, passwdEntryName, "", "Entry to write to /etc/passwd") + _ = cmd.RegisterFlagCompletionFunc(passwdEntryName, completion.AutocompleteNone) + if registry.IsRemote() { _ = createFlags.MarkHidden("env-host") _ = createFlags.MarkHidden("http-proxy") @@ -891,6 +871,30 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, "Memory limit "+sizeWithUnitFormat, ) _ = cmd.RegisterFlagCompletionFunc(memoryFlagName, completion.AutocompleteNone) + + memoryReservationFlagName := "memory-reservation" + createFlags.StringVar( + &cf.MemoryReservation, + memoryReservationFlagName, "", + "Memory soft limit "+sizeWithUnitFormat, + ) + _ = cmd.RegisterFlagCompletionFunc(memoryReservationFlagName, completion.AutocompleteNone) + + memorySwapFlagName := "memory-swap" + createFlags.StringVar( + &cf.MemorySwap, + memorySwapFlagName, "", + "Swap limit equal to memory plus swap: '-1' to enable unlimited swap", + ) + _ = cmd.RegisterFlagCompletionFunc(memorySwapFlagName, completion.AutocompleteNone) + + memorySwappinessFlagName := "memory-swappiness" + createFlags.Int64Var( + &cf.MemorySwappiness, + memorySwappinessFlagName, -1, + "Tune container memory swappiness (0 to 100, or -1 for system default)", + ) + _ = cmd.RegisterFlagCompletionFunc(memorySwappinessFlagName, completion.AutocompleteNone) } //anyone can use these cpusFlagName := "cpus" diff --git a/cmd/podman/machine/init.go b/cmd/podman/machine/init.go index 518e7490f..2d0afbf05 100644 --- a/cmd/podman/machine/init.go +++ b/cmd/podman/machine/init.go @@ -5,6 +5,7 @@ package machine import ( "fmt" + "os" "github.com/containers/common/pkg/completion" "github.com/containers/podman/v4/cmd/podman/registry" @@ -94,7 +95,7 @@ func init() { _ = initCmd.RegisterFlagCompletionFunc(ImagePathFlagName, completion.AutocompleteDefault) VolumeFlagName := "volume" - flags.StringArrayVarP(&initOpts.Volumes, VolumeFlagName, "v", []string{}, "Volumes to mount, source:target") + flags.StringArrayVarP(&initOpts.Volumes, VolumeFlagName, "v", cfg.Machine.Volumes, "Volumes to mount, source:target") _ = initCmd.RegisterFlagCompletionFunc(VolumeFlagName, completion.AutocompleteDefault) VolumeDriverFlagName := "volume-driver" @@ -112,9 +113,10 @@ func init() { // TODO should we allow for a users to append to the qemu cmdline? func initMachine(cmd *cobra.Command, args []string) error { var ( - vm machine.VM err error + vm machine.VM ) + provider := getSystemDefaultProvider() initOpts.Name = defaultMachineName if len(args) > 0 { @@ -126,7 +128,9 @@ func initMachine(cmd *cobra.Command, args []string) error { if _, err := provider.LoadVMByName(initOpts.Name); err == nil { return errors.Wrap(machine.ErrVMAlreadyExists, initOpts.Name) } - + for idx, vol := range initOpts.Volumes { + initOpts.Volumes[idx] = os.ExpandEnv(vol) + } vm, err = provider.NewMachine(initOpts) if err != nil { return err diff --git a/docs/source/markdown/podman-container-clone.1.md b/docs/source/markdown/podman-container-clone.1.md index 6c23abe81..7d5e1c262 100644 --- a/docs/source/markdown/podman-container-clone.1.md +++ b/docs/source/markdown/podman-container-clone.1.md @@ -137,6 +137,33 @@ system's page size (the value would be very large, that's millions of trillions) If no memory limits are specified, the original container's will be used. +#### **--memory-reservation**=*limit* + +Memory soft limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes)) + +After setting memory reservation, when the system detects memory contention +or low memory, containers are forced to restrict their consumption to their +reservation. So you should always set the value below **--memory**, otherwise the +hard limit will take precedence. By default, memory reservation will be the same +as memory limit from the container being cloned. + +#### **--memory-swap**=*limit* + +A limit value equal to memory plus swap. Must be used with the **-m** +(**--memory**) flag. The swap `LIMIT` should always be larger than **-m** +(**--memory**) value. By default, the swap `LIMIT` will be set to double +the value of --memory if specified. Otherwise, the container being cloned will be used to derive the swap value. + +The format of `LIMIT` is `<number>[<unit>]`. Unit can be `b` (bytes), +`k` (kilobytes), `m` (megabytes), or `g` (gigabytes). If you don't specify a +unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. + +#### **--memory-swappiness**=*number* + +Tune a container's memory swappiness behavior. Accepts an integer between 0 and 100. + +This flag is not supported on cgroups V2 systems. + #### **--name** Set a custom name for the cloned container. The default if not specified is of the syntax: **<ORIGINAL_NAME>-clone** diff --git a/docs/source/markdown/podman-container-inspect.1.md b/docs/source/markdown/podman-container-inspect.1.md index 9945fca7c..4e45bcc40 100644 --- a/docs/source/markdown/podman-container-inspect.1.md +++ b/docs/source/markdown/podman-container-inspect.1.md @@ -219,7 +219,7 @@ $ podman container inspect foobar "DnsSearch": [], "ExtraHosts": [], "GroupAdd": [], - "IpcMode": "private", + "IpcMode": "shareable", "Cgroup": "", "Cgroups": "default", "Links": null, diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index c4d27e321..45d0d0b3e 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -504,10 +504,16 @@ To specify multiple static IPv6 addresses per container, set multiple networks u #### **--ipc**=*ipc* -Default is to create a private IPC namespace (POSIX SysV IPC) for the container - `container:<name|id>`: reuses another container shared memory, semaphores and message queues - `host`: use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure. - `ns:<path>` path to an IPC namespace to join. +Set the IPC namespace mode for a container. The default is to create +a private IPC namespace. + +- "": Use Podman's default, defined in containers.conf. +- **container:**_id_: reuses another container's shared memory, semaphores, and message queues +- **host**: use the host's shared memory, semaphores, and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure. +- **none**: private IPC namespace, with /dev/shm not mounted. +- **ns:**_path_: path to an IPC namespace to join. +- **private**: private IPC namespace. += **shareable**: private IPC namespace with a possibility to share it with other containers. #### **--label**, **-l**=*label* @@ -755,6 +761,12 @@ Tune the host's OOM preferences for containers (accepts -1000 to 1000) #### **--os**=*OS* Override the OS, defaults to hosts, of the image to be pulled. For example, `windows`. +#### **--passwd-entry**=*ENTRY* + +Customize the entry that is written to the `/etc/passwd` file within the container when `--passwd` is used. + +The variables $USERNAME, $UID, $GID, $NAME, $HOME are automatically replaced with their value at runtime. + #### **--personality**=*persona* Personality sets the execution domain via Linux personality(2). diff --git a/docs/source/markdown/podman-machine-init.1.md b/docs/source/markdown/podman-machine-init.1.md index ac258eaae..33947bbba 100644 --- a/docs/source/markdown/podman-machine-init.1.md +++ b/docs/source/markdown/podman-machine-init.1.md @@ -83,6 +83,9 @@ Podman mounts _host-dir_ in the host to _machine-dir_ in the Podman machine. The root filesystem is mounted read-only in the default operating system, so mounts must be created under the /mnt directory. +Default volume mounts are defined in *containers.conf*. Unless changed, the default values +is `$HOME:$HOME`. + #### **--volume-driver** Driver to use for mounting volumes from the host, such as `virtfs`. diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index e4ccd0368..5c276c04a 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -528,9 +528,13 @@ To specify multiple static IPv6 addresses per container, set multiple networks u Set the IPC namespace mode for a container. The default is to create a private IPC namespace. +- "": Use Podman's default, defined in containers.conf. - **container:**_id_: reuses another container shared memory, semaphores and message queues - **host**: use the host shared memory,semaphores and message queues inside the container. Note: the host mode gives the container full access to local shared memory and is therefore considered insecure. +- **none**: private IPC namespace, with /dev/shm not mounted. - **ns:**_path_: path to an IPC namespace to join. +- **private**: private IPC namespace. += **shareable**: private IPC namespace with a possibility to share it with other containers. #### **--label**, **-l**=*key*=*value* @@ -787,6 +791,12 @@ Override the OS, defaults to hosts, of the image to be pulled. For example, `win Allow Podman to add entries to /etc/passwd and /etc/group when used in conjunction with the --user option. This is used to override the Podman provided user setup in favor of entrypoint configurations such as libnss-extrausers. +#### **--passwd-entry**=*ENTRY* + +Customize the entry that is written to the `/etc/passwd` file within the container when `--passwd` is used. + +The variables $USERNAME, $UID, $GID, $NAME, $HOME are automatically replaced with their value at runtime. + #### **--personality**=*persona* Personality sets the execution domain via Linux personality(2). diff --git a/libpod/container.go b/libpod/container.go index 578f16905..bc3cab439 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -291,6 +291,13 @@ func (c *Container) Config() *ContainerConfig { return returnConfig } +// ConfigNoCopy returns the configuration used by the container. +// Note that the returned value is not a copy and must hence +// only be used in a reading fashion. +func (c *Container) ConfigNoCopy() *ContainerConfig { + return c.config +} + // DeviceHostSrc returns the user supplied device to be passed down in the pod func (c *Container) DeviceHostSrc() []spec.LinuxDevice { return c.config.DeviceHostSrc diff --git a/libpod/container_config.go b/libpod/container_config.go index ea644764c..371a1dec0 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -120,6 +120,10 @@ type ContainerRootFSConfig struct { // with the size specified in ShmSize and populate this with the path of // said tmpfs. ShmDir string `json:"ShmDir,omitempty"` + // NoShmShare indicates whether /dev/shm can be shared with other containers + NoShmShare bool `json:"NOShmShare,omitempty"` + // NoShm indicates whether a tmpfs should be created and mounted on /dev/shm + NoShm bool `json:"NoShm,omitempty"` // ShmSize is the size of the container's SHM. Only used if ShmDir was // not set manually at time of creation. ShmSize int64 `json:"shmSize"` @@ -404,6 +408,8 @@ type ContainerMiscConfig struct { // InitContainerType specifies if the container is an initcontainer // and if so, what type: always or once are possible non-nil entries InitContainerType string `json:"init_container_type,omitempty"` + // PasswdEntry specifies arbitrary data to append to a file. + PasswdEntry string `json:"passwd_entry,omitempty"` } // InfraInherit contains the compatible options inheritable from the infra container diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go index c9d0b8a6c..14290ca0d 100644 --- a/libpod/container_inspect.go +++ b/libpod/container_inspect.go @@ -703,32 +703,31 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named } hostConfig.CapAdd = capAdd hostConfig.CapDrop = capDrop - - // IPC Namespace mode - ipcMode := "" - if c.config.IPCNsCtr != "" { - ipcMode = fmt.Sprintf("container:%s", c.config.IPCNsCtr) - } else if ctrSpec.Linux != nil { + switch { + case c.config.IPCNsCtr != "": + hostConfig.IpcMode = fmt.Sprintf("container:%s", c.config.IPCNsCtr) + case ctrSpec.Linux != nil: // Locate the spec's IPC namespace. // If there is none, it's ipc=host. // If there is one and it has a path, it's "ns:". // If no path, it's default - the empty string. - for _, ns := range ctrSpec.Linux.Namespaces { if ns.Type == spec.IPCNamespace { if ns.Path != "" { - ipcMode = fmt.Sprintf("ns:%s", ns.Path) + hostConfig.IpcMode = fmt.Sprintf("ns:%s", ns.Path) } else { - ipcMode = "private" + break } - break } } - if ipcMode == "" { - ipcMode = "host" - } + case c.config.NoShm: + hostConfig.IpcMode = "none" + case c.config.NoShmShare: + hostConfig.IpcMode = "private" + } + if hostConfig.IpcMode == "" { + hostConfig.IpcMode = "shareable" } - hostConfig.IpcMode = ipcMode // Cgroup namespace mode cgroupMode := "" diff --git a/libpod/container_internal.go b/libpod/container_internal.go index f1f467879..c7567a55e 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -1507,26 +1507,28 @@ func (c *Container) mountStorage() (_ string, deferredErr error) { return c.state.Mountpoint, nil } - mounted, err := mount.Mounted(c.config.ShmDir) - if err != nil { - return "", errors.Wrapf(err, "unable to determine if %q is mounted", c.config.ShmDir) - } - - if !mounted && !MountExists(c.config.Spec.Mounts, "/dev/shm") { - shmOptions := fmt.Sprintf("mode=1777,size=%d", c.config.ShmSize) - if err := c.mountSHM(shmOptions); err != nil { - return "", err - } - if err := os.Chown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil { - return "", errors.Wrapf(err, "failed to chown %s", c.config.ShmDir) + if !c.config.NoShm { + mounted, err := mount.Mounted(c.config.ShmDir) + if err != nil { + return "", errors.Wrapf(err, "unable to determine if %q is mounted", c.config.ShmDir) } - defer func() { - if deferredErr != nil { - if err := c.unmountSHM(c.config.ShmDir); err != nil { - logrus.Errorf("Unmounting SHM for container %s after mount error: %v", c.ID(), err) - } + + if !mounted && !MountExists(c.config.Spec.Mounts, "/dev/shm") { + shmOptions := fmt.Sprintf("mode=1777,size=%d", c.config.ShmSize) + if err := c.mountSHM(shmOptions); err != nil { + return "", err } - }() + if err := os.Chown(c.config.ShmDir, c.RootUID(), c.RootGID()); err != nil { + return "", errors.Wrapf(err, "failed to chown %s", c.config.ShmDir) + } + defer func() { + if deferredErr != nil { + if err := c.unmountSHM(c.config.ShmDir); err != nil { + logrus.Errorf("Unmounting SHM for container %s after mount error: %v", c.ID(), err) + } + } + }() + } } // We need to mount the container before volumes - to ensure the copyup diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 11ca169ca..9991003d6 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -1963,8 +1963,10 @@ func (c *Container) makeBindMounts() error { } } - // SHM is always added when we mount the container - c.state.BindMounts["/dev/shm"] = c.config.ShmDir + if c.config.ShmDir != "" { + // If ShmDir has a value SHM is always added when we mount the container + c.state.BindMounts["/dev/shm"] = c.config.ShmDir + } if c.config.Passwd == nil || *c.config.Passwd { newPasswd, newGroup, err := c.generatePasswdAndGroup() @@ -2724,6 +2726,9 @@ func (c *Container) userPasswdEntry(u *user.User) (string, error) { if !hasHomeSet { c.config.Spec.Process.Env = append(c.config.Spec.Process.Env, fmt.Sprintf("HOME=%s", homeDir)) } + if c.config.PasswdEntry != "" { + return c.passwdEntry(u.Username, u.Uid, u.Gid, u.Name, homeDir), nil + } return fmt.Sprintf("%s:*:%s:%s:%s:%s:/bin/sh\n", u.Username, u.Uid, u.Gid, u.Name, homeDir), nil } @@ -2775,9 +2780,25 @@ func (c *Container) generateUserPasswdEntry(addedUID int) (string, int, int, err gid = group.Gid } } + + if c.config.PasswdEntry != "" { + entry := c.passwdEntry(fmt.Sprintf("%d", uid), fmt.Sprintf("%d", uid), fmt.Sprintf("%d", gid), "container user", c.WorkingDir()) + return entry, int(uid), gid, nil + } + return fmt.Sprintf("%d:*:%d:%d:container user:%s:/bin/sh\n", uid, uid, gid, c.WorkingDir()), int(uid), gid, nil } +func (c *Container) passwdEntry(username string, uid, gid, name, homeDir string) string { + s := c.config.PasswdEntry + s = strings.Replace(s, "$USERNAME", username, -1) + s = strings.Replace(s, "$UID", uid, -1) + s = strings.Replace(s, "$GID", gid, -1) + s = strings.Replace(s, "$NAME", name, -1) + s = strings.Replace(s, "$HOME", homeDir, -1) + return s + "\n" +} + // generatePasswdAndGroup generates container-specific passwd and group files // iff g.config.User is a number or we are configured to make a passwd entry for // the current user or the user specified HostsUsers diff --git a/libpod/events.go b/libpod/events.go index d6595180a..3908536a1 100644 --- a/libpod/events.go +++ b/libpod/events.go @@ -13,8 +13,9 @@ import ( // newEventer returns an eventer that can be used to read/write events func (r *Runtime) newEventer() (events.Eventer, error) { options := events.EventerOptions{ - EventerType: r.config.Engine.EventsLogger, - LogFilePath: r.config.Engine.EventsLogFilePath, + EventerType: r.config.Engine.EventsLogger, + LogFilePath: r.config.Engine.EventsLogFilePath, + LogFileMaxSize: r.config.Engine.EventsLogFileMaxSize, } return events.NewEventer(options) } diff --git a/libpod/events/config.go b/libpod/events/config.go index 188d15578..35680a275 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -60,6 +60,8 @@ type EventerOptions struct { // LogFilePath is the path to where the log file should reside if using // the file logger LogFilePath string + // LogFileMaxSize is the default limit used for rotating the log file + LogFileMaxSize uint64 } // Eventer is the interface for journald or file event logging @@ -171,6 +173,8 @@ const ( Restart Status = "restart" // Restore ... Restore Status = "restore" + // Rotate indicates that the log file was rotated + Rotate Status = "log-rotation" // Save ... Save Status = "save" // Start ... diff --git a/libpod/events/events.go b/libpod/events/events.go index 2cdd2ab67..1745095fb 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -3,11 +3,9 @@ package events import ( "encoding/json" "fmt" - "os" "time" "github.com/containers/storage/pkg/stringid" - "github.com/nxadm/tail" "github.com/pkg/errors" ) @@ -87,7 +85,11 @@ func (e *Event) ToHumanReadable(truncate bool) string { case Image: humanFormat = fmt.Sprintf("%s %s %s %s %s", e.Time, e.Type, e.Status, id, e.Name) case System: - humanFormat = fmt.Sprintf("%s %s %s", e.Time, e.Type, e.Status) + if e.Name != "" { + humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name) + } else { + humanFormat = fmt.Sprintf("%s %s %s", e.Time, e.Type, e.Status) + } case Volume: humanFormat = fmt.Sprintf("%s %s %s %s", e.Time, e.Type, e.Status, e.Name) } @@ -196,6 +198,8 @@ func StringToStatus(name string) (Status, error) { return Restart, nil case Restore.String(): return Restore, nil + case Rotate.String(): + return Rotate, nil case Save.String(): return Save, nil case Start.String(): @@ -215,14 +219,3 @@ func StringToStatus(name string) (Status, error) { } return "", errors.Errorf("unknown event status %q", name) } - -func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) { - reopen := true - seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} - if options.FromStart || !options.Stream { - seek.Whence = 0 - reopen = false - } - stream := options.Stream - return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger, Poll: true}) -} diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go index 76173cde9..5091f3723 100644 --- a/libpod/events/logfile.go +++ b/libpod/events/logfile.go @@ -1,15 +1,24 @@ +//go:build linux +// +build linux + package events import ( + "bufio" "context" "fmt" + "io" + "io/ioutil" "os" + "path" "time" "github.com/containers/podman/v4/pkg/util" "github.com/containers/storage/pkg/lockfile" + "github.com/nxadm/tail" "github.com/pkg/errors" "github.com/sirupsen/logrus" + "golang.org/x/sys/unix" ) // EventLogFile is the structure for event writing to a logfile. It contains the eventer @@ -27,21 +36,55 @@ func (e EventLogFile) Write(ee Event) error { } lock.Lock() defer lock.Unlock() - f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700) + + eventJSONString, err := ee.ToJSONString() if err != nil { return err } - defer f.Close() - eventJSONString, err := ee.ToJSONString() + + rotated, err := rotateLog(e.options.LogFilePath, eventJSONString, e.options.LogFileMaxSize) + if err != nil { + return fmt.Errorf("rotating log file: %w", err) + } + + if rotated { + rEvent := NewEvent(Rotate) + rEvent.Type = System + rEvent.Name = e.options.LogFilePath + rotateJSONString, err := rEvent.ToJSONString() + if err != nil { + return err + } + if err := e.writeString(rotateJSONString); err != nil { + return err + } + } + + return e.writeString(eventJSONString) +} + +func (e EventLogFile) writeString(s string) error { + f, err := os.OpenFile(e.options.LogFilePath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0700) if err != nil { return err } - if _, err := f.WriteString(fmt.Sprintf("%s\n", eventJSONString)); err != nil { + if _, err := f.WriteString(s + "\n"); err != nil { return err } return nil } +func (e EventLogFile) getTail(options ReadOptions) (*tail.Tail, error) { + reopen := true + seek := tail.SeekInfo{Offset: 0, Whence: os.SEEK_END} + if options.FromStart || !options.Stream { + seek.Whence = 0 + reopen = false + } + stream := options.Stream + return tail.TailFile(e.options.LogFilePath, tail.Config{ReOpen: reopen, Follow: stream, Location: &seek, Logger: tail.DiscardingLogger, Poll: true}) +} + // Reads from the log file func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error { defer close(options.EventChannel) @@ -107,3 +150,108 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error { func (e EventLogFile) String() string { return LogFile.String() } + +// Rotates the log file if the log file size and content exceeds limit +func rotateLog(logfile string, content string, limit uint64) (bool, error) { + if limit == 0 { + return false, nil + } + file, err := os.Stat(logfile) + if err != nil { + return false, err + } + var filesize = uint64(file.Size()) + var contentsize = uint64(len([]rune(content))) + if filesize+contentsize < limit { + return false, nil + } + + if err := truncate(logfile); err != nil { + return false, err + } + return true, nil +} + +// Truncates the log file and saves 50% of content to new log file +func truncate(filePath string) error { + orig, err := os.Open(filePath) + if err != nil { + return err + } + defer orig.Close() + + origFinfo, err := orig.Stat() + if err != nil { + return err + } + + size := origFinfo.Size() + threshold := size / 2 + + tmp, err := ioutil.TempFile(path.Dir(filePath), "") + if err != nil { + // Retry in /tmp in case creating a tmp file in the same + // directory has failed. + tmp, err = ioutil.TempFile("", "") + if err != nil { + return err + } + } + defer tmp.Close() + + // Jump directly to the threshold, drop the first line and copy the remainder + if _, err := orig.Seek(threshold, 0); err != nil { + return err + } + reader := bufio.NewReader(orig) + if _, err := reader.ReadString('\n'); err != nil { + if !errors.Is(err, io.EOF) { + return err + } + } + if _, err := reader.WriteTo(tmp); err != nil { + return fmt.Errorf("writing truncated contents: %w", err) + } + + if err := renameLog(tmp.Name(), filePath); err != nil { + return fmt.Errorf("writing back %s to %s: %w", tmp.Name(), filePath, err) + } + + return nil +} + +// Renames from, to +func renameLog(from, to string) error { + err := os.Rename(from, to) + if err == nil { + return nil + } + + if !errors.Is(err, unix.EXDEV) { + return err + } + + // Files are not on the same partition, so we need to copy them the + // hard way. + fFrom, err := os.Open(from) + if err != nil { + return err + } + defer fFrom.Close() + + fTo, err := os.Create(to) + if err != nil { + return err + } + defer fTo.Close() + + if _, err := io.Copy(fTo, fFrom); err != nil { + return fmt.Errorf("writing back from temporary file: %w", err) + } + + if err := os.Remove(from); err != nil { + return fmt.Errorf("removing temporary file: %w", err) + } + + return nil +} diff --git a/libpod/events/logfile_test.go b/libpod/events/logfile_test.go new file mode 100644 index 000000000..302533c12 --- /dev/null +++ b/libpod/events/logfile_test.go @@ -0,0 +1,140 @@ +package events + +import ( + "io/ioutil" + "os" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestRotateLog(t *testing.T) { + tests := []struct { + // If sizeInitial + sizeContent >= sizeLimit, then rotate + sizeInitial uint64 + sizeContent uint64 + sizeLimit uint64 + mustRotate bool + }{ + // No rotation + {0, 0, 1, false}, + {1, 1, 0, false}, + {10, 10, 30, false}, + {1000, 500, 1600, false}, + // Rotation + {10, 10, 20, true}, + {30, 0, 29, true}, + {200, 50, 150, true}, + {1000, 600, 1500, true}, + } + + for _, test := range tests { + tmp, err := ioutil.TempFile("", "log-rotation-") + require.NoError(t, err) + defer os.Remove(tmp.Name()) + defer tmp.Close() + + // Create dummy file and content. + initialContent := make([]byte, test.sizeInitial) + logContent := make([]byte, test.sizeContent) + + // Write content to the file. + _, err = tmp.Write(initialContent) + require.NoError(t, err) + + // Now rotate + fInfoBeforeRotate, err := tmp.Stat() + require.NoError(t, err) + isRotated, err := rotateLog(tmp.Name(), string(logContent), test.sizeLimit) + require.NoError(t, err) + + fInfoAfterRotate, err := os.Stat(tmp.Name()) + // Test if rotation was successful + if test.mustRotate { + // File has been renamed + require.True(t, isRotated) + require.NoError(t, err, "log file has been renamed") + require.NotEqual(t, fInfoBeforeRotate.Size(), fInfoAfterRotate.Size()) + } else { + // File has not been renamed + require.False(t, isRotated) + require.NoError(t, err, "log file has not been renamed") + require.Equal(t, fInfoBeforeRotate.Size(), fInfoAfterRotate.Size()) + } + } +} + +func TestTruncationOutput(t *testing.T) { + contentBefore := `0 +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +` + contentAfter := `6 +7 +8 +9 +10 +` + // Create dummy file + tmp, err := ioutil.TempFile("", "log-rotation") + require.NoError(t, err) + defer os.Remove(tmp.Name()) + defer tmp.Close() + + // Write content before truncation to dummy file + _, err = tmp.WriteString(contentBefore) + require.NoError(t, err) + + // Truncate the file + beforeTruncation, err := ioutil.ReadFile(tmp.Name()) + require.NoError(t, err) + err = truncate(tmp.Name()) + require.NoError(t, err) + afterTruncation, err := ioutil.ReadFile(tmp.Name()) + require.NoError(t, err) + + // Test if rotation was successful + require.NoError(t, err, "Log content has changed") + require.NotEqual(t, beforeTruncation, afterTruncation) + require.Equal(t, string(afterTruncation), contentAfter) +} + +func TestRenameLog(t *testing.T) { + fileContent := `0 +1 +2 +3 +4 +5 +` + // Create two dummy files + source, err := ioutil.TempFile("", "removing") + require.NoError(t, err) + target, err := ioutil.TempFile("", "renaming") + require.NoError(t, err) + + // Write to source dummy file + _, err = source.WriteString(fileContent) + require.NoError(t, err) + + // Rename the files + beforeRename, err := ioutil.ReadFile(source.Name()) + require.NoError(t, err) + err = renameLog(source.Name(), target.Name()) + require.NoError(t, err) + afterRename, err := ioutil.ReadFile(target.Name()) + require.NoError(t, err) + + // Test if renaming was successful + require.Error(t, os.Remove(source.Name())) + require.NoError(t, os.Remove(target.Name())) + require.Equal(t, beforeRename, afterRename) +} diff --git a/libpod/options.go b/libpod/options.go index 2e5454393..ffd0e6037 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -559,6 +559,30 @@ func WithShmDir(dir string) CtrCreateOption { } } +// WithNOShmMount tells libpod whether to mount /dev/shm +func WithNoShm(mount bool) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.NoShm = mount + return nil + } +} + +// WithNoShmShare tells libpod whether to share containers /dev/shm with other containers +func WithNoShmShare(share bool) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.NoShmShare = share + return nil + } +} + // WithSystemd turns on systemd mode in the container func WithSystemd() CtrCreateOption { return func(ctr *Container) error { @@ -2051,3 +2075,16 @@ func WithChrootDirs(dirs []string) CtrCreateOption { return nil } } + +// WithPasswdEntry sets the entry to write to the /etc/passwd file. +func WithPasswdEntry(passwdEntry string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.PasswdEntry = passwdEntry + + return nil + } +} diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index f92898b1c..7edd49fd1 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -174,6 +174,8 @@ func (r *Runtime) initContainerVariables(rSpec *spec.Spec, config *ContainerConf return nil, errors.Wrapf(err, "converting containers.conf ShmSize %s to an int", r.config.Containers.ShmSize) } ctr.config.ShmSize = size + ctr.config.NoShm = false + ctr.config.NoShmShare = false ctr.config.StopSignal = 15 ctr.config.StopTimeout = r.config.Engine.StopTimeout @@ -514,7 +516,7 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai } } - if !MountExists(ctr.config.Spec.Mounts, "/dev/shm") && ctr.config.ShmDir == "" { + if !MountExists(ctr.config.Spec.Mounts, "/dev/shm") && ctr.config.ShmDir == "" && !ctr.config.NoShm { ctr.config.ShmDir = filepath.Join(ctr.bundlePath(), "shm") if err := os.MkdirAll(ctr.config.ShmDir, 0700); err != nil { if !os.IsExist(err) { diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index f1d445c4b..1e25e0872 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -272,6 +272,8 @@ type ContainerCreateOptions struct { Net *NetOptions `json:"net,omitempty"` CgroupConf []string + + PasswdEntry string } func NewInfraContainerCreateOptions() ContainerCreateOptions { diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index b38b0e695..f7ea2edfa 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -428,9 +428,12 @@ func ConfigToSpec(rt *libpod.Runtime, specg *specgen.SpecGenerator, contaierID s case "cgroup": specg.CgroupNS = specgen.Namespace{NSMode: specgen.Default} //default case "ipc": - if conf.ShmDir == "/dev/shm" { + switch conf.ShmDir { + case "/dev/shm": specg.IpcNS = specgen.Namespace{NSMode: specgen.Host} - } else { + case "": + specg.IpcNS = specgen.Namespace{NSMode: specgen.None} + default: specg.IpcNS = specgen.Namespace{NSMode: specgen.Default} //default } case "uts": diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index 6a611e854..5667a02e8 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -8,7 +8,6 @@ import ( cdi "github.com/container-orchestrated-devices/container-device-interface/pkg/cdi" "github.com/containers/common/libimage" - "github.com/containers/common/pkg/cgroups" "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" "github.com/containers/podman/v4/pkg/namespaces" @@ -184,32 +183,19 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener if err != nil { return nil, nil, nil, err } - - switch { - case s.ResourceLimits.CPU != nil: - runtimeSpec.Linux.Resources.CPU = s.ResourceLimits.CPU - case s.ResourceLimits.Memory != nil: - runtimeSpec.Linux.Resources.Memory = s.ResourceLimits.Memory - case s.ResourceLimits.BlockIO != nil: - runtimeSpec.Linux.Resources.BlockIO = s.ResourceLimits.BlockIO - case s.ResourceLimits.Devices != nil: - runtimeSpec.Linux.Resources.Devices = s.ResourceLimits.Devices - } - - cgroup2, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return nil, nil, nil, err - } - if cgroup2 && s.ResourceLimits.Memory != nil && s.ResourceLimits.Memory.Swappiness != nil { // conf.Spec.Linux contains memory swappiness established after the spec process we need to remove that - s.ResourceLimits.Memory.Swappiness = nil - if runtimeSpec.Linux.Resources.Memory != nil { - runtimeSpec.Linux.Resources.Memory.Swappiness = nil + if s.ResourceLimits != nil { + switch { + case s.ResourceLimits.CPU != nil: + runtimeSpec.Linux.Resources.CPU = s.ResourceLimits.CPU + case s.ResourceLimits.Memory != nil: + runtimeSpec.Linux.Resources.Memory = s.ResourceLimits.Memory + case s.ResourceLimits.BlockIO != nil: + runtimeSpec.Linux.Resources.BlockIO = s.ResourceLimits.BlockIO + case s.ResourceLimits.Devices != nil: + runtimeSpec.Linux.Resources.Devices = s.ResourceLimits.Devices } } } - if err != nil { - return nil, nil, nil, err - } if len(s.HostDeviceList) > 0 { options = append(options, libpod.WithHostDevice(s.HostDeviceList)) } @@ -286,6 +272,9 @@ func createContainerOptions(ctx context.Context, rt *libpod.Runtime, s *specgen. if s.Volatile { options = append(options, libpod.WithVolatile()) } + if s.PasswdEntry != "" { + options = append(options, libpod.WithPasswdEntry(s.PasswdEntry)) + } useSystemd := false switch s.Systemd { diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go index 9ce45aaf0..05c2d1741 100644 --- a/pkg/specgen/generate/namespaces.go +++ b/pkg/specgen/generate/namespaces.go @@ -134,8 +134,17 @@ func namespaceOptions(ctx context.Context, s *specgen.SpecGenerator, rt *libpod. if err != nil { return nil, errors.Wrapf(err, "error looking up container to share ipc namespace with") } + if ipcCtr.ConfigNoCopy().NoShmShare { + return nil, errors.Errorf("joining IPC of container %s is not allowed: non-shareable IPC (hint: use IpcMode:shareable for the donor container)", ipcCtr.ID()) + } toReturn = append(toReturn, libpod.WithIPCNSFrom(ipcCtr)) - toReturn = append(toReturn, libpod.WithShmDir(ipcCtr.ShmDir())) + if !ipcCtr.ConfigNoCopy().NoShm { + toReturn = append(toReturn, libpod.WithShmDir(ipcCtr.ShmDir())) + } + case specgen.None: + toReturn = append(toReturn, libpod.WithNoShm(true)) + case specgen.Private: + toReturn = append(toReturn, libpod.WithNoShmShare(true)) } // UTS diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go index 988c29832..ec52164ab 100644 --- a/pkg/specgen/generate/security.go +++ b/pkg/specgen/generate/security.go @@ -222,7 +222,7 @@ func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, for sysctlKey, sysctlVal := range defaultSysctls { // Ignore mqueue sysctls if --ipc=host if noUseIPC && strings.HasPrefix(sysctlKey, "fs.mqueue.") { - logrus.Infof("Sysctl %s=%s ignored in containers.conf, since IPC Namespace set to host", sysctlKey, sysctlVal) + logrus.Infof("Sysctl %s=%s ignored in containers.conf, since IPC Namespace set to %q", sysctlKey, sysctlVal, s.IpcNS.NSMode) continue } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index dfac1d457..79e20667b 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -206,6 +206,8 @@ type ContainerBasicConfig struct { UnsetEnvAll bool `json:"unsetenvall,omitempty"` // Passwd is a container run option that determines if we are validating users/groups before running the container Passwd *bool `json:"manage_password,omitempty"` + // PasswdEntry specifies arbitrary data to append to a file. + PasswdEntry string `json:"passwd_entry,omitempty"` } // ContainerStorageConfig contains information on the storage configuration of a diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 7d4fca846..c86af7295 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -190,7 +190,7 @@ func setNamespaces(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) } } if c.IPC != "" { - s.IpcNS, err = specgen.ParseNamespace(c.IPC) + s.IpcNS, err = specgen.ParseIPCNamespace(c.IPC) if err != nil { return err } @@ -832,6 +832,11 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions if s.Passwd == nil { s.Passwd = &t } + + if len(s.PasswdEntry) == 0 || len(c.PasswdEntry) != 0 { + s.PasswdEntry = c.PasswdEntry + } + return nil } diff --git a/test/e2e/container_clone_test.go b/test/e2e/container_clone_test.go index a327bb8ed..1d5944d1a 100644 --- a/test/e2e/container_clone_test.go +++ b/test/e2e/container_clone_test.go @@ -146,6 +146,20 @@ var _ = Describe("Podman container clone", func() { cloneData = cloneInspect.InspectContainerToJSON() Expect(createData[0].HostConfig.NanoCpus).ToNot(Equal(cloneData[0].HostConfig.NanoCpus)) Expect(cloneData[0].HostConfig.NanoCpus).To(Equal(nanoCPUs)) + + create = podmanTest.Podman([]string{"create", ALPINE}) + create.WaitWithDefaultTimeout() + Expect(create).To(Exit(0)) + clone = podmanTest.Podman([]string{"container", "clone", "--cpus=4", create.OutputToString()}) + clone.WaitWithDefaultTimeout() + Expect(clone).To(Exit(0)) + + cloneInspect = podmanTest.Podman([]string{"inspect", clone.OutputToString()}) + cloneInspect.WaitWithDefaultTimeout() + Expect(cloneInspect).To(Exit(0)) + cloneData = cloneInspect.InspectContainerToJSON() + Expect(cloneData[0].HostConfig.MemorySwappiness).To(Equal(int64(0))) + }) It("podman container clone in a pod", func() { diff --git a/test/e2e/run_passwd_test.go b/test/e2e/run_passwd_test.go index 4c97e665a..ce6c6ffda 100644 --- a/test/e2e/run_passwd_test.go +++ b/test/e2e/run_passwd_test.go @@ -137,4 +137,16 @@ USER 1000`, ALPINE) Expect(run).Should(Exit(0)) Expect(run.OutputToString()).NotTo((ContainSubstring("1234:1234"))) }) + + It("podman run --passwd-entry flag", func() { + // Test that the line we add doesn't contain anything else than what is specified + run := podmanTest.Podman([]string{"run", "--user", "1234:1234", "--passwd-entry=FOO", ALPINE, "grep", "^FOO$", "/etc/passwd"}) + run.WaitWithDefaultTimeout() + Expect(run).Should(Exit(0)) + + run = podmanTest.Podman([]string{"run", "--user", "12345:12346", "-w", "/etc", "--passwd-entry=$UID-$GID-$NAME-$HOME-$USERNAME", ALPINE, "cat", "/etc/passwd"}) + run.WaitWithDefaultTimeout() + Expect(run).Should(Exit(0)) + Expect(run.OutputToString()).To(ContainSubstring("12345-12346-container user-/etc-12345")) + }) }) diff --git a/test/system/090-events.bats b/test/system/090-events.bats index a0b0380a2..cac0a177c 100644 --- a/test/system/090-events.bats +++ b/test/system/090-events.bats @@ -129,3 +129,68 @@ EOF run cat $events_file is "$output" ".*\"Name\":\"$IMAGE" "test" } + +function _populate_events_file() { + # Create 100 duplicate entries to populate the events log file. + local events_file=$1 + truncate --size=0 $events_file + for i in {0..99}; do + printf '{"Name":"busybox","Status":"pull","Time":"2022-04-06T11:26:42.7236679%02d+02:00","Type":"image","Attributes":null}\n' $i >> $events_file + done +} + +@test "events log-file rotation" { + skip_if_remote "setting CONTAINERS_CONF logger options does not affect remote client" + + # Make sure that the events log file is (not) rotated depending on the + # settings in containers.conf. + + # Config without a limit + eventsFile=$PODMAN_TMPDIR/events.txt + _populate_events_file $eventsFile + containersConf=$PODMAN_TMPDIR/containers.conf + cat >$containersConf <<EOF +[engine] +events_logger="file" +events_logfile_path="$eventsFile" +EOF + + # Create events *without* a limit and make sure that it has not been + # rotated/truncated. + contentBefore=$(head -n100 $eventsFile) + CONTAINERS_CONF=$containersConf run_podman run --rm $IMAGE true + contentAfter=$(head -n100 $eventsFile) + is "$contentBefore" "$contentAfter" "events file has not been rotated" + + # Repopulate events file + rm $eventsFile + _populate_events_file $eventsFile + + # Config with a limit + rm $containersConf + cat >$containersConf <<EOF +[engine] +events_logger="file" +events_logfile_path="$eventsFile" +# The limit of 4750 is the *exact* half of the inital events file. +events_logfile_max_size=4750 +EOF + + # Create events *with* a limit and make sure that it has been + # rotated/truncated. Once rotated, the events file should only contain the + # second half of its previous events plus the new ones. + expectedContentAfterTruncation=$PODMAN_TMPDIR/truncated.txt + + run_podman create $IMAGE + CONTAINERS_CONF=$containersConf run_podman rm $output + tail -n52 $eventsFile >> $expectedContentAfterTruncation + + # Make sure the events file looks as expected. + is "$(cat $eventsFile)" "$(cat $expectedContentAfterTruncation)" "events file has been rotated" + + # Make sure that `podman events` can read the file, and that it returns the + # same amount of events. We checked the contents before. + CONTAINERS_CONF=$containersConf run_podman events --stream=false --since="2022-03-06T11:26:42.723667984+02:00" + is "$(wc -l <$eventsFile)" "$(wc -l <<<$output)" "all events are returned" + is "${lines[-2]}" ".* log-rotation $eventsFile" +} diff --git a/test/system/190-run-ipcns.bats b/test/system/190-run-ipcns.bats new file mode 100644 index 000000000..9327d8ec7 --- /dev/null +++ b/test/system/190-run-ipcns.bats @@ -0,0 +1,70 @@ +#!/usr/bin/env bats -*- bats -*- +# shellcheck disable=SC2096 +# +# Tests for podman build +# + +load helpers + +@test "podman --ipc=host" { + run readlink /proc/self/ns/ipc + hostipc=$output + run_podman run --rm --ipc=host $IMAGE readlink /proc/self/ns/ipc + is "$output" "$hostipc" "HostIPC and container IPC should be same" +} + +@test "podman --ipc=none" { + run readlink /proc/self/ns/ipc + hostipc=$output + run_podman run --rm --ipc=none $IMAGE readlink /proc/self/ns/ipc + if [[ $output == "$hostipc" ]]; then + die "hostipc and containeripc should be different" + fi + run_podman 1 run --rm --ipc=none $IMAGE ls /dev/shm + is "$output" "ls: /dev/shm: No such file or directory" "Should fail with missing /dev/shm" +} + +@test "podman --ipc=private" { + run readlink /proc/self/ns/ipc + hostipc=$output + run_podman run -d --ipc=private --name test $IMAGE sleep 100 + if [[ $output == "$hostipc" ]]; then + die "hostipc and containeripc should be different" + fi + run_podman 125 run --ipc=container:test --rm $IMAGE readlink /proc/self/ns/ipc + is "$output" ".*is not allowed: non-shareable IPC (hint: use IpcMode:shareable for the donor container)" "Containers should not share private ipc namespace" + run_podman stop -t 0 test + run_podman rm test +} + +@test "podman --ipc=shareable" { + run readlink /proc/self/ns/ipc + hostipc=$output + run_podman run -d --ipc=shareable --name test $IMAGE sleep 100 + if [[ $output == "$hostipc" ]]; then + die "hostipc and containeripc should be different" + fi + run_podman run --ipc=container:test --rm $IMAGE readlink /proc/self/ns/ipc + if [[ $output == "$hostipc" ]]; then + die "hostipc and containeripc should be different" + fi + run_podman stop -t 0 test + run_podman rm test +} + +@test "podman --ipc=container@test" { + run readlink /proc/self/ns/ipc + hostipc=$output + run_podman run -d --name test $IMAGE sleep 100 + run_podman exec test readlink /proc/self/ns/ipc + if [[ $output == "$hostipc" ]]; then + die "hostipc and containeripc should be different" + fi + testipc=$output + run_podman run --ipc=container:test --rm $IMAGE readlink /proc/self/ns/ipc + is "$output" "$testipc" "Containers should share ipc namespace" + run_podman stop -t 0 test + run_podman rm test +} + +# vim: filetype=sh diff --git a/troubleshooting.md b/troubleshooting.md index 941d1a322..cf554654b 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -18,8 +18,10 @@ and retry your command before reporting the issue. --- ### 2) Can't use volume mount, get permission denied +```console $ podman run -v ~/mycontent:/content fedora touch /content/file touch: cannot touch '/content/file': Permission denied +``` #### Solution @@ -38,14 +40,18 @@ content label. Shared volume labels allow all containers to read/write content. The **Z** option tells Podman to label the content with a private unshared label. Only the current container can use a private volume. +```console $ podman run -v ~/mycontent:/content:Z fedora touch /content/file +``` Make sure the content is private for the container. Do not relabel system directories and content. Relabeling system content might cause other confined services on your machine to fail. For these types of containers we recommend having SELinux separation disabled. The option `--security-opt label=disable` will disable SELinux separation for the container. +```console $ podman run --security-opt label=disable -v ~:/home/user fedora touch /home/user/file +``` In cases where the container image runs as a specific, non-root user, though, the solution is to fix the user namespace. This would include container images such as @@ -53,7 +59,9 @@ the Jupyter Notebook image (which runs as "jovyan") and the Postgres image (whic as "postgres"). In either case, use the `--userns` switch to map user namespaces, most of the time by using the **keep-id** option. +```console $ podman run -v "$PWD":/home/jovyan/work --userns=keep-id jupyter/scipy-notebook +``` --- ### 3) No such image or Bare keys cannot contain ':' @@ -131,8 +139,11 @@ It is most likely necessary to enable unprivileged pings on the host. Be sure the UID of the user is part of the range in the `/proc/sys/net/ipv4/ping_group_range` file. -To change its value you can use something like: `sysctl -w -"net.ipv4.ping_group_range=0 2000000"`. +To change its value you can use something like: + +```console +# sysctl -w "net.ipv4.ping_group_range=0 2000000" +``` To make the change persistent, you'll need to add a file in `/etc/sysctl.d` that contains `net.ipv4.ping_group_range=0 $MAX_UID`. @@ -159,8 +170,8 @@ When rootless Podman attempts to execute a container on a non exec home director If you are running Podman or Buildah on a home directory that is mounted noexec, then they will fail with a message like: -``` -podman run centos:7 +```console +$ podman run centos:7 standard_init_linux.go:203: exec user process caused "permission denied" ``` @@ -170,8 +181,8 @@ Since the administrator of the system set up your home directory to be noexec, y For example -``` -cat ~/.config/containers/storage.conf +```console +$ cat ~/.config/containers/storage.conf [storage] driver = "overlay" runroot = "/run/user/1000" @@ -203,7 +214,9 @@ container processes to write to the cgroup file system. Turn on this boolean, on SELinux separated systems, to allow systemd to run properly in the container. Only do this on systems running older versions of Podman. -`setsebool -P container_manage_cgroup true` +```console +# setsebool -P container_manage_cgroup true +``` ### 9) Newuidmap missing when running rootless Podman commands @@ -214,8 +227,8 @@ Rootless Podman requires the newuidmap and newgidmap programs to be installed. If you are running Podman or Buildah as a rootless user, you get an error complaining about a missing newuidmap executable. -``` -podman run -ti fedora sh +```console +$ podman run -ti fedora sh command required for rootless mode with multiple IDs: exec: "newuidmap": executable file not found in $PATH ``` @@ -231,8 +244,8 @@ Rootless Podman requires the user running it to have a range of UIDs listed in / A user, either via --user or through the default configured for the image, is not mapped inside the namespace. -``` -podman run --rm -ti --user 1000000 alpine echo hi +```console +$ podman run --rm -ti --user 1000000 alpine echo hi Error: container create failed: container_linux.go:344: starting container process caused "setup user: invalid argument" ``` @@ -240,38 +253,38 @@ Error: container create failed: container_linux.go:344: starting container proce Update the /etc/subuid and /etc/subgid with fields for users that look like: -``` -cat /etc/subuid +```console +$ cat /etc/subuid johndoe:100000:65536 test:165536:65536 ``` -The format of this file is USERNAME:UID:RANGE +The format of this file is `USERNAME:UID:RANGE` -* username as listed in /etc/passwd or getpwent. +* username as listed in `/etc/passwd` or `getpwent`. * The initial uid allocated for the user. * The size of the range of UIDs allocated for the user. -This means johndoe is allocated UIDS 100000-165535 as well as his standard UID in the -/etc/passwd file. +This means johndoe is allocated UIDs 100000-165535 as well as his standard UID in the +`/etc/passwd` file. -You should ensure that each user has a unique range of uids, because overlapping UIDs, +You should ensure that each user has a unique range of UIDs, because overlapping UIDs, would potentially allow one user to attack another user. In addition, make sure -that the range of uids you allocate can cover all uids that the container -requires. For example, if the container has a user with uid 10000, ensure you +that the range of UIDs you allocate can cover all UIDs that the container +requires. For example, if the container has a user with UID 10000, ensure you have at least 10001 subuids, and if the container needs to be run as a user with -uid 1000000, ensure you have at least 1000001 subuids. +UID 1000000, ensure you have at least 1000001 subuids. -You could also use the usermod program to assign UIDs to a user. +You could also use the `usermod` program to assign UIDs to a user. -If you update either the /etc/subuid or /etc/subgid file, you need to +If you update either the `/etc/subuid` or `/etc/subgid` file, you need to stop all running containers and kill the pause process. This is done automatically by the `system migrate` command, which can also be used to stop all the containers and kill the pause process. -``` -usermod --add-subuids 200000-201000 --add-subgids 200000-201000 johndoe -grep johndoe /etc/subuid /etc/subgid +```console +# usermod --add-subuids 200000-201000 --add-subgids 200000-201000 johndoe +# grep johndoe /etc/subuid /etc/subgid /etc/subuid:johndoe:200000:1001 /etc/subgid:johndoe:200000:1001 ``` @@ -281,7 +294,7 @@ grep johndoe /etc/subuid /etc/subgid When I change the graphroot storage location in storage.conf, the next time I run Podman, I get an error like: -``` +```console # podman run -p 5000:5000 -it centos bash bash: error while loading shared libraries: /lib64/libc.so.6: cannot apply additional memory protection after relocation: Permission denied @@ -293,9 +306,9 @@ and points storage.conf at this directory. #### Symptom -SELinux blocks containers from using random locations for overlay storage. +SELinux blocks containers from using arbitrary locations for overlay storage. These directories need to be labeled with the same labels as if the content was -under /var/lib/containers/storage. +under `/var/lib/containers/storage`. #### Solution @@ -303,9 +316,9 @@ Tell SELinux about the new containers storage by setting up an equivalence recor This tells SELinux to label content under the new path, as if it was stored under `/var/lib/containers/storage`. -``` -semanage fcontext -a -e /var/lib/containers /srv/containers -restorecon -R -v /srv/containers +```console +# semanage fcontext -a -e /var/lib/containers /srv/containers +# restorecon -R -v /srv/containers ``` The semanage command above tells SELinux to setup the default labeling of @@ -326,8 +339,8 @@ If you pull an anonymous image, one that should not require credentials, you can an `invalid username/password` error if you have credentials established in the authentication file for the target container registry that are no longer valid. -``` -podman run -it --rm docker://docker.io/library/alpine:latest ls +```console +$ podman run -it --rm docker://docker.io/library/alpine:latest ls Trying to pull docker://docker.io/library/alpine:latest...ERRO[0000] Error pulling image ref //alpine:latest: Error determining manifest MIME type for docker://alpine:latest: unable to retrieve auth token: invalid username/password Failed Error: unable to pull docker://docker.io/library/alpine:latest: unable to pull image: Error determining manifest MIME type for docker://alpine:latest: unable to retrieve auth token: invalid username/password @@ -376,7 +389,7 @@ error creating build container: Error committing the finished image: error addin Choose one of the following: * Setup containers/storage in a different directory, not on an NFS share. * Create a directory on a local file system. - * Edit `~/.config/containers/containers.conf` and point the `volume_path` option to that local directory. (Copy /usr/share/containers/containers.conf if ~/.config/containers/containers.conf does not exist) + * Edit `~/.config/containers/containers.conf` and point the `volume_path` option to that local directory. (Copy `/usr/share/containers/containers.conf` if `~/.config/containers/containers.conf` does not exist) * Otherwise just run Podman as root, via `sudo podman` ### 15) Rootless 'podman build' fails when using OverlayFS: @@ -386,7 +399,7 @@ when extracting an image. However, a rootless user does not have the privileges #### Symptom ```console -podman build --storage-driver overlay . +$ podman build --storage-driver overlay . STEP 1: FROM docker.io/ubuntu:xenial Getting image source signatures Copying blob edf72af6d627 done @@ -411,13 +424,12 @@ Choose one of the following: ### 16) RHEL 7 and CentOS 7 based `init` images don't work with cgroup v2 -The systemd version shipped in RHEL 7 and CentOS 7 doesn't have support for cgroup v2. Support for cgroup V2 requires version 230 of systemd or newer, which +The systemd version shipped in RHEL 7 and CentOS 7 doesn't have support for cgroup v2. Support for cgroup v2 requires version 230 of systemd or newer, which was never shipped or supported on RHEL 7 or CentOS 7. #### Symptom ```console - -sh# podman run --name test -d registry.access.redhat.com/rhel7-init:latest && sleep 10 && podman exec test systemctl status +# podman run --name test -d registry.access.redhat.com/rhel7-init:latest && sleep 10 && podman exec test systemctl status c8567461948439bce72fad3076a91ececfb7b14d469bfa5fbc32c6403185beff Failed to get D-Bus connection: Operation not permitted Error: non zero exit code: 1: OCI runtime error @@ -426,10 +438,9 @@ Error: non zero exit code: 1: OCI runtime error #### Solution You'll need to either: -* configure the host to use cgroup v1 +* configure the host to use cgroup v1. On Fedora you can do: -``` -On Fedora you can do: +```console # dnf install -y grubby # grubby --update-kernel=ALL --args=âsystemd.unified_cgroup_hierarchy=0" # reboot @@ -449,11 +460,9 @@ Once the user logs out all the containers exit. #### Solution You'll need to either: -* loginctl enable-linger $UID - -or as root if your user has not enough privileges. - -* sudo loginctl enable-linger $UID +```console +# loginctl enable-linger $UID +``` ### 18) `podman run` fails with "bpf create: permission denied error" @@ -488,7 +497,7 @@ $ podman system migrate Original command now returns -``` +```console $ podman unshare cat /proc/self/uid_map 0 1000 1 1 100000 65536 @@ -507,12 +516,13 @@ Any access inside the container is rejected with "Permission denied". #### Solution -The runtime uses `setgroups(2)` hence the process looses all additional groups +The runtime uses `setgroups(2)` hence the process loses all additional groups the non-root user has. Use the `--group-add keep-groups` flag to pass the user's supplementary group access into the container. Currently only available with the `crun` OCI runtime. ### 21) A rootless container running in detached mode is closed at logout +<!-- This is the same as section 17 above and should be deleted --> When running a container with a command like `podman run --detach httpd` as a rootless user, the container is closed upon logout and is not kept running. @@ -535,14 +545,14 @@ LOGINCTL(1), SYSTEMD(1) ### 22) Containers default detach keys conflict with shell history navigation Podman defaults to `ctrl-p,ctrl-q` to detach from a running containers. The -bash and zsh shells default to ctrl-p for the displaying of the previous +bash and zsh shells default to `ctrl-p` for the displaying of the previous command. This causes issues when running a shell inside of a container. #### Symptom With the default detach key combo ctrl-p,ctrl-q, shell history navigation (tested in bash and zsh) using ctrl-p to access the previous command will not -display this previous command. Or anything else. Conmon is waiting for an +display this previous command, or anything else. Conmon is waiting for an additional character to see if the user wants to detach from the container. Adding additional characters to the command will cause it to be displayed along with the additional character. If the user types ctrl-p a second time the shell @@ -553,23 +563,23 @@ display the 2nd to last command. The solution to this is to change the default detach_keys. For example in order to change the defaults to `ctrl-q,ctrl-q` use the `--detach-keys` option. -``` -podman run -ti --detach-keys ctrl-q,ctrl-q fedora sh +```console +$ podman run -ti --detach-keys ctrl-q,ctrl-q fedora sh ``` To make this change the default for all containers, users can modify the containers.conf file. This can be done simply in your home directory, but adding the following lines to users containers.conf -``` -$ cat >> ~/.config/containers/containers.conf < _eof +```console +$ cat >> ~/.config/containers/containers.conf << _eof [engine] detach_keys="ctrl-q,ctrl-q" _eof ``` In order to effect root running containers and all users, modify the system -wide defaults in /etc/containers/containers.conf +wide defaults in `/etc/containers/containers.conf`. ### 23) Container with exposed ports won't run in a pod @@ -579,7 +589,7 @@ can not be run within a pod. #### Symptom -``` +```console $ podman pod create --name srcview -p 127.0.0.1:3434:3434 -p 127.0.0.1:7080:7080 -p 127.0.0.1:3370:3370 4b2f4611fa2cbd60b3899b936368c2b3f4f0f68bc8e6593416e0ab8ecb0a3f1d $ podman run --pod srcview --name src-expose -p 3434:3434 -v "${PWD}:/var/opt/localrepo":Z,ro sourcegraph/src-expose:latest serve /var/opt/localrepo @@ -601,7 +611,7 @@ In the example from the symptom section, dropping the `-p 3434:3434` would allow `podman run` command to complete, and the container as part of the pod would still have access to that port. For example: -``` +```console $ podman run --pod srcview --name src-expose -v "${PWD}:/var/opt/localrepo":Z,ro sourcegraph/src-expose:latest serve /var/opt/localrepo ``` @@ -615,6 +625,7 @@ before they will run with the fuse filesystem in play. When trying to run the container images found at quay.io/podman, quay.io/containers registry.access.redhat.com/ubi8 or other locations, an error will sometimes be returned: +<!-- this would be better if it showed the command being run, and use ```console markup --> ``` ERRO error unmounting /var/lib/containers/storage/overlay/30c058cdadc888177361dd14a7ed7edab441c58525b341df321f07bc11440e68/merged: invalid argument error mounting container "1ae176ca72b3da7c70af31db7434bcf6f94b07dbc0328bc7e4e8fc9579d0dc2e": error mounting build container "1ae176ca72b3da7c70af31db7434bcf6f94b07dbc0328bc7e4e8fc9579d0dc2e": error creating overlay mount to /var/lib/containers/storage/overlay/30c058cdadc888177361dd14a7ed7edab441c58525b341df321f07bc11440e68/merged: using mount program /usr/bin/fuse-overlayfs: fuse: device not found, try 'modprobe fuse' first @@ -646,7 +657,7 @@ to mount volumes on them. Run the container once in read/write mode, Podman will generate all of the FDs on the rootfs, and from that point forward you can run with a read-only rootfs. -``` +```console $ podman run --rm --rootfs /path/to/rootfs true ``` @@ -654,13 +665,13 @@ The command above will create all the missing directories needed to run the cont After that, it can be used in read only mode, by multiple containers at the same time: -``` +```console $ podman run --read-only --rootfs /path/to/rootfs .... ``` Another option is to use an Overlay Rootfs Mount: -``` +```console $ podman run --rootfs /path/to/rootfs:O .... ``` @@ -685,7 +696,9 @@ This means that CPU limit delegation is not enabled for the current user. You can verify whether CPU limit delegation is enabled by running the following command: - cat "/sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers" +```console +$ cat "/sys/fs/cgroup/user.slice/user-$(id -u).slice/user@$(id -u).service/cgroup.controllers" +``` Example output might be: @@ -697,8 +710,10 @@ not have permission to set CPU limits. If you want to enable CPU limit delegation for all users, you can create the file `/etc/systemd/system/user@.service.d/delegate.conf` with the contents: - [Service] - Delegate=memory pids cpu io +```ini +[Service] +Delegate=memory pids cpu io +``` After logging out and logging back in, you should have permission to set CPU limits. @@ -724,26 +739,33 @@ You can confirm this is the case by attempting to connect to the host via `podma Create a new key using a supported algorithm e.g. ecdsa: -`ssh-keygen -t ecdsa -f ~/.ssh/podman` +```console +$ ssh-keygen -t ecdsa -f ~/.ssh/podman +``` Then copy the new id over: -`ssh-copy-id -i ~/.ssh/podman.pub user@host` +```console +$ ssh-copy-id -i ~/.ssh/podman.pub user@host +``` And then re-add the connection (removing the old one if necessary): -`podman-remote system connection add myuser --identity ~/.ssh/podman ssh://user@host/run/user/1000/podman/podman.sock` +```console +$ podman-remote system connection add myuser --identity ~/.ssh/podman ssh://user@host/run/user/1000/podman/podman.sock +``` And now this should work: -`podman-remote info` +```console +$ podman-remote info +``` ---- ### 28) Rootless CNI networking fails in RHEL with Podman v2.2.1 to v3.0.1. A failure is encountered when trying to use networking on a rootless container in Podman v2.2.1 through v3.0.1 on RHEL. This error does not -occur on other Linux Distributions. +occur on other Linux distributions. #### Symptom @@ -757,6 +779,7 @@ an Infra container image for CNI-in-slirp4netns must be created. The instructions for building the Infra container image can be found for v2.2.1 [here](https://github.com/containers/podman/tree/v2.2.1-rhel/contrib/rootless-cni-infra), and for v3.0.1 [here](https://github.com/containers/podman/tree/v3.0.1-rhel/contrib/rootless-cni-infra). + ### 29) Container related firewall rules are lost after reloading firewalld Container network can't be reached after `firewall-cmd --reload` and `systemctl restart firewalld` Running `podman network reload` will fix it but it has to be done manually. @@ -767,7 +790,7 @@ The firewall rules created by podman are lost when the firewall is reloaded. [@ranjithrajaram](https://github.com/containers/podman/issues/5431#issuecomment-847758377) has created a systemd-hook to fix this issue 1) For "firewall-cmd --reload", create a systemd unit file with the following -``` +```ini [Unit] Description=firewalld reload hook - run a hook script on firewalld reload Wants=dbus.service @@ -780,8 +803,9 @@ ExecStart=/bin/bash -c '/bin/busctl monitor --system --match "interface=org.fedo [Install] WantedBy=default.target ``` + 2) For "systemctl restart firewalld", create a systemd unit file with the following -``` +```ini [Unit] Description=podman network reload Wants=firewalld.service @@ -796,11 +820,12 @@ ExecStart=/usr/bin/podman network reload --all [Install] WantedBy=default.target ``` -However, If you use busctl monitor then you can't get machine-readable output on `RHEL 8`. + +However, If you use busctl monitor then you can't get machine-readable output on RHEL 8. Since it doesn't have `busctl -j` as mentioned here by [@yrro](https://github.com/containers/podman/issues/5431#issuecomment-896943018). For RHEL 8, you can use the following one-liner bash script. -``` +```ini [Unit] Description=Redo podman NAT rules after firewalld starts or reloads Wants=dbus.service @@ -815,13 +840,13 @@ Restart=Always [Install] WantedBy=default.target ``` -`busctl-monitor` is almost usable in `RHEL 8`, except that it always outputs two bogus events when it starts up, +`busctl-monitor` is almost usable in RHEL 8, except that it always outputs two bogus events when it starts up, one of which is (in its only machine-readable format) indistinguishable from the `NameOwnerChanged` that you get when firewalld starts up. This means you would get an extra `podman network reload --all` when this unit starts. Apart from this, you can use the following systemd service with the python3 code. -``` +```ini [Unit] Description=Redo podman NAT rules after firewalld starts or reloads Wants=dbus.service @@ -837,7 +862,7 @@ Restart=always WantedBy=default.target ``` The code reloads podman network twice when you use `systemctl restart firewalld`. -``` +```python3 import dbus from gi.repository import GLib from dbus.mainloop.glib import DBusGMainLoop @@ -892,6 +917,7 @@ def signal_listener(): if __name__ == "__main__": signal_listener() ``` + ### 30) Podman run fails with `ERRO[0000] XDG_RUNTIME_DIR directory "/run/user/0" is not owned by the current user` or `Error: error creating tmpdir: mkdir /run/user/1000: permission denied`. A failure is encountered when performing `podman run` with a warning `XDG_RUNTIME_DIR is pointing to a path which is not writable. Most likely podman will fail.` @@ -901,30 +927,30 @@ A failure is encountered when performing `podman run` with a warning `XDG_RUNTIM A rootless container is being invoked with cgroup configuration as `cgroupv2` for user with missing or invalid **systemd session**. Example cases -```bash +```console # su user1 -c 'podman images' ERRO[0000] XDG_RUNTIME_DIR directory "/run/user/0" is not owned by the current user ``` -```bash +```console # su - user1 -c 'podman images' Error: error creating tmpdir: mkdir /run/user/1000: permission denied ``` #### Solution -Podman expects a valid login session for the `rootless+cgroupv2` use-case. Podman execution is expected to fail if the login session is not present. In most cases, podman will figure out a solution on its own but if `XDG_RUNTIME_DIR` is pointing to a path that is not writable execution will most fail. Typical scenarios of such cases are seen when users are trying to use Podman with `su - <user> -c '<podman-command>`, or `sudo -l` and badly configured systemd session. +Podman expects a valid login session for the `rootless+cgroupv2` use-case. Podman execution is expected to fail if the login session is not present. In most cases, podman will figure out a solution on its own but if `XDG_RUNTIME_DIR` is pointing to a path that is not writable execution will most likely fail. Typical scenarios of such cases are seen when users are trying to use Podman with `su - <user> -c '<podman-command>'`, or `sudo -l` and badly configured systemd session. Alternatives: * Execute Podman via __systemd-run__ that will first start a systemd login session: - ``` - sudo systemd-run --machine=username@ --quiet --user --collect --pipe --wait podman run --rm docker.io/library/alpine echo hello + ```console + $ sudo systemd-run --machine=username@ --quiet --user --collect --pipe --wait podman run --rm docker.io/library/alpine echo hello ``` * Start an interactive shell in a systemd login session with the command `machinectl shell <username>@` and then run Podman - ``` + ```console $ sudo -i # machinectl shell username@ Connected to the local host. Press ^] three times within 1s to exit session. @@ -977,7 +1003,7 @@ from the user's subordinate UID and GID ranges on the host system. An example -```Text +```console $ mkdir dir1 $ chmod 777 dir1 $ podman run --rm -v ./dir1:/dir1:Z \ @@ -995,8 +1021,8 @@ If you want to read, chown, or remove such a file, enter a user namespace. Instead of running commands such as `less dir1/a` or `rm dir1/a`, you need to prepend the command-line with `podman unshare`, i.e., `podman unshare less dir1/a` or `podman unshare rm dir1/a`. To change the ownership -of the file _dir1/a_ to your regular user's UID and GID, run `podman unshare chown 0:0 dir1/a`. -A file having the ownership _0:0_ in the user namespace is owned by the regular +of the file `dir1/a` to your regular user's UID and GID, run `podman unshare chown 0:0 dir1/a`. +A file having the ownership `0:0` in the user namespace is owned by the regular user on the host. To use Bash features, such as variable expansion and globbing, you need to wrap the command with `bash -c`, e.g. `podman unshare bash -c 'ls $HOME/dir1/a*'`. @@ -1008,41 +1034,41 @@ between the container and the host. Let's try it out. In the example above `ls -l` shows the UID 102002 and GID 102002. Set shell variables -```Text -$ uid_from_ls = 102002 -$ gid_from_ls = 102002 +```console +$ uid_from_ls=102002 +$ gid_from_ls=102002 ``` Set shell variables to the lowest subordinate UID and GID -```Text +```console $ lowest_subuid=$(podman info --format "{{ (index .Host.IDMappings.UIDMap 1).HostID }}") $ lowest_subgid=$(podman info --format "{{ (index .Host.IDMappings.GIDMap 1).HostID }}") ``` Compute the UID and GID inside the container that map to the owner of the created file on the host. -```Text +```console $ uid=$(( $uid_from_ls - $lowest_subuid + 1)) $ gid=$(( $gid_from_ls - $lowest_subgid + 1)) ``` (In the computation it was assumed that there is only one subuid range and one subgid range) -```Text +```console $ echo $uid 2003 $ echo $gid 2003 ``` -The computation shows that the UID is _2003_ and the GID is _2003_ inside the container. +The computation shows that the UID is `2003` and the GID is `2003` inside the container. This comes as no surprise as this is what was specified before with `--user=2003:2003`, but the same computation could be used whenever a username is specified -or the __--user__ option is not used. +or the `--user` option is not used. Run the container again but now with UIDs and GIDs mapped -```Text +```console $ subuidSize=$(( $(podman info --format "{{ range .Host.IDMappings.UIDMap }}+{{.Size }}{{end }}" ) - 1 )) $ subgidSize=$(( $(podman info --format "{{ range .Host.IDMappings.GIDMap }}+{{.Size }}{{end }}" ) - 1 )) $ mkdir dir1 @@ -1066,18 +1092,17 @@ $ ls -l dir1/a $ ``` -In this example the __--user__ option specified a rootless user in the container. -As the rootless user could also have been specified in the container image, e.g., +In this example the `--user` option specified a rootless user in the container. +As the rootless user could also have been specified in the container image, e.g. -```Text +```console $ podman image inspect --format "user: {{.User}}" IMAGE user: hpc -$ ``` -the same problem could also occur even without specifying __--user__. +the same problem could also occur even without specifying `--user`. Another variant of the same problem could occur when using -__--user=root:root__ (the default), but where the root user creates non-root owned files +`--user=root:root` (the default), but where the root user creates non-root owned files in some way (e.g by creating them themselves, or switching the effective UID to a rootless user and then creates files). @@ -1097,7 +1122,7 @@ permissions of the regular user of the host. for files, directories or devices passed in to the container with `--device=..`,`--volume=..` or `--mount=..`, e.g. -```Text +```console $ mkdir dir1 $ chmod 700 dir1 $ podman run --rm -v ./dir1:/dir1:Z \ @@ -1110,7 +1135,9 @@ ls: cannot open directory '/dir1': Permission denied We follow essentially the same solution as in the previous troubleshooting tip: - "_Container creates a file that is not owned by the regular UID_" + + Container creates a file that is not owned by the regular UID + but for this problem the container UID and GID can't be as easily computed by mere addition and subtraction. @@ -1118,42 +1145,41 @@ In other words, it might be more challenging to find out the UID and the GID inside the container that we want to map to the regular user on the host. -If the __--user__ option is used together with a numerical UID and GID +If the `--user` option is used together with a numerical UID and GID to specify a rootless user, we already know the answer. -If the __--user__ option is used together with a username and groupname, -we could look up the UID and GID in the file _/etc/passwd_ of the container. +If the `--user` option is used together with a username and groupname, +we could look up the UID and GID in the file `/etc/passwd` of the container. -If the container user is not set via __--user__ but instead from the +If the container user is not set via `--user` but instead from the container image, we could inspect the container image -```Text +```console $ podman image inspect --format "user: {{.User}}" IMAGE user: hpc -$ ``` -and then look it up in _/etc/passwd_ of the container. +and then look it up in `/etc/passwd` of the container. If the problem occurs in a container that is started to run as root but later switches to an effictive UID of a rootless user, it might be less straightforward to find out the UID and the GID. Reading the -_Containerfile_, _Dockerfile_ or the _/etc/passwd_ could give a clue. +`Containerfile`, `Dockerfile` or the `/etc/passwd` could give a clue. To run the container with the rootless container UID and GID mapped to the user's regular UID and GID on the host follow these steps: -Set the _uid_ and _gid_ shell variables in a Bash shell to the UID and GID +Set the `uid` and `gid` shell variables in a Bash shell to the UID and GID of the user that will be running inside the container, e.g. -```Text +```console $ uid=2003 $ gid=2003 ``` and run -```Text +```console $ mkdir dir1 $ echo hello > dir1/file.txt $ chmod 700 dir1/file.txt @@ -1170,13 +1196,12 @@ $ podman run --rm \ --gidmap $(($gid+1)):$(($gid+1)):$(($subgidSize-$gid)) \ docker.io/library/alpine cat /dir1/file.txt hello -$ ``` A side-note: Using [__--userns=keep-id__](https://docs.podman.io/en/latest/markdown/podman-run.1.html#userns-mode) can sometimes be an alternative solution, but it forces the regular user's host UID to be mapped to the same UID inside the container -so it provides less flexibility than using __--uidmap__ and __--gidmap__. +so it provides less flexibility than using `--uidmap` and `--gidmap`. ### 35) Images in the additional stores can be deleted even if there are containers using them |