From d59ef411949564518e3b5d0b5bbc1625ede45486 Mon Sep 17 00:00:00 2001 From: Valentin Rothberg Date: Mon, 6 Jul 2020 11:14:33 +0200 Subject: auto-update: clarify systemd-unit requirements Clarify in the help message and the man page that auto updates only work with systemd units that are similar to the ones from `generate systemd --new`. Units that merely start/stop a container do not work as they will use the same image. Fixes: #6793 Signed-off-by: Valentin Rothberg --- docs/source/markdown/podman-auto-update.1.md | 3 +++ 1 file changed, 3 insertions(+) (limited to 'docs/source') diff --git a/docs/source/markdown/podman-auto-update.1.md b/docs/source/markdown/podman-auto-update.1.md index 90e581e42..73d75be1f 100644 --- a/docs/source/markdown/podman-auto-update.1.md +++ b/docs/source/markdown/podman-auto-update.1.md @@ -23,6 +23,9 @@ Note that `podman auto-update` relies on systemd and requires a fully-qualified This enforcement is necessary to know which image to actually check and pull. If an image ID was used, Podman would not know which image to check/pull anymore. +Moreover, the systemd units are expected to be generated with `podman-generate-systemd --new`, or similar units that create new containers in order to run the updated images. +Systemd units that start and stop a container cannot run a new image. + ## OPTIONS **--authfile**=*path* -- cgit v1.2.3-54-g00ecf From 56c873c14e68c0cb63488ad7ded60e831c2d4194 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 29 Jun 2020 14:26:07 -0400 Subject: Add a note on the APIs supported by `system service` This makes it clear that we target compatibility with a specific Docker version (v1.40), but do not reject other versions. It also adds a link to documentation on the Podman-specific API. Signed-off-by: Matthew Heon --- docs/source/markdown/podman-system-service.1.md | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'docs/source') diff --git a/docs/source/markdown/podman-system-service.1.md b/docs/source/markdown/podman-system-service.1.md index 3ae414f7a..7d18a6832 100644 --- a/docs/source/markdown/podman-system-service.1.md +++ b/docs/source/markdown/podman-system-service.1.md @@ -13,6 +13,10 @@ If no endpoint is provided, defaults will be used. The default endpoint for a r service is *unix:/run/podman/podman.sock* and rootless is *unix:/$XDG_RUNTIME_DIR/podman/podman.sock* (for example *unix:/run/user/1000/podman/podman.sock*) +The REST API provided by **podman system service** is split into two parts: a compatibility layer offering support for the Docker v1.40 API, and a Podman-native Libpod layer. +Documentation for the latter is available at *https://docs.podman.io/en/latest/_static/api.html*. +Both APIs are versioned, but the server will not reject requests with an unsupported version set. + ## OPTIONS **--time**, **-t** -- cgit v1.2.3-54-g00ecf From 1c02d5ab890ba7de6d0ad0603438107e086c18b7 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Sun, 28 Jun 2020 11:06:46 -0400 Subject: Allow empty host port in --publish flag I didn't believe that this was actually legal, but it looks like it is. And, unlike our previous understanding (host port being empty means just use container port), empty host port actually carries the same meaning as `--expose` + `--publish-all` (that is, assign a random host port to the given container port). This requires a significant rework of our port handling code to handle this new case. I don't foresee this being commonly used, so I optimized having a fixed port number as fast path, which this random assignment code running after the main port handling code only if necessary. Fixes #6806 Signed-off-by: Matthew Heon --- cmd/podman/common/util.go | 26 ++++---- docs/source/markdown/podman-create.1.md | 2 + docs/source/markdown/podman-run.1.md | 3 + pkg/specgen/generate/ports.go | 112 ++++++++++++++++++++++++++------ pkg/specgen/specgen.go | 3 +- test/e2e/run_networking_test.go | 24 +++++++ 6 files changed, 136 insertions(+), 34 deletions(-) (limited to 'docs/source') diff --git a/cmd/podman/common/util.go b/cmd/podman/common/util.go index a3ce6198c..e21e349d9 100644 --- a/cmd/podman/common/util.go +++ b/cmd/podman/common/util.go @@ -184,22 +184,24 @@ func parseSplitPort(hostIP, hostPort *string, ctrPort string, protocol *string) } if hostPort != nil { if *hostPort == "" { - return newPort, errors.Errorf("must provide a non-empty container host port to publish") - } - hostStart, hostLen, err := parseAndValidateRange(*hostPort) - if err != nil { - return newPort, errors.Wrapf(err, "error parsing host port") - } - if hostLen != ctrLen { - return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen) + // Set 0 as a placeholder. The server side of Specgen + // will find a random, open, unused port to use. + newPort.HostPort = 0 + } else { + hostStart, hostLen, err := parseAndValidateRange(*hostPort) + if err != nil { + return newPort, errors.Wrapf(err, "error parsing host port") + } + if hostLen != ctrLen { + return newPort, errors.Errorf("host and container port ranges have different lengths: %d vs %d", hostLen, ctrLen) + } + newPort.HostPort = hostStart } - newPort.HostPort = hostStart + } else { + newPort.HostPort = newPort.ContainerPort } hport := newPort.HostPort - if hport == 0 { - hport = newPort.ContainerPort - } logrus.Debugf("Adding port mapping from %d to %d length %d protocol %q", hport, newPort.ContainerPort, newPort.Range, newPort.Protocol) return newPort, nil diff --git a/docs/source/markdown/podman-create.1.md b/docs/source/markdown/podman-create.1.md index 3ec91a3ad..7c2903e33 100644 --- a/docs/source/markdown/podman-create.1.md +++ b/docs/source/markdown/podman-create.1.md @@ -623,6 +623,8 @@ When specifying ranges for both, the number of container ports in the range must (e.g., `podman run -p 1234-1236:1222-1224 --name thisWorks -t busybox` but not `podman run -p 1230-1236:1230-1240 --name RangeContainerPortsBiggerThanRangeHostPorts -t busybox`) With ip: `podman run -p 127.0.0.1:$HOSTPORT:$CONTAINERPORT --name CONTAINER -t someimage` +Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`). +If it is not, the container port will be randomly assigned a port on the host. Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT` **--publish-all**, **-P**=*true|false* diff --git a/docs/source/markdown/podman-run.1.md b/docs/source/markdown/podman-run.1.md index 7e91a06a3..6b6ab03f6 100644 --- a/docs/source/markdown/podman-run.1.md +++ b/docs/source/markdown/podman-run.1.md @@ -632,6 +632,9 @@ Both hostPort and containerPort can be specified as a range of ports. When specifying ranges for both, the number of container ports in the range must match the number of host ports in the range. +Host port does not have to be specified (e.g. `podman run -p 127.0.0.1::80`). +If it is not, the container port will be randomly assigned a port on the host. + Use **podman port** to see the actual mapping: **podman port $CONTAINER $CONTAINERPORT**. **--publish-all**, **-P**=**true**|**false** diff --git a/pkg/specgen/generate/ports.go b/pkg/specgen/generate/ports.go index b529fd4cd..9412ecfbf 100644 --- a/pkg/specgen/generate/ports.go +++ b/pkg/specgen/generate/ports.go @@ -43,6 +43,8 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, containerPortValidate[proto] = make(map[string]map[uint16]uint16) } + postAssignHostPort := false + // Iterate through all port mappings, generating OCICNI PortMapping // structs and validating there is no overlap. for _, port := range portMappings { @@ -71,9 +73,6 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, return nil, nil, nil, errors.Errorf("container port number must be non-0") } hostPort := port.HostPort - if hostPort == 0 { - hostPort = containerPort - } if uint32(len-1)+uint32(containerPort) > 65535 { return nil, nil, nil, errors.Errorf("container port range exceeds maximum allowable port number") } @@ -105,26 +104,42 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, cPort := containerPort + index hPort := hostPort + index - if cPort == 0 || hPort == 0 { - return nil, nil, nil, errors.Errorf("host and container ports cannot be 0") - } - - testCPort := ctrPortMap[cPort] - if testCPort != 0 && testCPort != hPort { - // This is an attempt to redefine a port - return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p) + if cPort == 0 { + return nil, nil, nil, errors.Errorf("container port cannot be 0") } - ctrPortMap[cPort] = hPort - testHPort := hostPortMap[hPort] - if testHPort != 0 && testHPort != cPort { - return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p) - } - hostPortMap[hPort] = cPort - - // If we have an exact duplicate, just continue - if testCPort == hPort && testHPort == cPort { - continue + // Host port is allowed to be 0. If it is, we + // select a random port on the host. + // This will happen *after* all other ports are + // placed, to ensure we don't accidentally + // select a port that a later mapping wanted. + if hPort == 0 { + // If we already have a host port + // assigned to their container port - + // just use that. + if ctrPortMap[cPort] != 0 { + hPort = ctrPortMap[cPort] + } else { + postAssignHostPort = true + } + } else { + testCPort := ctrPortMap[cPort] + if testCPort != 0 && testCPort != hPort { + // This is an attempt to redefine a port + return nil, nil, nil, errors.Errorf("conflicting port mappings for container port %d (protocol %s)", cPort, p) + } + ctrPortMap[cPort] = hPort + + testHPort := hostPortMap[hPort] + if testHPort != 0 && testHPort != cPort { + return nil, nil, nil, errors.Errorf("conflicting port mappings for host port %d (protocol %s)", hPort, p) + } + hostPortMap[hPort] = cPort + + // If we have an exact duplicate, just continue + if testCPort == hPort && testHPort == cPort { + continue + } } // We appear to be clear. Make an OCICNI port @@ -142,6 +157,61 @@ func parsePortMapping(portMappings []specgen.PortMapping) ([]ocicni.PortMapping, } } + // Handle any 0 host ports now by setting random container ports. + if postAssignHostPort { + remadeMappings := make([]ocicni.PortMapping, 0, len(finalMappings)) + + // Iterate over all + for _, p := range finalMappings { + if p.HostPort != 0 { + remadeMappings = append(remadeMappings, p) + continue + } + + hostIPMap := hostPortValidate[p.Protocol] + ctrIPMap := containerPortValidate[p.Protocol] + + hostPortMap, ok := hostIPMap[p.HostIP] + if !ok { + hostPortMap = make(map[uint16]uint16) + hostIPMap[p.HostIP] = hostPortMap + } + ctrPortMap, ok := ctrIPMap[p.HostIP] + if !ok { + ctrPortMap = make(map[uint16]uint16) + ctrIPMap[p.HostIP] = ctrPortMap + } + + // See if container port has been used elsewhere + if ctrPortMap[uint16(p.ContainerPort)] != 0 { + // Duplicate definition. Let's not bother + // including it. + continue + } + + // Max retries to ensure we don't loop forever. + for i := 0; i < 15; i++ { + candidate, err := getRandomPort() + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "error getting candidate host port for container port %d", p.ContainerPort) + } + + if hostPortMap[uint16(candidate)] == 0 { + logrus.Debugf("Successfully assigned container port %d to host port %d (IP %s Protocol %s)", p.ContainerPort, candidate, p.HostIP, p.Protocol) + hostPortMap[uint16(candidate)] = uint16(p.ContainerPort) + ctrPortMap[uint16(p.ContainerPort)] = uint16(candidate) + p.HostPort = int32(candidate) + break + } + } + if p.HostPort == 0 { + return nil, nil, nil, errors.Errorf("could not find open host port to map container port %d to", p.ContainerPort) + } + remadeMappings = append(remadeMappings, p) + } + return remadeMappings, containerPortValidate, hostPortValidate, nil + } + return finalMappings, containerPortValidate, hostPortValidate, nil } diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 03e840ab4..327c15c5a 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -430,7 +430,8 @@ type PortMapping struct { ContainerPort uint16 `json:"container_port"` // HostPort is the port number that will be forwarded from the host into // the container. - // If omitted, will be assumed to be identical to + // If omitted, a random port on the host (guaranteed to be over 1024) + // will be assigned. HostPort uint16 `json:"host_port,omitempty"` // Range is the number of ports that will be forwarded, starting at // HostPort and ContainerPort and counting up. diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index dd018b910..6c049c5c1 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -184,6 +184,30 @@ var _ = Describe("Podman run networking", func() { Expect(inspectOut[0].NetworkSettings.Ports["80/tcp"][0].HostIP).To(Equal("")) }) + It("podman run -p 127.0.0.1::8080/udp", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "-p", "127.0.0.1::8080/udp", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports["8080/udp"])).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports["8080/udp"][0].HostPort).To(Not(Equal("8080"))) + Expect(inspectOut[0].NetworkSettings.Ports["8080/udp"][0].HostIP).To(Equal("127.0.0.1")) + }) + + It("podman run -p :8080", func() { + name := "testctr" + session := podmanTest.Podman([]string{"create", "-t", "-p", ":8080", "--name", name, ALPINE, "/bin/sh"}) + session.WaitWithDefaultTimeout() + inspectOut := podmanTest.InspectContainer(name) + Expect(len(inspectOut)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports)).To(Equal(1)) + Expect(len(inspectOut[0].NetworkSettings.Ports["8080/tcp"])).To(Equal(1)) + Expect(inspectOut[0].NetworkSettings.Ports["8080/tcp"][0].HostPort).To(Not(Equal("8080"))) + Expect(inspectOut[0].NetworkSettings.Ports["8080/tcp"][0].HostIP).To(Equal("")) + }) + It("podman run network expose host port 80 to container port 8000", func() { SkipIfRootless() session := podmanTest.Podman([]string{"run", "-dt", "-p", "80:8000", ALPINE, "/bin/sh"}) -- cgit v1.2.3-54-g00ecf