Browse code

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

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

Guillaume J. Charmes authored on 2014/05/06 05:54:55
Showing 9 changed files
... ...
@@ -325,7 +325,7 @@ func (container *Container) Attach(stdin io.ReadCloser, stdinCloser io.Closer, s
325 325
 	})
326 326
 }
327 327
 
328
-func populateCommand(c *Container, env []string) {
328
+func populateCommand(c *Container, env []string) error {
329 329
 	var (
330 330
 		en      *execdriver.Network
331 331
 		context = make(map[string][]string)
... ...
@@ -338,14 +338,29 @@ func populateCommand(c *Container, env []string) {
338 338
 		Interface: nil,
339 339
 	}
340 340
 
341
-	if !c.Config.NetworkDisabled {
342
-		network := c.NetworkSettings
343
-		en.Interface = &execdriver.NetworkInterface{
344
-			Gateway:     network.Gateway,
345
-			Bridge:      network.Bridge,
346
-			IPAddress:   network.IPAddress,
347
-			IPPrefixLen: network.IPPrefixLen,
341
+	parts := strings.SplitN(string(c.hostConfig.NetworkMode), ":", 2)
342
+	switch parts[0] {
343
+	case "none":
344
+	case "host":
345
+		en.HostNetworking = true
346
+	case "bridge", "": // empty string to support existing containers
347
+		if !c.Config.NetworkDisabled {
348
+			network := c.NetworkSettings
349
+			en.Interface = &execdriver.NetworkInterface{
350
+				Gateway:     network.Gateway,
351
+				Bridge:      network.Bridge,
352
+				IPAddress:   network.IPAddress,
353
+				IPPrefixLen: network.IPPrefixLen,
354
+			}
348 355
 		}
356
+	case "container":
357
+		nc, err := c.getNetworkedContainer()
358
+		if err != nil {
359
+			return err
360
+		}
361
+		en.ContainerID = nc.ID
362
+	default:
363
+		return fmt.Errorf("invalid network mode: %s", c.hostConfig.NetworkMode)
349 364
 	}
350 365
 
351 366
 	// TODO: this can be removed after lxc-conf is fully deprecated
... ...
@@ -372,6 +387,7 @@ func populateCommand(c *Container, env []string) {
372 372
 	}
373 373
 	c.command.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
374 374
 	c.command.Env = env
375
+	return nil
375 376
 }
376 377
 
377 378
 func (container *Container) Start() (err error) {
... ...
@@ -415,7 +431,9 @@ func (container *Container) Start() (err error) {
415 415
 	if err := container.setupWorkingDirectory(); err != nil {
416 416
 		return err
417 417
 	}
418
-	populateCommand(container, env)
418
+	if err := populateCommand(container, env); err != nil {
419
+		return err
420
+	}
419 421
 	if err := setupMountsForContainer(container); err != nil {
420 422
 		return err
421 423
 	}
... ...
@@ -485,9 +503,18 @@ func (container *Container) StderrLogPipe() io.ReadCloser {
485 485
 	return utils.NewBufReader(reader)
486 486
 }
487 487
 
488
-func (container *Container) buildHostnameAndHostsFiles(IP string) {
488
+func (container *Container) buildHostname() {
489 489
 	container.HostnamePath = path.Join(container.root, "hostname")
490
-	ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
490
+
491
+	if container.Config.Domainname != "" {
492
+		ioutil.WriteFile(container.HostnamePath, []byte(fmt.Sprintf("%s.%s\n", container.Config.Hostname, container.Config.Domainname)), 0644)
493
+	} else {
494
+		ioutil.WriteFile(container.HostnamePath, []byte(container.Config.Hostname+"\n"), 0644)
495
+	}
496
+}
497
+
498
+func (container *Container) buildHostnameAndHostsFiles(IP string) {
499
+	container.buildHostname()
491 500
 
492 501
 	hostsContent := []byte(`
493 502
 127.0.0.1	localhost
... ...
@@ -505,12 +532,12 @@ ff02::2		ip6-allrouters
505 505
 	} else if !container.Config.NetworkDisabled {
506 506
 		hostsContent = append([]byte(fmt.Sprintf("%s\t%s\n", IP, container.Config.Hostname)), hostsContent...)
507 507
 	}
508
-
509 508
 	ioutil.WriteFile(container.HostsPath, hostsContent, 0644)
510 509
 }
511 510
 
512 511
 func (container *Container) allocateNetwork() error {
513
-	if container.Config.NetworkDisabled {
512
+	mode := container.hostConfig.NetworkMode
513
+	if container.Config.NetworkDisabled || mode.IsContainer() || mode.IsHost() {
514 514
 		return nil
515 515
 	}
516 516
 
... ...
@@ -963,14 +990,22 @@ func (container *Container) setupContainerDns() error {
963 963
 	if container.ResolvConfPath != "" {
964 964
 		return nil
965 965
 	}
966
+
966 967
 	var (
967 968
 		config = container.hostConfig
968 969
 		daemon = container.daemon
969 970
 	)
971
+
972
+	if config.NetworkMode == "host" {
973
+		container.ResolvConfPath = "/etc/resolv.conf"
974
+		return nil
975
+	}
976
+
970 977
 	resolvConf, err := utils.GetResolvConf()
971 978
 	if err != nil {
972 979
 		return err
973 980
 	}
981
+
974 982
 	// If custom dns exists, then create a resolv.conf for the container
975 983
 	if len(config.Dns) > 0 || len(daemon.config.Dns) > 0 || len(config.DnsSearch) > 0 || len(daemon.config.DnsSearch) > 0 {
976 984
 		var (
... ...
@@ -1010,7 +1045,32 @@ func (container *Container) setupContainerDns() error {
1010 1010
 }
1011 1011
 
1012 1012
 func (container *Container) initializeNetworking() error {
1013
-	if container.daemon.config.DisableNetwork {
1013
+	var err error
1014
+	if container.hostConfig.NetworkMode.IsHost() {
1015
+		container.Config.Hostname, err = os.Hostname()
1016
+		if err != nil {
1017
+			return err
1018
+		}
1019
+
1020
+		parts := strings.SplitN(container.Config.Hostname, ".", 2)
1021
+		if len(parts) > 1 {
1022
+			container.Config.Hostname = parts[0]
1023
+			container.Config.Domainname = parts[1]
1024
+		}
1025
+		container.HostsPath = "/etc/hosts"
1026
+
1027
+		container.buildHostname()
1028
+	} else if container.hostConfig.NetworkMode.IsContainer() {
1029
+		// we need to get the hosts files from the container to join
1030
+		nc, err := container.getNetworkedContainer()
1031
+		if err != nil {
1032
+			return err
1033
+		}
1034
+		container.HostsPath = nc.HostsPath
1035
+		container.ResolvConfPath = nc.ResolvConfPath
1036
+		container.Config.Hostname = nc.Config.Hostname
1037
+		container.Config.Domainname = nc.Config.Domainname
1038
+	} else if container.daemon.config.DisableNetwork {
1014 1039
 		container.Config.NetworkDisabled = true
1015 1040
 		container.buildHostnameAndHostsFiles("127.0.1.1")
1016 1041
 	} else {
... ...
@@ -1219,3 +1279,20 @@ func (container *Container) GetMountLabel() string {
1219 1219
 	}
1220 1220
 	return container.MountLabel
1221 1221
 }
1222
+
1223
+func (container *Container) getNetworkedContainer() (*Container, error) {
1224
+	parts := strings.SplitN(string(container.hostConfig.NetworkMode), ":", 2)
1225
+	switch parts[0] {
1226
+	case "container":
1227
+		nc := container.daemon.Get(parts[1])
1228
+		if nc == nil {
1229
+			return nil, fmt.Errorf("no such container to join network: %s", parts[1])
1230
+		}
1231
+		if !nc.State.IsRunning() {
1232
+			return nil, fmt.Errorf("cannot join network of a non running container: %s", parts[1])
1233
+		}
1234
+		return nc, nil
1235
+	default:
1236
+		return nil, fmt.Errorf("network mode not set to container")
1237
+	}
1238
+}
... ...
@@ -89,8 +89,10 @@ type Driver interface {
89 89
 
90 90
 // Network settings of the container
91 91
 type Network struct {
92
-	Interface *NetworkInterface `json:"interface"` // if interface is nil then networking is disabled
93
-	Mtu       int               `json:"mtu"`
92
+	Interface      *NetworkInterface `json:"interface"` // if interface is nil then networking is disabled
93
+	Mtu            int               `json:"mtu"`
94
+	ContainerID    string            `json:"container_id"` // id of the container to join network.
95
+	HostNetworking bool              `json:"host_networking"`
94 96
 }
95 97
 
96 98
 type NetworkInterface struct {
... ...
@@ -3,15 +3,16 @@ package lxc
3 3
 import (
4 4
 	"encoding/json"
5 5
 	"fmt"
6
-	"github.com/dotcloud/docker/daemon/execdriver"
7
-	"github.com/dotcloud/docker/pkg/netlink"
8
-	"github.com/dotcloud/docker/pkg/user"
9
-	"github.com/syndtr/gocapability/capability"
10 6
 	"io/ioutil"
11 7
 	"net"
12 8
 	"os"
13 9
 	"strings"
14 10
 	"syscall"
11
+
12
+	"github.com/dotcloud/docker/daemon/execdriver"
13
+	"github.com/dotcloud/docker/pkg/netlink"
14
+	"github.com/dotcloud/docker/pkg/user"
15
+	"github.com/syndtr/gocapability/capability"
15 16
 )
16 17
 
17 18
 // Clear environment pollution introduced by lxc-start
... ...
@@ -14,12 +14,13 @@ const LxcTemplate = `
14 14
 lxc.network.type = veth
15 15
 lxc.network.link = {{.Network.Interface.Bridge}}
16 16
 lxc.network.name = eth0
17
-{{else}}
17
+lxc.network.mtu = {{.Network.Mtu}}
18
+{{else if not .Network.HostNetworking}}
18 19
 # network is disabled (-n=false)
19 20
 lxc.network.type = empty
20 21
 lxc.network.flags = up
21
-{{end}}
22 22
 lxc.network.mtu = {{.Network.Mtu}}
23
+{{end}}
23 24
 
24 25
 # root filesystem
25 26
 {{$ROOTFS := .Rootfs}}
... ...
@@ -3,6 +3,7 @@ package native
3 3
 import (
4 4
 	"fmt"
5 5
 	"os"
6
+	"path/filepath"
6 7
 
7 8
 	"github.com/dotcloud/docker/daemon/execdriver"
8 9
 	"github.com/dotcloud/docker/daemon/execdriver/native/configuration"
... ...
@@ -52,6 +53,10 @@ func (d *driver) createContainer(c *execdriver.Command) (*libcontainer.Container
52 52
 }
53 53
 
54 54
 func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver.Command) error {
55
+	if c.Network.HostNetworking {
56
+		container.Namespaces.Get("NEWNET").Enabled = false
57
+		return nil
58
+	}
55 59
 	container.Networks = []*libcontainer.Network{
56 60
 		{
57 61
 			Mtu:     c.Network.Mtu,
... ...
@@ -75,6 +80,20 @@ func (d *driver) createNetwork(container *libcontainer.Container, c *execdriver.
75 75
 		}
76 76
 		container.Networks = append(container.Networks, &vethNetwork)
77 77
 	}
78
+
79
+	if c.Network.ContainerID != "" {
80
+		cmd := d.activeContainers[c.Network.ContainerID]
81
+		if cmd == nil || cmd.Process == nil {
82
+			return fmt.Errorf("%s is not a valid running container to join", c.Network.ContainerID)
83
+		}
84
+		nspath := filepath.Join("/proc", fmt.Sprint(cmd.Process.Pid), "ns", "net")
85
+		container.Networks = append(container.Networks, &libcontainer.Network{
86
+			Type: "netns",
87
+			Context: libcontainer.Context{
88
+				"nspath": nspath,
89
+			},
90
+		})
91
+	}
78 92
 	return nil
79 93
 }
80 94
 
... ...
@@ -136,8 +136,8 @@ PID files):
136 136
 
137 137
 ## Network Settings
138 138
 
139
-    -n=true   : Enable networking for this container
140
-    --dns=[]  : Set custom dns servers for the container
139
+    --dns=[]     : Set custom dns servers for the container
140
+    --net=bridge : Set the network mode
141 141
 
142 142
 By default, all containers have networking enabled and they can make any
143 143
 outgoing connections. The operator can completely disable networking
... ...
@@ -148,6 +148,48 @@ files or STDIN/STDOUT only.
148 148
 Your container will use the same DNS servers as the host by default, but
149 149
 you can override this with `--dns`.
150 150
 
151
+Supported networking modes are: 
152
+
153
+* none - no networking in the container
154
+* bridge - (default) connect the container to the bridge via veth interfaces
155
+* host - use the host's network stack inside the container
156
+* container - use another container's network stack
157
+
158
+#### Mode: none
159
+With the networking mode set to `none` a container will not have a access to 
160
+any external routes.  The container will still have a `loopback` interface 
161
+enabled in the container but it does not have any routes to external traffic.
162
+
163
+#### Mode: bridge
164
+With the networking mode set to `bridge` a container will use docker's default
165
+networking setup.  A bridge is setup on the host, commonly named `docker0`, 
166
+and a pair of veth interfaces will be created for the container.  One side of 
167
+the veth pair will remain on the host attached to the bridge while the other 
168
+side of the pair will be placed inside the container's namespaces in addition 
169
+to the `loopback` interface.  An IP address will be allocated for containers 
170
+on the bridge's network and trafic will be routed though this bridge to the
171
+container.
172
+
173
+#### Mode: host
174
+With the networking mode set to `host` a container will share the host's
175
+network stack and all interfaces from the host will be available to the 
176
+container.  The container's hostname will match the hostname on the host 
177
+system.  Publishing ports and linking to other containers will not work 
178
+when sharing the host's network stack.  
179
+
180
+#### Mode: container
181
+With the networking mode set to `container` a container will share the 
182
+network stack of another container.  The other container's name must be 
183
+provided in the format of `--net container:<name|id>`.
184
+
185
+Example running a redis container with redis binding to localhost then 
186
+running the redis-cli and connecting to the redis server over the 
187
+localhost interface.
188
+
189
+    $ docker run -d --name redis example/redis --bind 127.0.0.1
190
+    $ # use the redis container's network stack to access localhost
191
+    $ docker run --rm -ti --net container:redis example/redis-cli -h 127.0.0.1
192
+
151 193
 ## Clean Up (–rm)
152 194
 
153 195
 By default a container's file system persists even after the container
... ...
@@ -1,11 +1,24 @@
1 1
 package runconfig
2 2
 
3 3
 import (
4
+	"strings"
5
+
4 6
 	"github.com/dotcloud/docker/engine"
5 7
 	"github.com/dotcloud/docker/nat"
6 8
 	"github.com/dotcloud/docker/utils"
7 9
 )
8 10
 
11
+type NetworkMode string
12
+
13
+func (n NetworkMode) IsHost() bool {
14
+	return n == "host"
15
+}
16
+
17
+func (n NetworkMode) IsContainer() bool {
18
+	parts := strings.SplitN(string(n), ":", 2)
19
+	return len(parts) > 1 && parts[0] == "container"
20
+}
21
+
9 22
 type HostConfig struct {
10 23
 	Binds           []string
11 24
 	ContainerIDFile string
... ...
@@ -17,6 +30,7 @@ type HostConfig struct {
17 17
 	Dns             []string
18 18
 	DnsSearch       []string
19 19
 	VolumesFrom     []string
20
+	NetworkMode     NetworkMode
20 21
 }
21 22
 
22 23
 func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
... ...
@@ -24,6 +38,7 @@ func ContainerHostConfigFromJob(job *engine.Job) *HostConfig {
24 24
 		ContainerIDFile: job.Getenv("ContainerIDFile"),
25 25
 		Privileged:      job.GetenvBool("Privileged"),
26 26
 		PublishAllPorts: job.GetenvBool("PublishAllPorts"),
27
+		NetworkMode:     NetworkMode(job.Getenv("NetworkMode")),
27 28
 	}
28 29
 	job.GetenvJson("LxcConf", &hostConfig.LxcConf)
29 30
 	job.GetenvJson("PortBindings", &hostConfig.PortBindings)
... ...
@@ -2,14 +2,15 @@ package runconfig
2 2
 
3 3
 import (
4 4
 	"fmt"
5
+	"io/ioutil"
6
+	"path"
7
+	"strings"
8
+
5 9
 	"github.com/dotcloud/docker/nat"
6 10
 	"github.com/dotcloud/docker/opts"
7 11
 	flag "github.com/dotcloud/docker/pkg/mflag"
8 12
 	"github.com/dotcloud/docker/pkg/sysinfo"
9 13
 	"github.com/dotcloud/docker/utils"
10
-	"io/ioutil"
11
-	"path"
12
-	"strings"
13 14
 )
14 15
 
15 16
 var (
... ...
@@ -49,7 +50,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
49 49
 
50 50
 		flAutoRemove      = cmd.Bool([]string{"#rm", "-rm"}, false, "Automatically remove the container when it exits (incompatible with -d)")
51 51
 		flDetach          = cmd.Bool([]string{"d", "-detach"}, false, "Detached mode: Run container in the background, print new container id")
52
-		flNetwork         = cmd.Bool([]string{"n", "-networking"}, true, "Enable networking for this container")
52
+		flNetwork         = cmd.Bool([]string{"#n", "#-networking"}, true, "Enable networking for this container")
53 53
 		flPrivileged      = cmd.Bool([]string{"#privileged", "-privileged"}, false, "Give extended privileges to this container")
54 54
 		flPublishAll      = cmd.Bool([]string{"P", "-publish-all"}, false, "Publish all exposed ports to the host interfaces")
55 55
 		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
61 61
 		flUser            = cmd.String([]string{"u", "-user"}, "", "Username or UID")
62 62
 		flWorkingDir      = cmd.String([]string{"w", "-workdir"}, "", "Working directory inside the container")
63 63
 		flCpuShares       = cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
64
-
64
+		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)")
65 65
 		// For documentation purpose
66 66
 		_ = cmd.Bool([]string{"#sig-proxy", "-sig-proxy"}, true, "Proxify all received signal to the process (even in non-tty mode)")
67 67
 		_ = 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
197 197
 	// boo, there's no debug output for docker run
198 198
 	//utils.Debugf("Environment variables for the container: %#v", envVariables)
199 199
 
200
+	netMode, err := parseNetMode(*flNetMode)
201
+	if err != nil {
202
+		return nil, nil, cmd, fmt.Errorf("--net: invalid net mode: %v", err)
203
+	}
204
+
200 205
 	config := &Config{
201 206
 		Hostname:        hostname,
202 207
 		Domainname:      domainname,
... ...
@@ -230,6 +236,7 @@ func parseRun(cmd *flag.FlagSet, args []string, sysInfo *sysinfo.SysInfo) (*Conf
230 230
 		Dns:             flDns.GetAll(),
231 231
 		DnsSearch:       flDnsSearch.GetAll(),
232 232
 		VolumesFrom:     flVolumesFrom.GetAll(),
233
+		NetworkMode:     netMode,
233 234
 	}
234 235
 
235 236
 	if sysInfo != nil && flMemory > 0 && !sysInfo.SwapLimit {
... ...
@@ -274,3 +281,17 @@ func parseKeyValueOpts(opts opts.ListOpts) ([]utils.KeyValuePair, error) {
274 274
 	}
275 275
 	return out, nil
276 276
 }
277
+
278
+func parseNetMode(netMode string) (NetworkMode, error) {
279
+	parts := strings.Split(netMode, ":")
280
+	switch mode := parts[0]; mode {
281
+	case "bridge", "none", "host":
282
+	case "container":
283
+		if len(parts) < 2 || parts[1] == "" {
284
+			return "", fmt.Errorf("invalid container format container:<name|id>")
285
+		}
286
+	default:
287
+		return "", fmt.Errorf("invalid --net: %s", netMode)
288
+	}
289
+	return NetworkMode(netMode), nil
290
+}
... ...
@@ -1,8 +1,9 @@
1 1
 package runconfig
2 2
 
3 3
 import (
4
-	"github.com/dotcloud/docker/utils"
5 4
 	"testing"
5
+
6
+	"github.com/dotcloud/docker/utils"
6 7
 )
7 8
 
8 9
 func TestParseLxcConfOpt(t *testing.T) {