diff options
-rw-r--r-- | cmd/podman/pod_create.go | 4 | ||||
-rw-r--r-- | cmd/podman/pod_stats.go | 130 | ||||
-rw-r--r-- | cmd/podman/rm.go | 11 | ||||
-rw-r--r-- | docs/podman-create.1.md | 33 | ||||
-rw-r--r-- | docs/podman-rm.1.md | 4 | ||||
-rw-r--r-- | docs/podman-run.1.md | 38 | ||||
-rw-r--r-- | docs/podman-stats.1.md | 9 | ||||
-rw-r--r-- | libpod/errors.go | 11 | ||||
-rw-r--r-- | libpod/kube.go | 10 | ||||
-rw-r--r-- | libpod/oci.go | 1 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 15 | ||||
-rw-r--r-- | test/README.md | 2 | ||||
-rw-r--r-- | test/e2e/pod_stats_test.go | 23 | ||||
-rw-r--r-- | troubleshooting.md | 63 |
14 files changed, 304 insertions, 50 deletions
diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index 9ac5d94a9..35e58df31 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -9,7 +9,6 @@ import ( "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -82,9 +81,6 @@ func podCreateCmd(c *cliconfig.PodCreateValues) error { if !c.Infra { return errors.Errorf("you must have an infra container to publish port bindings to the host") } - if rootless.IsRootless() { - return errors.Errorf("rootless networking does not allow port binding to the host") - } } if !c.Infra && c.Flag("share").Changed && c.Share != "none" && c.Share != "" { diff --git a/cmd/podman/pod_stats.go b/cmd/podman/pod_stats.go index ccbcf9f7c..02dee68ac 100644 --- a/cmd/podman/pod_stats.go +++ b/cmd/podman/pod_stats.go @@ -2,7 +2,11 @@ package main import ( "fmt" + "html/template" + "os" + "reflect" "strings" + "text/tabwriter" "time" "encoding/json" @@ -136,6 +140,25 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { step = 0 } + headerNames := make(map[string]string) + if c.Format != "" { + // Make a map of the field names for the headers + v := reflect.ValueOf(podStatOut{}) + t := v.Type() + for i := 0; i < t.NumField(); i++ { + value := strings.ToUpper(splitCamelCase(t.Field(i).Name)) + switch value { + case "CPU": + value = value + " %" + case "MEM": + value = value + " %" + case "MEM USAGE": + value = "MEM USAGE / LIMIT" + } + headerNames[t.Field(i).Name] = value + } + } + for i := 0; i < times; i += step { var newStats []*libpod.PodContainerStats for _, p := range pods { @@ -163,7 +186,14 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { outputJson(newStats) } else { - outputToStdOut(newStats) + results := podContainerStatsToPodStatOut(newStats) + if len(format) == 0 { + outputToStdOut(results) + } else { + if err := printPSFormat(c.Format, results, headerNames); err != nil { + return err + } + } } time.Sleep(time.Second) previousPodStats := new([]*libpod.PodContainerStats) @@ -177,28 +207,88 @@ func podStatsCmd(c *cliconfig.PodStatsValues) error { return nil } -func outputToStdOut(stats []*libpod.PodContainerStats) { - outFormat := ("%-14s %-14s %-12s %-6s %-19s %-6s %-19s %-19s %-4s\n") - fmt.Printf(outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS") - for _, i := range stats { - if len(i.ContainerStats) == 0 { - fmt.Printf(outFormat, i.Pod.ID()[:12], "--", "--", "--", "--", "--", "--", "--", "--") - } - for _, c := range i.ContainerStats { - cpu := floatToPercentString(c.CPU) - memUsage := combineHumanValues(c.MemUsage, c.MemLimit) - memPerc := floatToPercentString(c.MemPerc) - netIO := combineHumanValues(c.NetInput, c.NetOutput) - blockIO := combineHumanValues(c.BlockInput, c.BlockOutput) - pids := pidsToString(c.PIDs) - containerName := c.Name - if len(c.Name) > 10 { - containerName = containerName[:10] +func podContainerStatsToPodStatOut(stats []*libpod.PodContainerStats) []*podStatOut { + var out []*podStatOut + for _, p := range stats { + for _, c := range p.ContainerStats { + o := podStatOut{ + CPU: floatToPercentString(c.CPU), + MemUsage: combineHumanValues(c.MemUsage, c.MemLimit), + Mem: floatToPercentString(c.MemPerc), + NetIO: combineHumanValues(c.NetInput, c.NetOutput), + BlockIO: combineHumanValues(c.BlockInput, c.BlockOutput), + PIDS: pidsToString(c.PIDs), + CID: c.ContainerID[:12], + Name: c.Name, + Pod: p.Pod.ID()[:12], } - fmt.Printf(outFormat, i.Pod.ID()[:12], c.ContainerID[:12], containerName, cpu, memUsage, memPerc, netIO, blockIO, pids) + out = append(out, &o) + } + } + return out +} + +type podStatOut struct { + CPU string + MemUsage string + Mem string + NetIO string + BlockIO string + PIDS string + Pod string + CID string + Name string +} + +func printPSFormat(format string, stats []*podStatOut, headerNames map[string]string) error { + if len(stats) == 0 { + return nil + } + + // Use a tabwriter to align column format + w := tabwriter.NewWriter(os.Stdout, 0, 0, 3, ' ', 0) + // Spit out the header if "table" is present in the format + if strings.HasPrefix(format, "table") { + hformat := strings.Replace(strings.TrimSpace(format[5:]), " ", "\t", -1) + format = hformat + headerTmpl, err := template.New("header").Parse(hformat) + if err != nil { + return err + } + if err := headerTmpl.Execute(w, headerNames); err != nil { + return err + } + fmt.Fprintln(w, "") + } + + // Spit out the data rows now + dataTmpl, err := template.New("data").Parse(format) + if err != nil { + return err + } + for _, container := range stats { + if err := dataTmpl.Execute(w, container); err != nil { + return err + } + fmt.Fprintln(w, "") + } + // Flush the writer + return w.Flush() + +} + +func outputToStdOut(stats []*podStatOut) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + outFormat := ("%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n") + fmt.Fprintf(w, outFormat, "POD", "CID", "NAME", "CPU %", "MEM USAGE/ LIMIT", "MEM %", "NET IO", "BLOCK IO", "PIDS") + for _, i := range stats { + if len(stats) == 0 { + fmt.Fprintf(w, outFormat, i.Pod, "--", "--", "--", "--", "--", "--", "--", "--") + } else { + fmt.Fprintf(w, outFormat, i.Pod, i.CID, i.Name, i.CPU, i.MemUsage, i.Mem, i.NetIO, i.BlockIO, i.PIDS) } } - fmt.Println() + w.Flush() } func getPreviousPodContainerStats(podID string, prev []*libpod.PodContainerStats) map[string]*libpod.ContainerStats { diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index e9083ef74..f8aef6461 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -6,6 +6,7 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" + "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -60,10 +61,18 @@ func rmCmd(c *cliconfig.RmValues) error { delContainers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { + if c.Force && len(c.InputArgs) > 0 { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + err = nil + } + runtime.RemoveContainersFromStorage(c.InputArgs) + } if len(delContainers) == 0 { return err } - fmt.Println(err.Error()) + if err != nil { + fmt.Println(err.Error()) + } } for _, container := range delContainers { diff --git a/docs/podman-create.1.md b/docs/podman-create.1.md index 98b1a2a17..2dffaff3b 100644 --- a/docs/podman-create.1.md +++ b/docs/podman-create.1.md @@ -657,18 +657,21 @@ The followings examples are all valid: Without this argument the command will be run as root in the container. -**--userns**="" +**--userns**=host +**--userns**=ns:my_namespace -Set the usernamespace mode for the container. The use of userns is disabled by default. +Set the user namespace mode for the container. The use of userns is disabled by default. - **host**: use the host usernamespace and enable all privileged options (e.g., `pid=host` or `--privileged`). - **ns**: specify the usernamespace to use. +- `host`: run in the user namespace of the caller. This is the default if no user namespace options are set. The processes running in the container will have the same privileges on the host as any other process launched by the calling user. +- `ns`: run the container in the given existing user namespace. + +This option is incompatible with --gidmap, --uidmap, --subuid and --subgid **--uts**=*host* Set the UTS mode for the container **host**: use the host's UTS namespace inside the container. - **ns**: specify the usernamespace to use. + **ns**: specify the user namespace to use. Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. **--volume**, **-v**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] @@ -782,8 +785,8 @@ can override the working directory by using the **-w** option. ### Set UID/GID mapping in a new user namespace -If you want to run the container in a new user namespace and define the mapping of -the uid and gid from the host. +Running a container in a new user namespace requires a mapping of +the uids and gids from the host. ``` $ podman create --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello @@ -804,13 +807,27 @@ KillMode=process WantedBy=multi-user.target ``` +### Rootless Containers + +Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils +be installed. The shadow-utils package must include the newuidmap and newgidmap executables. + +Note: RHEL7 and Centos 7 will not have this feature until RHEL7.7 is released. + +In order for users to run rootless, there must be an entry for their username in /etc/subuid and /etc/subgid which lists the UIDs for their user namespace. + +Rootless podman works better if the fuse-overlayfs and slirp4netns packages are installed. +The fuse-overlay package provides a userspace overlay storage driver, otherwise users need to use +the vfs storage driver, which is diskspace expensive and does not perform well. slirp4netns is +required for VPN, without it containers need to be run with the --net=host flag. + ## FILES **/etc/subuid** **/etc/subgid** ## SEE ALSO -subgid(5), subuid(5), libpod.conf(5), systemd.unit(5), setsebool(8) +subgid(5), subuid(5), libpod.conf(5), systemd.unit(5), setsebool(8), slirp4netns(1), fuse-overlayfs(1) ## HISTORY October 2017, converted from Docker documentation to podman by Dan Walsh for podman <dwalsh@redhat.com> diff --git a/docs/podman-rm.1.md b/docs/podman-rm.1.md index 4fcb0b6c5..f4513c2be 100644 --- a/docs/podman-rm.1.md +++ b/docs/podman-rm.1.md @@ -17,7 +17,9 @@ Remove all containers. Can be used in conjunction with -f as well. **--force, f** -Force the removal of a running and paused containers +Force the removal of running and paused containers. Forcing a containers removal also +removes containers from container storage even if the container is not known to podman. +Containers could have been created by a different container engine. **--latest, -l** diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index 828ae96a8..cef9a6e8a 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -663,7 +663,7 @@ Without this argument the command will be run as root in the container. **--userns**=host **--userns**=ns:my_namespace -Set the user namespace for the container. +Set the user namespace mode for the container. The use of userns is disabled by default. - `host`: run in the user namespace of the caller. This is the default if no user namespace options are set. The processes running in the container will have the same privileges on the host as any other process launched by the calling user. - `ns`: run the container in the given existing user namespace. @@ -675,7 +675,7 @@ This option is incompatible with --gidmap, --uidmap, --subuid and --subgid Set the UTS mode for the container `host`: use the host's UTS namespace inside the container. -`ns`: specify the usernamespace to use. +`ns`: specify the user namespace to use. **NOTE**: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. @@ -709,6 +709,20 @@ Current supported mount TYPES are bind, and tmpfs. ยท tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux. +**--userns**="" + +Set the user namespace mode for the container. The use of userns is disabled by default. + + **host**: use the host user namespace and enable all privileged options (e.g., `pid=host` or `--privileged`). + **ns**: specify the user namespace to use. + +**--uts**=*host* + +Set the UTS mode for the container + **host**: use the host's UTS namespace inside the container. + **ns**: specify the user namespace to use. + Note: the host mode gives the container access to changing the host's hostname and is therefore considered insecure. + **--volume**, **-v**[=*[HOST-DIR:CONTAINER-DIR[:OPTIONS]]*] Create a bind mount. If you specify, ` -v /HOST-DIR:/CONTAINER-DIR`, podman @@ -1074,8 +1088,8 @@ supported sysctls. ### Set UID/GID mapping in a new user namespace -If you want to run the container in a new user namespace and define the mapping of -the uid and gid from the host. +Running a container in a new user namespace requires a mapping of +the uids and gids from the host. ``` $ podman run --uidmap 0:30000:7000 --gidmap 0:30000:7000 fedora echo hello @@ -1096,13 +1110,27 @@ KillMode=process WantedBy=multi-user.target ``` +### Rootless Containers + +Podman runs as a non root user on most systems. This feature requires that a new enough version of shadow-utils +be installed. The shadow-utils package must include the newuidmap and newgidmap executables. + +Note: RHEL7 and Centos 7 will not have this feature until RHEL7.7 is released. + +In order for users to run rootless, there must be an entry for their username in /etc/subuid and /etc/subgid which lists the UIDs for their user namespace. + +Rootless podman works better if the fuse-overlayfs and slirp4netns packages are installed. +The fuse-overlay package provides a userspace overlay storage driver, otherwise users need to use +the vfs storage driver, which is diskspace expensive and does not perform well. slirp4netns is +required for VPN, without it containers need to be run with the --net=host flag. + ## FILES **/etc/subuid** **/etc/subgid** ## SEE ALSO -subgid(5), subuid(5), libpod.conf(5), systemd.unit(5), setsebool(8) +subgid(5), subuid(5), libpod.conf(5), systemd.unit(5), setsebool(8), slirp4netns(1), fuse-overlayfs(1) ## HISTORY September 2018, updated by Kunal Kushwaha <kushwaha_kunal_v7@lab.ntt.co.jp> diff --git a/docs/podman-stats.1.md b/docs/podman-stats.1.md index 8fc765326..d0b56b2e6 100644 --- a/docs/podman-stats.1.md +++ b/docs/podman-stats.1.md @@ -36,16 +36,17 @@ Valid placeholders for the Go template are listed below: | **Placeholder** | **Description** | | --------------- | --------------- | -| .ID | Container ID | +| .Pod | Pod ID | +| .CID | Container ID | | .Name | Container Name | -| .CPUPerc | CPU percentage | +| .CPU | CPU percentage | | .MemUsage | Memory usage | -| .MemPerc | Memory percentage | +| .Mem | Memory percentage | | .NetIO | Network IO | | .BlockIO | Block IO | | .PIDS | Number of PIDs | - +When using a GO template, you may preceed the format with `table` to print headers. ## EXAMPLE ``` diff --git a/libpod/errors.go b/libpod/errors.go index d6614141c..30a19d30f 100644 --- a/libpod/errors.go +++ b/libpod/errors.go @@ -2,15 +2,20 @@ package libpod import ( "errors" + + "github.com/containers/libpod/libpod/image" ) var ( // ErrNoSuchCtr indicates the requested container does not exist - ErrNoSuchCtr = errors.New("no such container") + ErrNoSuchCtr = image.ErrNoSuchCtr + // ErrNoSuchPod indicates the requested pod does not exist - ErrNoSuchPod = errors.New("no such pod") + ErrNoSuchPod = image.ErrNoSuchPod + // ErrNoSuchImage indicates the requested image does not exist - ErrNoSuchImage = errors.New("no such image") + ErrNoSuchImage = image.ErrNoSuchImage + // ErrNoSuchVolume indicates the requested volume does not exist ErrNoSuchVolume = errors.New("no such volume") diff --git a/libpod/kube.go b/libpod/kube.go index f34805e39..16cebf99b 100644 --- a/libpod/kube.go +++ b/libpod/kube.go @@ -401,7 +401,7 @@ func capAddDrop(caps *specs.LinuxCapabilities) (*v1.Capabilities, error) { func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) { priv := c.Privileged() ro := c.IsReadOnly() - allowPrivEscalation := !c.Spec().Process.NoNewPrivileges + allowPrivEscalation := !c.config.Spec.Process.NoNewPrivileges newCaps, err := capAddDrop(c.config.Spec.Process.Capabilities) if err != nil { @@ -421,7 +421,13 @@ func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) { } if c.User() != "" { - // It is *possible* that + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + } + if err := c.syncContainer(); err != nil { + return nil, errors.Wrapf(err, "unable to sync container during YAML generation") + } logrus.Debugf("Looking in container for user: %s", c.User()) u, err := lookup.GetUser(c.state.Mountpoint, c.User()) if err != nil { diff --git a/libpod/oci.go b/libpod/oci.go index e55bd57dc..26d2c6ef1 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -321,7 +321,6 @@ func (r *OCIRuntime) createOCIContainer(ctr *Container, cgroupParent string, res cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBPOD_USERNS_CONFIGURED=%s", os.Getenv("_LIBPOD_USERNS_CONFIGURED"))) cmd.Env = append(cmd.Env, fmt.Sprintf("_LIBPOD_ROOTLESS_UID=%s", os.Getenv("_LIBPOD_ROOTLESS_UID"))) cmd.Env = append(cmd.Env, fmt.Sprintf("HOME=%s", os.Getenv("HOME"))) - cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) if r.reservePorts && !ctr.config.NetMode.IsSlirp4netns() { ports, err := bindPorts(ctr.config.PortMappings) diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 9afdef7b6..4f8192198 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -10,7 +10,9 @@ import ( "strings" "time" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/rootless" + "github.com/containers/storage" "github.com/containers/storage/pkg/stringid" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" @@ -564,3 +566,16 @@ func (r *Runtime) Export(name string, path string) error { return ctr.Export(path) } + +// RemoveContainersFromStorage attempt to remove containers from storage that do not exist in libpod database +func (r *Runtime) RemoveContainersFromStorage(ctrs []string) { + for _, i := range ctrs { + // if the container does not exist in database, attempt to remove it from storage + if _, err := r.LookupContainer(i); err != nil && errors.Cause(err) == image.ErrNoSuchCtr { + r.storageService.UnmountContainerImage(i, true) + if err := r.storageService.DeleteContainer(i); err != nil && errors.Cause(err) != storage.ErrContainerUnknown { + logrus.Errorf("Failed to remove container %q from storage: %s", i, err) + } + } + } +} diff --git a/test/README.md b/test/README.md index 90dcdfe3d..ef3bfbcf9 100644 --- a/test/README.md +++ b/test/README.md @@ -66,7 +66,7 @@ switch is optional. You can run a single file of integration tests using the go test command: ``` -GOPATH=~/go go test -v test/e2e/libpod_suite_test.go test/e2e/config.go test/e2e/config_amd64.go test/e2e/your_test.go +GOPATH=~/go go test -v test/e2e/libpod_suite_test.go test/e2e/common_test.go test/e2e/config.go test/e2e/config_amd64.go test/e2e/your_test.go ``` #### Run all tests like PAPR diff --git a/test/e2e/pod_stats_test.go b/test/e2e/pod_stats_test.go index 43d089a24..e330c3a39 100644 --- a/test/e2e/pod_stats_test.go +++ b/test/e2e/pod_stats_test.go @@ -147,5 +147,28 @@ var _ = Describe("Podman pod stats", func() { Expect(stats.ExitCode()).To(Equal(0)) Expect(stats.IsJSONOutputValid()).To(BeTrue()) }) + It("podman stats with GO template", func() { + _, ec, podid := podmanTest.CreatePod("") + Expect(ec).To(Equal(0)) + + session := podmanTest.RunTopContainerInPod("", podid) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + stats := podmanTest.Podman([]string{"pod", "stats", "-a", "--no-reset", "--no-stream", "--format", "\"table {{.CID}} {{.Pod}} {{.Mem}} {{.MemUsage}} {{.CPU}} {{.NetIO}} {{.BlockIO}} {{.PIDS}} {{.Pod}}\""}) + stats.WaitWithDefaultTimeout() + Expect(stats.ExitCode()).To(Equal(0)) + }) + + It("podman stats with invalid GO template", func() { + _, ec, podid := podmanTest.CreatePod("") + Expect(ec).To(Equal(0)) + + session := podmanTest.RunTopContainerInPod("", podid) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + stats := podmanTest.Podman([]string{"pod", "stats", "-a", "--no-reset", "--no-stream", "--format", "\"table {{.ID}} \""}) + stats.WaitWithDefaultTimeout() + Expect(stats.ExitCode()).ToNot(Equal(0)) + }) }) diff --git a/troubleshooting.md b/troubleshooting.md index 3f66b56ef..24a1dc6cb 100644 --- a/troubleshooting.md +++ b/troubleshooting.md @@ -191,3 +191,66 @@ SELinux provides a boolean `container_manage_cgroup`, which allows 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. `setsebool -P container_manage_cgroup true` + +### 9) Newuidmap missing when running rootless Podman commands + +Rootless podman requires the newuidmap and newgidmap programs to be installed. + +#### Symptom + +If you are running podman or buildah as a not root user, you get an error complaining about +a missing newuidmap executable. + +``` +podman run -ti fedora sh +cannot find newuidmap: exec: "newuidmap": executable file not found in $PATH +``` + +#### Solution + +Install a version of shadow-utils that includes these executables. Note RHEL7 and Centos 7 will not have support for this until RHEL7.7 is released. + +### 10) podman fails to run in user namespace because /etc/subuid is not properly populated. + +Rootless podman requires the user running it to have a range of UIDs listed in /etc/subuid and /etc/subgid. + +#### Symptom + +If you are running podman or buildah as a user, you get an error complaining about +a missing subuid ranges in /etc/subuid. + +``` +podman run -ti fedora sh +No subuid ranges found for user "johndoe" in /etc/subuid +``` + +#### Solution + +Update the /etc/subuid and /etc/subgid with fields for users that look like: + +``` +cat /etc/subuid +johndoe:100000:65536 +test:165536:65536 +``` + +The format of this file is USERNAME:UID:RANGE + +* 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. + +You should ensure that each user has a unique range of uids, because overlapping UIDs, +would potentially allow one user to attack another user. + +You could also use the usermod program to assign UIDs to a user. + +``` +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 +``` |