Browse Source

Merge pull request #4441 from crosbymichael/add-net-flag

Add --net flag to docker run and allow host network stack
Guillaume J. Charmes 11 years ago
parent
commit
70fef1460a

+ 91 - 14
daemon/container.go

@@ -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)
@@ -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
@@ -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) {
@@ -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
 	}
@@ -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
@@ -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
 	}
 
@@ -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 (
@@ -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 {
@@ -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")
+	}
+}

+ 4 - 2
daemon/execdriver/driver.go

@@ -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 {

+ 5 - 4
daemon/execdriver/lxc/init.go

@@ -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

+ 3 - 2
daemon/execdriver/lxc/lxc_template.go

@@ -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}}

+ 19 - 0
daemon/execdriver/native/create.go

@@ -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"
@@ -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,
@@ -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
 }
 

+ 44 - 2
docs/sources/reference/run.md

@@ -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
@@ -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.  
+
+#### 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

+ 15 - 0
runconfig/hostconfig.go

@@ -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
@@ -17,6 +30,7 @@ type HostConfig struct {
 	Dns             []string
 	DnsSearch       []string
 	VolumesFrom     []string
+	NetworkMode     NetworkMode
 }
 
 func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
@@ -24,6 +38,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *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)

+ 26 - 5
runconfig/parse.go

@@ -2,14 +2,15 @@ package runconfig
 
 import (
 	"fmt"
+	"io/ioutil"
+	"path"
+	"strings"
+
 	"github.com/dotcloud/docker/nat"
 	"github.com/dotcloud/docker/opts"
 	flag "github.com/dotcloud/docker/pkg/mflag"
 	"github.com/dotcloud/docker/pkg/sysinfo"
 	"github.com/dotcloud/docker/utils"
-	"io/ioutil"
-	"path"
-	"strings"
 )
 
 var (
@@ -49,7 +50,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 
 		flAutoRemove      = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
 		flDetach          = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id")
-		flNetwork         = cmd.Bool([]string{"n", "-networking"}, true, "Enable networking for this container")
+		flNetwork         = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
 		flPrivileged      = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
 		flPublishAll      = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to the host interfaces")
 		flStdin           = cmd.Bool([]string{"i", "-interactive"}, false, "Keep stdin open even if not attached")
@@ -61,7 +62,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		flUser            = cmd.String([]string{"u", "-user"}, "", "Username or UID")
 		flWorkingDir      = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
 		flCpuShares       = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
-
+		flNetMode         = cmd.String([]string{"-net"}, "bridge", "Set the Network mode for the container ('bridge': creates a new network stack for the container on the docker bridge, 'none': no networking for this container, 'container:<name|id>': reuses another container network stack)")
 		// For documentation purpose
 		_ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)")
 		_ = cmd.String([]string{"#name", "-name"}, "", "Assign a name to the container")
@@ -197,6 +198,11 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 	// boo, there's no debug output for docker run
 	//utils.Debugf("Environment variables for the container: %#v", envVariables)
 
+	netMode, err := parseNetMode(*flNetMode)
+	if err != nil {
+		return nil, nil, cmd, fmt.Errorf("--net: invalid net mode: %v", err)
+	}
+
 	config := &Config{
 		Hostname:        hostname,
 		Domainname:      domainname,
@@ -230,6 +236,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
 		Dns:             flDns.GetAll(),
 		DnsSearch:       flDnsSearch.GetAll(),
 		VolumesFrom:     flVolumesFrom.GetAll(),
+		NetworkMode:     netMode,
 	}
 
 	if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
@@ -274,3 +281,17 @@ func parseKeyValueOpts(opts opts.ListOpts) ([]utils.KeyValuePair, error) {
 	}
 	return out, nil
 }
+
+func parseNetMode(netMode string) (NetworkMode, error) {
+	parts := strings.Split(netMode, ":")
+	switch mode := parts[0]; mode {
+	case "bridge", "none", "host":
+	case "container":
+		if len(parts) < 2 || parts[1] == "" {
+			return "", fmt.Errorf("invalid container format container:<name|id>")
+		}
+	default:
+		return "", fmt.Errorf("invalid --net: %s", netMode)
+	}
+	return NetworkMode(netMode), nil
+}

+ 2 - 1
runconfig/parse_test.go

@@ -1,8 +1,9 @@
 package runconfig
 
 import (
-	"github.com/dotcloud/docker/utils"
 	"testing"
+
+	"github.com/dotcloud/docker/utils"
 )
 
 func TestParseLxcConfOpt(t *testing.T) {