Browse code

Merge pull request #11410 from cpuguy83/10191_build_resources

Allow setting resource constraints for build

Doug Davis authored on 2015/03/19 10:44:21
Showing 11 changed files
... ...
@@ -90,6 +90,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
90 90
 	forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers")
91 91
 	pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
92 92
 	dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
93
+	flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
94
+	flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
95
+	flCpuShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
96
+	flCpuSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
93 97
 
94 98
 	cmd.Require(flag.Exact, 1)
95 99
 
... ...
@@ -242,6 +246,28 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
242 242
 			Action:    "Sending build context to Docker daemon",
243 243
 		})
244 244
 	}
245
+
246
+	var memory int64
247
+	if *flMemoryString != "" {
248
+		parsedMemory, err := units.RAMInBytes(*flMemoryString)
249
+		if err != nil {
250
+			return err
251
+		}
252
+		memory = parsedMemory
253
+	}
254
+
255
+	var memorySwap int64
256
+	if *flMemorySwap != "" {
257
+		if *flMemorySwap == "-1" {
258
+			memorySwap = -1
259
+		} else {
260
+			parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap)
261
+			if err != nil {
262
+				return err
263
+			}
264
+			memorySwap = parsedMemorySwap
265
+		}
266
+	}
245 267
 	// Send the build context
246 268
 	v := &url.Values{}
247 269
 
... ...
@@ -283,6 +309,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
283 283
 		v.Set("pull", "1")
284 284
 	}
285 285
 
286
+	v.Set("cpusetcpus", *flCpuSetCpus)
287
+	v.Set("cpushares", strconv.FormatInt(*flCpuShares, 10))
288
+	v.Set("memory", strconv.FormatInt(memory, 10))
289
+	v.Set("memswap", strconv.FormatInt(memorySwap, 10))
290
+
286 291
 	v.Set("dockerfile", *dockerfileName)
287 292
 
288 293
 	cli.LoadConfigFile()
... ...
@@ -1082,6 +1082,10 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
1082 1082
 	job.Setenv("forcerm", r.FormValue("forcerm"))
1083 1083
 	job.SetenvJson("authConfig", authConfig)
1084 1084
 	job.SetenvJson("configFile", configFile)
1085
+	job.Setenv("memswap", r.FormValue("memswap"))
1086
+	job.Setenv("memory", r.FormValue("memory"))
1087
+	job.Setenv("cpusetcpus", r.FormValue("cpusetcpus"))
1088
+	job.Setenv("cpushares", r.FormValue("cpushares"))
1085 1089
 
