Skip to content

Commit

Permalink
Allow users to expose ports from the pod to the host
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
baude committed Nov 19, 2018
1 parent 4eecc8c commit b2ab6d6
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 5 deletions.
25 changes: 25 additions & 0 deletions cmd/podman/pod_create.go
Expand Up @@ -2,12 +2,14 @@ package main

import (
"fmt"
"github.com/containers/libpod/pkg/rootless"
"os"
"strings"

"github.com/containers/libpod/cmd/podman/libpodruntime"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/urfave/cli"
Expand Down Expand Up @@ -58,6 +60,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",
Expand Down Expand Up @@ -102,6 +108,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")
}
Expand Down Expand Up @@ -131,6 +147,15 @@ func podCreateCmd(c *cli.Context) error {
options = append(options, nsOptions...)
}

var portBindings map[nat.Port][]nat.PortBinding
if len(c.StringSlice("publish")) > 0 {
_, portBindings, err = nat.ParsePortSpecs(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())
Expand Down
6 changes: 4 additions & 2 deletions completions/bash/podman
Expand Up @@ -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="
Expand Down
9 changes: 9 additions & 0 deletions docs/podman-pod-create.1.md
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions libpod/options.go
Expand Up @@ -10,6 +10,7 @@ import (
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-connections/nat"
"github.com/pkg/errors"
)

Expand Down Expand Up @@ -1295,3 +1296,14 @@ func WithInfraContainer() PodCreateOption {
return nil
}
}

// WithInfraContainerPorts tells the pod to add port bindings to the pause container
func WithInfraContainerPorts(bindings map[nat.Port][]nat.PortBinding) PodCreateOption {
return func(pod *Pod) error {
if pod.valid {
return ErrPodFinalized
}
pod.config.InfraContainer.PortBindings = bindings
return nil
}
}
4 changes: 3 additions & 1 deletion libpod/pod.go
@@ -1,6 +1,7 @@
package libpod

import (
"github.com/docker/go-connections/nat"
"time"

"github.com/containers/storage"
Expand Down Expand Up @@ -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 map[nat.Port][]nat.PortBinding `json:"infraPortBindings"`
}

// ID retrieves the pod's ID
Expand Down
138 changes: 138 additions & 0 deletions libpod/pod_easyjson.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

34 changes: 33 additions & 1 deletion libpod/runtime_pod_infra_linux.go
Expand Up @@ -4,6 +4,8 @@ package libpod

import (
"context"
"github.com/pkg/errors"
"strconv"

"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/pkg/rootless"
Expand All @@ -18,6 +20,33 @@ const (
IDTruncLength = 12
)

// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands
func (p *Pod) CreatePortBindings() ([]ocicni.PortMapping, error) {
var portBindings []ocicni.PortMapping
for containerPb, hostPb := range p.config.InfraContainer.PortBindings {
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
}

func (r *Runtime) makeInfraContainer(ctx context.Context, p *Pod, imgName, imgID string) (*Container, error) {

// Set up generator for infra container defaults
Expand Down Expand Up @@ -50,7 +79,10 @@ 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)
portMappings, err := p.CreatePortBindings()
if err != nil {
return nil, err
}
networks := make([]string, 0)
options = append(options, WithNetNS(portMappings, isRootless, networks))

Expand Down
1 change: 0 additions & 1 deletion pkg/spec/createconfig.go
Expand Up @@ -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 {
Expand Down
39 changes: 39 additions & 0 deletions test/e2e/pod_create_test.go
Expand Up @@ -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))
})
})

0 comments on commit b2ab6d6

Please sign in to comment.