Allow setting resource constraints for build
| ... | ... |
@@ -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 = ®istry.AuthConfig{}
|
| 61 | 65 |
configFile = ®istry.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 |
+} |