mirror of
https://github.com/containers/podman.git
synced 2025-05-21 09:05:56 +08:00
Allow users to expose ports from the pod to the host
we need to allow users to expose ports to the host for the purposes of networking, like a webserver. the port exposure must be done at the time the pod is created. strictly speaking, the port exposure occurs on the infra container. Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
@ -3,11 +3,15 @@ package main
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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/cri-o/ocicni/pkg/ocicni"
|
||||
"github.com/docker/go-connections/nat"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/sirupsen/logrus"
|
||||
"github.com/urfave/cli"
|
||||
@ -58,6 +62,10 @@ var podCreateFlags = []cli.Flag{
|
||||
Name: "pod-id-file",
|
||||
Usage: "Write the pod ID to the file",
|
||||
},
|
||||
cli.StringSliceFlag{
|
||||
Name: "publish, p",
|
||||
Usage: "Publish a container's port, or a range of ports, to the host (default [])",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "share",
|
||||
Usage: "A comma delimited list of kernel namespaces the pod will share",
|
||||
@ -102,6 +110,16 @@ func podCreateCmd(c *cli.Context) error {
|
||||
defer podIdFile.Close()
|
||||
defer podIdFile.Sync()
|
||||
}
|
||||
|
||||
if len(c.StringSlice("publish")) > 0 {
|
||||
if !c.BoolT("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.BoolT("infra") && c.IsSet("share") && c.String("share") != "none" && c.String("share") != "" {
|
||||
return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container")
|
||||
}
|
||||
@ -131,6 +149,14 @@ func podCreateCmd(c *cli.Context) error {
|
||||
options = append(options, nsOptions...)
|
||||
}
|
||||
|
||||
if len(c.StringSlice("publish")) > 0 {
|
||||
portBindings, err := CreatePortBindings(c.StringSlice("publish"))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
options = append(options, libpod.WithInfraContainerPorts(portBindings))
|
||||
|
||||
}
|
||||
// always have containers use pod cgroups
|
||||
// User Opt out is not yet supported
|
||||
options = append(options, libpod.WithPodCgroups())
|
||||
@ -152,3 +178,36 @@ func podCreateCmd(c *cli.Context) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands
|
||||
func CreatePortBindings(ports []string) ([]ocicni.PortMapping, error) {
|
||||
var portBindings []ocicni.PortMapping
|
||||
// The conversion from []string to natBindings is temporary while mheon reworks the port
|
||||
// deduplication code. Eventually that step will not be required.
|
||||
_, natBindings, err := nat.ParsePortSpecs(ports)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for containerPb, hostPb := range natBindings {
|
||||
var pm ocicni.PortMapping
|
||||
pm.ContainerPort = int32(containerPb.Int())
|
||||
for _, i := range hostPb {
|
||||
var hostPort int
|
||||
var err error
|
||||
pm.HostIP = i.HostIP
|
||||
if i.HostPort == "" {
|
||||
hostPort = containerPb.Int()
|
||||
} else {
|
||||
hostPort, err = strconv.Atoi(i.HostPort)
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "unable to convert host port to integer")
|
||||
}
|
||||
}
|
||||
|
||||
pm.HostPort = int32(hostPort)
|
||||
pm.Protocol = containerPb.Proto()
|
||||
portBindings = append(portBindings, pm)
|
||||
}
|
||||
}
|
||||
return portBindings, nil
|
||||
}
|
||||
|
@ -2178,12 +2178,14 @@ _podman_pod_create() {
|
||||
--cgroup-parent
|
||||
--infra-command
|
||||
--infra-image
|
||||
--share
|
||||
--podidfile
|
||||
--label-file
|
||||
--label
|
||||
-l
|
||||
--name
|
||||
--podidfile
|
||||
--publish
|
||||
-p
|
||||
--share
|
||||
"
|
||||
|
||||
local boolean_options="
|
||||
|
@ -51,6 +51,15 @@ Assign a name to the pod
|
||||
|
||||
Write the pod ID to the file
|
||||
|
||||
**-p**, **--publish**=[]
|
||||
|
||||
Publish a port or range of ports from the pod to the host
|
||||
|
||||
Format: `ip:hostPort:containerPort | ip::containerPort | hostPort:containerPort | containerPort`
|
||||
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.
|
||||
Use `podman port` to see the actual mapping: `podman port CONTAINER $CONTAINERPORT`
|
||||
|
||||
**--share**=""
|
||||
|
||||
A comma deliminated list of kernel namespaces to share. If none or "" is specified, no namespaces will be shared. The namespaces to choose from are ipc, net, pid, user, uts.
|
||||
|
@ -1295,3 +1295,14 @@ func WithInfraContainer() PodCreateOption {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithInfraContainerPorts tells the pod to add port bindings to the pause container
|
||||
func WithInfraContainerPorts(bindings []ocicni.PortMapping) PodCreateOption {
|
||||
return func(pod *Pod) error {
|
||||
if pod.valid {
|
||||
return ErrPodFinalized
|
||||
}
|
||||
pod.config.InfraContainer.PortBindings = bindings
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/containers/storage"
|
||||
"github.com/cri-o/ocicni/pkg/ocicni"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -96,7 +97,8 @@ type PodContainerInfo struct {
|
||||
|
||||
// InfraContainerConfig is the configuration for the pod's infra container
|
||||
type InfraContainerConfig struct {
|
||||
HasInfraContainer bool `json:"makeInfraContainer"`
|
||||
HasInfraContainer bool `json:"makeInfraContainer"`
|
||||
PortBindings []ocicni.PortMapping `json:"infraPortBindings"`
|
||||
}
|
||||
|
||||
// ID retrieves the pod's ID
|
||||
|
@ -6,6 +6,7 @@ package libpod
|
||||
|
||||
import (
|
||||
json "encoding/json"
|
||||
ocicni "github.com/cri-o/ocicni/pkg/ocicni"
|
||||
easyjson "github.com/mailru/easyjson"
|
||||
jlexer "github.com/mailru/easyjson/jlexer"
|
||||
jwriter "github.com/mailru/easyjson/jwriter"
|
||||
@ -721,6 +722,29 @@ func easyjsonBe091417DecodeGithubComContainersLibpodLibpod5(in *jlexer.Lexer, ou
|
||||
switch key {
|
||||
case "makeInfraContainer":
|
||||
out.HasInfraContainer = bool(in.Bool())
|
||||
case "infraPortBindings":
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
out.PortBindings = nil
|
||||
} else {
|
||||
in.Delim('[')
|
||||
if out.PortBindings == nil {
|
||||
if !in.IsDelim(']') {
|
||||
out.PortBindings = make([]ocicni.PortMapping, 0, 1)
|
||||
} else {
|
||||
out.PortBindings = []ocicni.PortMapping{}
|
||||
}
|
||||
} else {
|
||||
out.PortBindings = (out.PortBindings)[:0]
|
||||
}
|
||||
for !in.IsDelim(']') {
|
||||
var v6 ocicni.PortMapping
|
||||
easyjsonBe091417DecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in, &v6)
|
||||
out.PortBindings = append(out.PortBindings, v6)
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim(']')
|
||||
}
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
@ -745,5 +769,109 @@ func easyjsonBe091417EncodeGithubComContainersLibpodLibpod5(out *jwriter.Writer,
|
||||
}
|
||||
out.Bool(bool(in.HasInfraContainer))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"infraPortBindings\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
if in.PortBindings == nil && (out.Flags&jwriter.NilSliceAsEmpty) == 0 {
|
||||
out.RawString("null")
|
||||
} else {
|
||||
out.RawByte('[')
|
||||
for v7, v8 := range in.PortBindings {
|
||||
if v7 > 0 {
|
||||
out.RawByte(',')
|
||||
}
|
||||
easyjsonBe091417EncodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(out, v8)
|
||||
}
|
||||
out.RawByte(']')
|
||||
}
|
||||
}
|
||||
out.RawByte('}')
|
||||
}
|
||||
func easyjsonBe091417DecodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(in *jlexer.Lexer, out *ocicni.PortMapping) {
|
||||
isTopLevel := in.IsStart()
|
||||
if in.IsNull() {
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
in.Skip()
|
||||
return
|
||||
}
|
||||
in.Delim('{')
|
||||
for !in.IsDelim('}') {
|
||||
key := in.UnsafeString()
|
||||
in.WantColon()
|
||||
if in.IsNull() {
|
||||
in.Skip()
|
||||
in.WantComma()
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "hostPort":
|
||||
out.HostPort = int32(in.Int32())
|
||||
case "containerPort":
|
||||
out.ContainerPort = int32(in.Int32())
|
||||
case "protocol":
|
||||
out.Protocol = string(in.String())
|
||||
case "hostIP":
|
||||
out.HostIP = string(in.String())
|
||||
default:
|
||||
in.SkipRecursive()
|
||||
}
|
||||
in.WantComma()
|
||||
}
|
||||
in.Delim('}')
|
||||
if isTopLevel {
|
||||
in.Consumed()
|
||||
}
|
||||
}
|
||||
func easyjsonBe091417EncodeGithubComContainersLibpodVendorGithubComCriOOcicniPkgOcicni(out *jwriter.Writer, in ocicni.PortMapping) {
|
||||
out.RawByte('{')
|
||||
first := true
|
||||
_ = first
|
||||
{
|
||||
const prefix string = ",\"hostPort\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.Int32(int32(in.HostPort))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"containerPort\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.Int32(int32(in.ContainerPort))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"protocol\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.String(string(in.Protocol))
|
||||
}
|
||||
{
|
||||
const prefix string = ",\"hostIP\":"
|
||||
if first {
|
||||
first = false
|
||||
out.RawString(prefix[1:])
|
||||
} else {
|
||||
out.RawString(prefix)
|
||||
}
|
||||
out.String(string(in.HostIP))
|
||||
}
|
||||
out.RawByte('}')
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import (
|
||||
|
||||
"github.com/containers/libpod/libpod/image"
|
||||
"github.com/containers/libpod/pkg/rootless"
|
||||
"github.com/cri-o/ocicni/pkg/ocicni"
|
||||
spec "github.com/opencontainers/runtime-spec/specs-go"
|
||||
"github.com/opencontainers/runtime-tools/generate"
|
||||
)
|
||||
@ -50,9 +49,8 @@ func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID
|
||||
options = append(options, withIsInfra())
|
||||
|
||||
// Since user namespace sharing is not implemented, we only need to check if it's rootless
|
||||
portMappings := make([]ocicni.PortMapping, 0)
|
||||
networks := make([]string, 0)
|
||||
options = append(options, WithNetNS(portMappings, isRootless, networks))
|
||||
options = append(options, WithNetNS(p.config.InfraContainer.PortBindings, isRootless, networks))
|
||||
|
||||
return r.newContainer(ctx, g.Config, options...)
|
||||
}
|
||||
|
@ -335,7 +335,6 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime) ([]lib
|
||||
}
|
||||
options = append(options, runtime.WithPod(pod))
|
||||
}
|
||||
|
||||
if len(c.PortBindings) > 0 {
|
||||
portBindings, err = c.CreatePortBindings()
|
||||
if err != nil {
|
||||
|
@ -80,4 +80,43 @@ var _ = Describe("Podman pod create", func() {
|
||||
check.WaitWithDefaultTimeout()
|
||||
Expect(len(check.OutputToStringArray())).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman create pod without network portbindings", func() {
|
||||
name := "test"
|
||||
session := podmanTest.Podman([]string{"pod", "create", "--name", name})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
pod := session.OutputToString()
|
||||
|
||||
webserver := podmanTest.Podman([]string{"run", "--pod", pod, "-dt", nginx})
|
||||
webserver.WaitWithDefaultTimeout()
|
||||
Expect(webserver.ExitCode()).To(Equal(0))
|
||||
|
||||
check := SystemExec("nc", []string{"-z", "localhost", "80"})
|
||||
check.WaitWithDefaultTimeout()
|
||||
Expect(check.ExitCode()).To(Equal(1))
|
||||
})
|
||||
|
||||
It("podman create pod with network portbindings", func() {
|
||||
name := "test"
|
||||
session := podmanTest.Podman([]string{"pod", "create", "--name", name, "-p", "80:80"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(0))
|
||||
pod := session.OutputToString()
|
||||
|
||||
webserver := podmanTest.Podman([]string{"run", "--pod", pod, "-dt", nginx})
|
||||
webserver.WaitWithDefaultTimeout()
|
||||
Expect(webserver.ExitCode()).To(Equal(0))
|
||||
|
||||
check := SystemExec("nc", []string{"-z", "localhost", "80"})
|
||||
check.WaitWithDefaultTimeout()
|
||||
Expect(check.ExitCode()).To(Equal(0))
|
||||
})
|
||||
|
||||
It("podman create pod with no infra but portbindings should fail", func() {
|
||||
name := "test"
|
||||
session := podmanTest.Podman([]string{"pod", "create", "--infra=false", "--name", name, "-p", "80:80"})
|
||||
session.WaitWithDefaultTimeout()
|
||||
Expect(session.ExitCode()).To(Equal(125))
|
||||
})
|
||||
})
|
||||
|
Reference in New Issue
Block a user