Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add --net flag to docker run and allow host network stack #4441

Merged
merged 7 commits into from
May 5, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 91 additions & 14 deletions daemon/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -325,7 +325,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
})
}

func populateCommand(c *Container, env []string) {
func populateCommand(c *Container, env []string) error {
var (
en *execdriver.Network
context = make(map[string][]string)
Expand All @@ -338,14 +338,29 @@ func populateCommand(c *Container, env []string) {
Interface: nil,
}

if !c.Config.NetworkDisabled {
network := c.NetworkSettings
en.Interface = &execdriver.NetworkInterface{
Gateway: network.Gateway,
Bridge: network.Bridge,
IPAddress: network.IPAddress,
IPPrefixLen: network.IPPrefixLen,
parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2)
switch parts[0] {
case "none":
case "host":
en.HostNetworking = true
case "bridge", "": // empty string to support existing containers
if !c.Config.NetworkDisabled {
network := c.NetworkSettings
en.Interface = &execdriver.NetworkInterface{
Gateway: network.Gateway,
Bridge: network.Bridge,
IPAddress: network.IPAddress,
IPPrefixLen: network.IPPrefixLen,
}
}
case "container":
nc, err := c.getNetworkedContainer()
if err != nil {
return err
}
en.ContainerID = nc.ID
default:
return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode)
}

// TODO: this can be removed after lxc-conf is fully deprecated
Expand All @@ -372,6 +387,7 @@ func populateCommand(c *Container, env []string) {
}
c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
c.command.Env = env
return nil
}

func (container *Container) Start() (err error) {
Expand Down Expand Up @@ -415,7 +431,9 @@ func (container *Container) Start() (err error) {
if err := container.setupWorkingDirectory(); err != nil {
return err
}
populateCommand(container, env)
if err := populateCommand(container, env); err != nil {
return err
}
if err := setupMountsForContainer(container); err != nil {
return err
}
Expand Down Expand Up @@ -485,9 +503,18 @@ func (container *Container) StderrLogPipe() io.ReadCloser {
return utils.NewBufReader(reader)
}

func (container *Container) buildHostnameAndHostsFiles(IP string) {
func (container *Container) buildHostname() {
container.HostnamePath = path.Join(container.root, "hostname")
ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)

if container.Config.Domainname != "" {
ioutil.WriteFile(container.HostnamePath, []byte(fmt.Sprintf("%s.%s\n", container.Config.Hostname, container.Config.Domainname)), 0644)
} else {
ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
}
}

func (container *Container) buildHostnameAndHostsFiles(IP string) {
container.buildHostname()

hostsContent := []byte(`
127.0.0.1 localhost
Expand All @@ -505,12 +532,12 @@ ff02::2 ip6-allrouters
} else if !container.Config.NetworkDisabled {
hostsContent = append([]byte(fmt.Sprintf("%s\t%s\n", IP, container.Config.Hostname)), hostsContent...)
}

ioutil.WriteFile(container.HostsPath, hostsContent, 0644)
}

func (container *Container) allocateNetwork() error {
if container.Config.NetworkDisabled {
mode := container.hostConfig.NetworkMode
if container.Config.NetworkDisabled || mode.IsContainer() || mode.IsHost() {
return nil
}

Expand Down Expand Up @@ -963,14 +990,22 @@ func (container *Container) setupContainerDns() error {
if container.ResolvConfPath != "" {
return nil
}

var (
config = container.hostConfig
daemon = container.daemon
)

if config.NetworkMode == "host" {
container.ResolvConfPath = "/etc/resolv.conf"
return nil
}

resolvConf, err := utils.GetResolvConf()
if err != nil {
return err
}

// If custom dns exists, then create a resolv.conf for the container
if len(config.Dns) > 0 || len(daemon.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(daemon.config.DnsSearch) > 0 {
var (
Expand Down Expand Up @@ -1010,7 +1045,32 @@ func (container *Container) setupContainerDns() error {
}

func (container *Container) initializeNetworking() error {
if container.daemon.config.DisableNetwork {
var err error
if container.hostConfig.NetworkMode.IsHost() {
container.Config.Hostname, err = os.Hostname()
if err != nil {
return err
}

parts := strings.SplitN(container.Config.Hostname, ".", 2)
if len(parts) > 1 {
container.Config.Hostname = parts[0]
container.Config.Domainname = parts[1]
}
container.HostsPath = "/etc/hosts"

container.buildHostname()
} else if container.hostConfig.NetworkMode.IsContainer() {
// we need to get the hosts files from the container to join
nc, err := container.getNetworkedContainer()
if err != nil {
return err
}
container.HostsPath = nc.HostsPath
container.ResolvConfPath = nc.ResolvConfPath
container.Config.Hostname = nc.Config.Hostname
container.Config.Domainname = nc.Config.Domainname
} else if container.daemon.config.DisableNetwork {
container.Config.NetworkDisabled = true
container.buildHostnameAndHostsFiles("127.0.1.1")
} else {
Expand Down Expand Up @@ -1219,3 +1279,20 @@ func (container *Container) GetMountLabel() string {
}
return container.MountLabel
}

func (container *Container) getNetworkedContainer() (*Container, error) {
parts := strings.SplitN(string(container.hostConfig.NetworkMode), ":", 2)
switch parts[0] {
case "container":
nc := container.daemon.Get(parts[1])
if nc == nil {
return nil, fmt.Errorf("no such container to join network: %s", parts[1])
}
if !nc.State.IsRunning() {
return nil, fmt.Errorf("cannot join network of a non running container: %s", parts[1])
}
return nc, nil
default:
return nil, fmt.Errorf("network mode not set to container")
}
}
6 changes: 4 additions & 2 deletions daemon/execdriver/driver.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,10 @@ type Driver interface {

// Network settings of the container
type Network struct {
Interface *NetworkInterface `json:"interface"` // if interface is nil then networking is disabled
Mtu int `json:"mtu"`
Interface *NetworkInterface `json:"interface"` // if interface is nil then networking is disabled
Mtu int `json:"mtu"`
ContainerID string `json:"container_id"` // id of the container to join network.
HostNetworking bool `json:"host_networking"`
}

type NetworkInterface struct {
Expand Down
9 changes: 5 additions & 4 deletions daemon/execdriver/lxc/init.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,16 @@ package lxc
import (
"encoding/json"
"fmt"
"github.com/dotcloud/docker/daemon/execdriver"
"github.com/dotcloud/docker/pkg/netlink"
"github.com/dotcloud/docker/pkg/user"
"github.com/syndtr/gocapability/capability"
"io/ioutil"
"net"
"os"
"strings"
"syscall"

"github.com/dotcloud/docker/daemon/execdriver"
"github.com/dotcloud/docker/pkg/netlink"
"github.com/dotcloud/docker/pkg/user"
"github.com/syndtr/gocapability/capability"
)

// Clear environment pollution introduced by lxc-start
Expand Down
5 changes: 3 additions & 2 deletions daemon/execdriver/lxc/lxc_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,13 @@ const LxcTemplate = `
lxc.network.type = veth
lxc.network.link = {{.Network.Interface.Bridge}}
lxc.network.name = eth0
{{else}}
lxc.network.mtu = {{.Network.Mtu}}
{{else if not .Network.HostNetworking}}
# network is disabled (-n=false)
lxc.network.type = empty
lxc.network.flags = up
{{end}}
lxc.network.mtu = {{.Network.Mtu}}
{{end}}

# root filesystem
{{$ROOTFS := .Rootfs}}
Expand Down
19 changes: 19 additions & 0 deletions daemon/execdriver/native/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package native
import (
"fmt"
"os"
"path/filepath"

"github.com/dotcloud/docker/daemon/execdriver"
"github.com/dotcloud/docker/daemon/execdriver/native/configuration"
Expand Down Expand Up @@ -52,6 +53,10 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container
}

func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver.Command) error {
if c.Network.HostNetworking {
container.Namespaces.Get("NEWNET").Enabled = false
return nil
}
container.Networks = []*libcontainer.Network{
{
Mtu: c.Network.Mtu,
Expand All @@ -75,6 +80,20 @@ func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver.
}
container.Networks = append(container.Networks, &vethNetwork)
}

if c.Network.ContainerID != "" {
cmd := d.activeContainers[c.Network.ContainerID]
if cmd == nil || cmd.Process == nil {
return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID)
}
nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net")
container.Networks = append(container.Networks, &libcontainer.Network{
Type: "netns",
Context: libcontainer.Context{
"nspath": nspath,
},
})
}
return nil
}

Expand Down
46 changes: 44 additions & 2 deletions docs/sources/reference/run.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,8 +136,8 @@ PID files):

## Network Settings

-n=true : Enable networking for this container
--dns=[] : Set custom dns servers for the container
--dns=[] : Set custom dns servers for the container
--net=bridge : Set the network mode

By default, all containers have networking enabled and they can make any
outgoing connections. The operator can completely disable networking
Expand All @@ -148,6 +148,48 @@ files or STDIN/STDOUT only.
Your container will use the same DNS servers as the host by default, but
you can override this with `--dns`.

Supported networking modes are:

* none - no networking in the container
* bridge - (default) connect the container to the bridge via veth interfaces
* host - use the host's network stack inside the container
* container - use another container's network stack

#### Mode: none
With the networking mode set to `none` a container will not have a access to
any external routes. The container will still have a `loopback` interface
enabled in the container but it does not have any routes to external traffic.

#### Mode: bridge
With the networking mode set to `bridge` a container will use docker's default
networking setup. A bridge is setup on the host, commonly named `docker0`,
and a pair of veth interfaces will be created for the container. One side of
the veth pair will remain on the host attached to the bridge while the other
side of the pair will be placed inside the container's namespaces in addition
to the `loopback` interface. An IP address will be allocated for containers
on the bridge's network and trafic will be routed though this bridge to the
container.

#### Mode: host
With the networking mode set to `host` a container will share the host's
network stack and all interfaces from the host will be available to the
container. The container's hostname will match the hostname on the host
system. Publishing ports and linking to other containers will not work
when sharing the host's network stack.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the container be limited to listening on the exposed ports? or does it get to do anything it likes?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Anything it wants because it has access to the full interfaces


#### Mode: container
With the networking mode set to `container` a container will share the
network stack of another container. The other container's name must be
provided in the format of `--net container:<name|id>`.

Example running a redis container with redis binding to localhost then
running the redis-cli and connecting to the redis server over the
localhost interface.

$ docker run -d --name redis example/redis --bind 127.0.0.1
$ # use the redis container's network stack to access localhost
$ docker run --rm -ti --net container:redis example/redis-cli -h 127.0.0.1

## Clean Up (–rm)

By default a container's file system persists even after the container
Expand Down
15 changes: 15 additions & 0 deletions runconfig/hostconfig.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
package runconfig

import (
"strings"

"github.com/dotcloud/docker/engine"
"github.com/dotcloud/docker/nat"
"github.com/dotcloud/docker/utils"
)

type NetworkMode string

func (n NetworkMode) IsHost() bool {
return n == "host"
}

func (n NetworkMode) IsContainer() bool {
parts := strings.SplitN(string(n), ":", 2)
return len(parts) > 1 && parts[0] == "container"
}

type HostConfig struct {
Binds []string
ContainerIDFile string
Expand All @@ -17,13 +30,15 @@ type HostConfig struct {
Dns []string
DnsSearch []string
VolumesFrom []string
NetworkMode NetworkMode
}

func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
hostConfig := &HostConfig{
ContainerIDFile: job.Getenv("ContainerIDFile"),
Privileged: job.GetenvBool("Privileged"),
PublishAllPorts: job.GetenvBool("PublishAllPorts"),
NetworkMode: NetworkMode(job.Getenv("NetworkMode")),
}
job.GetenvJson("LxcConf", &hostConfig.LxcConf)
job.GetenvJson("PortBindings", &hostConfig.PortBindings)
Expand Down
Loading