diff --git a/components/engine/daemon/container.go b/components/engine/daemon/container.go index 1c6dc077dc..123eca0263 100644 --- a/components/engine/daemon/container.go +++ b/components/engine/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") + } +} diff --git a/components/engine/daemon/execdriver/driver.go b/components/engine/daemon/execdriver/driver.go index 27a575cb3a..4837a398ea 100644 --- a/components/engine/daemon/execdriver/driver.go +++ b/components/engine/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 { diff --git a/components/engine/daemon/execdriver/lxc/init.go b/components/engine/daemon/execdriver/lxc/init.go index 52d75fc9f8..e21e717645 100644 --- a/components/engine/daemon/execdriver/lxc/init.go +++ b/components/engine/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 diff --git a/components/engine/daemon/execdriver/lxc/lxc_template.go b/components/engine/daemon/execdriver/lxc/lxc_template.go index 19fa43c4c2..7fdc5ce92b 100644 --- a/components/engine/daemon/execdriver/lxc/lxc_template.go +++ b/components/engine/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}} diff --git a/components/engine/daemon/execdriver/native/create.go b/components/engine/daemon/execdriver/native/create.go index 12aa64c9df..51cc2ddfd3 100644 --- a/components/engine/daemon/execdriver/native/create.go +++ b/components/engine/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 } diff --git a/components/engine/docs/sources/reference/run.md b/components/engine/docs/sources/reference/run.md index 0125394d4f..521e8010e2 100644 --- a/components/engine/docs/sources/reference/run.md +++ b/components/engine/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:`. + +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 diff --git a/components/engine/runconfig/hostconfig.go b/components/engine/runconfig/hostconfig.go index 3235bf1f4e..79ffad723b 100644 --- a/components/engine/runconfig/hostconfig.go +++ b/components/engine/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) diff --git a/components/engine/runconfig/parse.go b/components/engine/runconfig/parse.go index d395b49e80..0d511ef2ec 100644 --- a/components/engine/runconfig/parse.go +++ b/components/engine/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:': 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:") + } + default: + return "", fmt.Errorf("invalid --net: %s", netMode) + } + return NetworkMode(netMode), nil +} diff --git a/components/engine/runconfig/parse_test.go b/components/engine/runconfig/parse_test.go index fd28c4593e..8ad40b9d2d 100644 --- a/components/engine/runconfig/parse_test.go +++ b/components/engine/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) {