1086 1090
 	if err := job.Run(); err != nil {
1087 1091
 		if !job.Stdout.Used() {
... ...
@@ -125,6 +125,12 @@ type Builder struct {
125 125
 	context        tarsum.TarSum // the context is a tarball that is uploaded by the client
126 126
 	contextPath    string        // the path of the temporary directory the local context is unpacked to (server side)
127 127
 	noBaseImage    bool          // indicates that this build does not start from any base image, but is being built from an empty file system.
128
+
129
+	// Set resource restrictions for build containers
130
+	cpuSetCpus string
131
+	cpuShares  int64
132
+	memory     int64
133
+	memorySwap int64
128 134
 }
129 135
 
130 136
 // Run the builder with the context. This is the lynchpin of this package. This
... ...
@@ -156,6 +162,7 @@ func (b *Builder) Run(context io.Reader) (string, error) {
156 156
 
157 157
 	// some initializations that would not have been supplied by the caller.
158 158
 	b.Config = &runconfig.Config{}
159
+
159 160
 	b.TmpContainers = map[string]struct{}{}
160 161
 
161 162
 	for i, n := range b.dockerfile.Children {
... ...
@@ -34,6 +34,7 @@ import (
34 34
 	"github.com/docker/docker/pkg/tarsum"
35 35
 	"github.com/docker/docker/pkg/urlutil"
36 36
 	"github.com/docker/docker/registry"
37
+	"github.com/docker/docker/runconfig"
37 38
 	"github.com/docker/docker/utils"
38 39
 )
39 40
 
... ...
@@ -537,10 +538,17 @@ func (b *Builder) create() (*daemon.Container, error) {
537 537
 	}
538 538
 	b.Config.Image = b.image
539 539
 
540
+	hostConfig := &runconfig.HostConfig{
541
+		CpuShares:  b.cpuShares,
542
+		CpusetCpus: b.cpuSetCpus,
543
+		Memory:     b.memory,
544
+		MemorySwap: b.memorySwap,
545
+	}
546
+
540 547
 	config := *b.Config
541 548
 
542 549
 	// Create the container
543
-	c, warnings, err := b.Daemon.Create(b.Config, nil, "")
550
+	c, warnings, err := b.Daemon.Create(b.Config, hostConfig, "")
544 551
 	if err != nil {
545 552
 		return nil, err
546 553
 	}
... ...
@@ -57,6 +57,10 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
57 57
 		rm             = job.GetenvBool("rm")
58 58
 		forceRm        = job.GetenvBool("forcerm")
59 59
 		pull           = job.GetenvBool("pull")
60
+		memory         = job.GetenvInt64("memory")
61
+		memorySwap     = job.GetenvInt64("memswap")
62
+		cpuShares      = job.GetenvInt64("cpushares")
63
+		cpuSetCpus     = job.Getenv("cpusetcpus")
60 64
 		authConfig     = &registry.AuthConfig{}
61 65
 		configFile     = &registry.ConfigFile{}
62 66
 		tag            string
... ...
@@ -145,6 +149,10 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
145 145
 		AuthConfig:      authConfig,
146 146
 		AuthConfigFile:  configFile,
147 147
 		dockerfileName:  dockerfileName,
148
+		cpuShares:       cpuShares,
149
+		cpuSetCpus:      cpuSetCpus,
150
+		memory:          memory,
151
+		memorySwap:      memorySwap,
148 152
 	}
149 153
 
150 154
 	id, err := builder.Run(context)
... ...
@@ -14,6 +14,11 @@ docker-build - Build a new image from the source code at PATH
14 14
 [**-q**|**--quiet**[=*false*]]
15 15
 [**--rm**[=*true*]]
16 16
 [**-t**|**--tag**[=*TAG*]]
17
+[**-m**|**--memory**[=*MEMORY*]]
18
+[**--memory-swap**[=*MEMORY-SWAP*]]
19
+[**-c**|**--cpu-shares**[=*0*]]
20
+[**--cpuset-cpus**[=*CPUSET-CPUS*]]
21
+
17 22
 PATH | URL | -
18 23
 
19 24
 # DESCRIPTION
... ...
@@ -31,7 +31,7 @@ docker-run - Run a command in a new container
31 31
 [**--lxc-conf**[=*[]*]]
32 32
 [**--log-driver**[=*[]*]]
33 33
 [**-m**|**--memory**[=*MEMORY*]]
34
-[**--memory-swap**[=*MEMORY-SWAP]]
34
+[**--memory-swap**[=*MEMORY-SWAP*]]
35 35
 [**--mac-address**[=*MAC-ADDRESS*]]
36 36
 [**--name**[=*NAME*]]
37 37
 [**--net**[=*"bridge"*]]
... ...
@@ -60,13 +60,18 @@ You can set ulimit settings to be used within the container.
60 60
 `GET /info`
61 61
 
62 62
 **New!**
63
-This endpoint now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`. 
63
+This endpoint now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`.
64 64
 
65 65
 `GET /images/json`
66 66
 
67 67
 **New!**
68 68
 Added a `RepoDigests` field to include image digest information.
69 69
 
70
+`POST /build`
71
+
72
+**New!**
73
+Builds can now set resource constraints for all containers created for the build.
74
+
70 75
 ## v1.17
71 76
 
72 77
 ### Full Documentation
... ...
@@ -1156,6 +1156,10 @@ Query Parameters:
1156 1156
 -   **pull** - attempt to pull the image even if an older image exists locally
1157 1157
 -   **rm** - remove intermediate containers after a successful build (default behavior)
1158 1158
 -   **forcerm** - always remove intermediate containers (includes rm)
1159
+-   **memory** - set memory limit for build
1160
+-   **memswap** - Total memory (memory + swap), `-1` to disable swap
1161
+-   **cpushares** - CPU shares (relative weight)
1162
+-   **cpusetcpus** - CPUs in which to allow exection, e.g., `0-3`, `0,1`
1159 1163
 
1160 1164
     Request Headers:
1161 1165
 
... ...
@@ -515,6 +515,10 @@ is returned by the `docker attach` command to its caller too:
515 515
       -q, --quiet=false        Suppress the verbose output generated by the containers
516 516
       --rm=true                Remove intermediate containers after a successful build
517 517
       -t, --tag=""             Repository name (and optionally a tag) for the image
518
+      -m, --memory=""          Memory limit for all build containers
519
+      --memory-swap=""         Total memory (memory + swap), `-1` to disable swap
520
+      -c, --cpu-shares         CPU Shares (relative weight)
521
+      --cpuset-cpus=""         CPUs in which to allow exection, e.g. `0-3`, `0,1`
518 522
 
519 523
 Builds Docker images from a Dockerfile and a "context". A build's context is
520 524
 the files located in the specified `PATH` or `URL`.  The build process can
... ...
@@ -4455,7 +4455,7 @@ func TestBuildExoticShellInterpolation(t *testing.T) {
4455 4455
 
4456 4456
 	_, err := buildImage(name, `
4457 4457
 		FROM busybox
4458
-		
4458
+
4459 4459
 		ENV SOME_VAR a.b.c
4460 4460
 
4461 4461
 		RUN [ "$SOME_VAR"       = 'a.b.c' ]
... ...
@@ -5276,3 +5276,74 @@ RUN [ "/hello" ]`, map[string]string{})
5276 5276
 
5277 5277
 	logDone("build - RUN with one JSON arg")
5278 5278
 }
5279
+
5280
+func TestBuildResourceConstraintsAreUsed(t *testing.T) {
5281
+	name := "testbuildresourceconstraints"
5282
+	defer deleteAllContainers()
5283
+	defer deleteImages(name)
5284
+
5285
+	ctx, err := fakeContext(`
5286
+	FROM hello-world:frozen
5287
+	RUN ["/hello"]
5288
+	`, map[string]string{})
5289
+	if err != nil {
5290
+		t.Fatal(err)
5291
+	}
5292
+
5293
+	cmd := exec.Command(dockerBinary, "build", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=1", "--cpu-shares=100", "-t", name, ".")
5294
+	cmd.Dir = ctx.Dir
5295
+
5296
+	out, _, err := runCommandWithOutput(cmd)
5297
+	if err != nil {
5298
+		t.Fatal(err, out)
5299
+	}
5300
+	out, _, err = dockerCmd(t, "ps", "-lq")
5301
+	if err != nil {
5302
+		t.Fatal(err, out)
5303
+	}
5304
+
5305
+	cID := stripTrailingCharacters(out)
5306
+
5307
+	type hostConfig struct {
5308
+		Memory     float64 // Use float64 here since the json decoder sees it that way
5309
+		MemorySwap int
5310
+		CpusetCpus string
5311
+		CpuShares  int
5312
+	}
5313
+
5314
+	cfg, err := inspectFieldJSON(cID, "HostConfig")
5315
+	if err != nil {
5316
+		t.Fatal(err)
5317
+	}
5318
+
5319
+	var c1 hostConfig
5320
+	if err := json.Unmarshal([]byte(cfg), &c1); err != nil {
5321
+		t.Fatal(err, cfg)
5322
+	}
5323
+	mem := int64(c1.Memory)
5324
+	if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "1" || c1.CpuShares != 100 {
5325
+		t.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d",
5326
+			mem, c1.MemorySwap, c1.CpusetCpus, c1.CpuShares)
5327
+	}
5328
+
5329
+	// Make sure constraints aren't saved to image
5330
+	_, _, err = dockerCmd(t, "run", "--name=test", name)
5331
+	if err != nil {
5332
+		t.Fatal(err)
5333
+	}
5334
+	cfg, err = inspectFieldJSON("test", "HostConfig")
5335
+	if err != nil {
5336
+		t.Fatal(err)
5337
+	}
5338
+	var c2 hostConfig
5339
+	if err := json.Unmarshal([]byte(cfg), &c2); err != nil {
5340
+		t.Fatal(err, cfg)
5341
+	}
5342
+	mem = int64(c2.Memory)
5343
+	if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "1" || c2.CpuShares == 100 {
5344
+		t.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d",
5345
+			mem, c2.MemorySwap, c2.CpusetCpus, c2.CpuShares)
5346
+	}
5347
+
5348
+	logDone("build - resource constraints applied")
5349
+}