소스 검색

Allow setting resource constrains for build

Closes #10191

Allow `docker build` to set --cpu-shares, --cpuset, --memory,
--memory-swap for all containers created by the build.

Signed-off-by: Brian Goff <cpuguy83@gmail.com>
Brian Goff 10 년 전
부모
커밋
e6ae89a45a

+ 31 - 0
api/client/commands.go

@@ -90,6 +90,10 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 	forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers")
 	forceRm := cmd.Bool([]string{"-force-rm"}, false, "Always remove intermediate containers")
 	pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
 	pull := cmd.Bool([]string{"-pull"}, false, "Always attempt to pull a newer version of the image")
 	dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
 	dockerfileName := cmd.String([]string{"f", "-file"}, "", "Name of the Dockerfile (Default is 'PATH/Dockerfile')")
+	flMemoryString := cmd.String([]string{"m", "-memory"}, "", "Memory limit")
+	flMemorySwap := cmd.String([]string{"-memory-swap"}, "", "Total memory (memory + swap), '-1' to disable swap")
+	flCpuShares := cmd.Int64([]string{"c", "-cpu-shares"}, 0, "CPU shares (relative weight)")
+	flCpuSetCpus := cmd.String([]string{"-cpuset-cpus"}, "", "CPUs in which to allow execution (0-3, 0,1)")
 
 
 	cmd.Require(flag.Exact, 1)
 	cmd.Require(flag.Exact, 1)
 
 
@@ -242,6 +246,28 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 			Action:    "Sending build context to Docker daemon",
 			Action:    "Sending build context to Docker daemon",
 		})
 		})
 	}
 	}
+
+	var memory int64
+	if *flMemoryString != "" {
+		parsedMemory, err := units.RAMInBytes(*flMemoryString)
+		if err != nil {
+			return err
+		}
+		memory = parsedMemory
+	}
+
+	var memorySwap int64
+	if *flMemorySwap != "" {
+		if *flMemorySwap == "-1" {
+			memorySwap = -1
+		} else {
+			parsedMemorySwap, err := units.RAMInBytes(*flMemorySwap)
+			if err != nil {
+				return err
+			}
+			memorySwap = parsedMemorySwap
+		}
+	}
 	// Send the build context
 	// Send the build context
 	v := &url.Values{}
 	v := &url.Values{}
 
 
@@ -283,6 +309,11 @@ func (cli *DockerCli) CmdBuild(args ...string) error {
 		v.Set("pull", "1")
 		v.Set("pull", "1")
 	}
 	}
 
 
+	v.Set("cpusetcpus", *flCpuSetCpus)
+	v.Set("cpushares", strconv.FormatInt(*flCpuShares, 10))
+	v.Set("memory", strconv.FormatInt(memory, 10))
+	v.Set("memswap", strconv.FormatInt(memorySwap, 10))
+
 	v.Set("dockerfile", *dockerfileName)
 	v.Set("dockerfile", *dockerfileName)
 
 
 	cli.LoadConfigFile()
 	cli.LoadConfigFile()

+ 4 - 0
api/server/server.go

@@ -1082,6 +1082,10 @@ func postBuild(eng *engine.Engine, version version.Version, w http.ResponseWrite
 	job.Setenv("forcerm", r.FormValue("forcerm"))
 	job.Setenv("forcerm", r.FormValue("forcerm"))
 	job.SetenvJson("authConfig", authConfig)
 	job.SetenvJson("authConfig", authConfig)
 	job.SetenvJson("configFile", configFile)
 	job.SetenvJson("configFile", configFile)
+	job.Setenv("memswap", r.FormValue("memswap"))
+	job.Setenv("memory", r.FormValue("memory"))
+	job.Setenv("cpusetcpus", r.FormValue("cpusetcpus"))
+	job.Setenv("cpushares", r.FormValue("cpushares"))
 
 
 	if err := job.Run(); err != nil {
 	if err := job.Run(); err != nil {
 		if !job.Stdout.Used() {
 		if !job.Stdout.Used() {

+ 7 - 0
builder/evaluator.go

@@ -125,6 +125,12 @@ type Builder struct {
 	context        tarsum.TarSum // the context is a tarball that is uploaded by the client
 	context        tarsum.TarSum // the context is a tarball that is uploaded by the client
 	contextPath    string        // the path of the temporary directory the local context is unpacked to (server side)
 	contextPath    string        // the path of the temporary directory the local context is unpacked to (server side)
 	noBaseImage    bool          // indicates that this build does not start from any base image, but is being built from an empty file system.
 	noBaseImage    bool          // indicates that this build does not start from any base image, but is being built from an empty file system.
+
+	// Set resource restrictions for build containers
+	cpuSetCpus string
+	cpuShares  int64
+	memory     int64
+	memorySwap int64
 }
 }
 
 
 // Run the builder with the context. This is the lynchpin of this package. This
 // 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) {
 
 
 	// some initializations that would not have been supplied by the caller.
 	// some initializations that would not have been supplied by the caller.
 	b.Config = &runconfig.Config{}
 	b.Config = &runconfig.Config{}
+
 	b.TmpContainers = map[string]struct{}{}
 	b.TmpContainers = map[string]struct{}{}
 
 
 	for i, n := range b.dockerfile.Children {
 	for i, n := range b.dockerfile.Children {

+ 9 - 1
builder/internals.go

@@ -34,6 +34,7 @@ import (
 	"github.com/docker/docker/pkg/tarsum"
 	"github.com/docker/docker/pkg/tarsum"
 	"github.com/docker/docker/pkg/urlutil"
 	"github.com/docker/docker/pkg/urlutil"
 	"github.com/docker/docker/registry"
 	"github.com/docker/docker/registry"
+	"github.com/docker/docker/runconfig"
 	"github.com/docker/docker/utils"
 	"github.com/docker/docker/utils"
 )
 )
 
 
@@ -537,10 +538,17 @@ func (b *Builder) create() (*daemon.Container, error) {
 	}
 	}
 	b.Config.Image = b.image
 	b.Config.Image = b.image
 
 
+	hostConfig := &runconfig.HostConfig{
+		CpuShares:  b.cpuShares,
+		CpusetCpus: b.cpuSetCpus,
+		Memory:     b.memory,
+		MemorySwap: b.memorySwap,
+	}
+
 	config := *b.Config
 	config := *b.Config
 
 
 	// Create the container
 	// Create the container
-	c, warnings, err := b.Daemon.Create(b.Config, nil, "")
+	c, warnings, err := b.Daemon.Create(b.Config, hostConfig, "")
 	if err != nil {
 	if err != nil {
 		return nil, err
 		return nil, err
 	}
 	}

+ 8 - 0
builder/job.go

@@ -57,6 +57,10 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
 		rm             = job.GetenvBool("rm")
 		rm             = job.GetenvBool("rm")
 		forceRm        = job.GetenvBool("forcerm")
 		forceRm        = job.GetenvBool("forcerm")
 		pull           = job.GetenvBool("pull")
 		pull           = job.GetenvBool("pull")
+		memory         = job.GetenvInt64("memory")
+		memorySwap     = job.GetenvInt64("memswap")
+		cpuShares      = job.GetenvInt64("cpushares")
+		cpuSetCpus     = job.Getenv("cpusetcpus")
 		authConfig     = &registry.AuthConfig{}
 		authConfig     = &registry.AuthConfig{}
 		configFile     = &registry.ConfigFile{}
 		configFile     = &registry.ConfigFile{}
 		tag            string
 		tag            string
@@ -145,6 +149,10 @@ func (b *BuilderJob) CmdBuild(job *engine.Job) engine.Status {
 		AuthConfig:      authConfig,
 		AuthConfig:      authConfig,
 		AuthConfigFile:  configFile,
 		AuthConfigFile:  configFile,
 		dockerfileName:  dockerfileName,
 		dockerfileName:  dockerfileName,
+		cpuShares:       cpuShares,
+		cpuSetCpus:      cpuSetCpus,
+		memory:          memory,
+		memorySwap:      memorySwap,
 	}
 	}
 
 
 	id, err := builder.Run(context)
 	id, err := builder.Run(context)

+ 5 - 0
docs/man/docker-build.1.md

@@ -14,6 +14,11 @@ docker-build - Build a new image from the source code at PATH
 [**-q**|**--quiet**[=*false*]]
 [**-q**|**--quiet**[=*false*]]
 [**--rm**[=*true*]]
 [**--rm**[=*true*]]
 [**-t**|**--tag**[=*TAG*]]
 [**-t**|**--tag**[=*TAG*]]
+[**-m**|**--memory**[=*MEMORY*]]
+[**--memory-swap**[=*MEMORY-SWAP*]]
+[**-c**|**--cpu-shares**[=*0*]]
+[**--cpuset-cpus**[=*CPUSET-CPUS*]]
+
 PATH | URL | -
 PATH | URL | -
 
 
 # DESCRIPTION
 # DESCRIPTION

+ 1 - 1
docs/man/docker-run.1.md

@@ -31,7 +31,7 @@ docker-run - Run a command in a new container
 [**--lxc-conf**[=*[]*]]
 [**--lxc-conf**[=*[]*]]
 [**--log-driver**[=*[]*]]
 [**--log-driver**[=*[]*]]
 [**-m**|**--memory**[=*MEMORY*]]
 [**-m**|**--memory**[=*MEMORY*]]
-[**--memory-swap**[=*MEMORY-SWAP]]
+[**--memory-swap**[=*MEMORY-SWAP*]]
 [**--mac-address**[=*MAC-ADDRESS*]]
 [**--mac-address**[=*MAC-ADDRESS*]]
 [**--name**[=*NAME*]]
 [**--name**[=*NAME*]]
 [**--net**[=*"bridge"*]]
 [**--net**[=*"bridge"*]]

+ 6 - 1
docs/sources/reference/api/docker_remote_api.md

@@ -60,13 +60,18 @@ You can set ulimit settings to be used within the container.
 `GET /info`
 `GET /info`
 
 
 **New!**
 **New!**
-This endpoint now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`. 
+This endpoint now returns `SystemTime`, `HttpProxy`,`HttpsProxy` and `NoProxy`.
 
 
 `GET /images/json`
 `GET /images/json`
 
 
 **New!**
 **New!**
 Added a `RepoDigests` field to include image digest information.
 Added a `RepoDigests` field to include image digest information.
 
 
+`POST /build`
+
+**New!**
+Builds can now set resource constraints for all containers created for the build.
+
 ## v1.17
 ## v1.17
 
 
 ### Full Documentation
 ### Full Documentation

+ 4 - 0
docs/sources/reference/api/docker_remote_api_v1.18.md

@@ -1156,6 +1156,10 @@ Query Parameters:
 -   **pull** - attempt to pull the image even if an older image exists locally
 -   **pull** - attempt to pull the image even if an older image exists locally
 -   **rm** - remove intermediate containers after a successful build (default behavior)
 -   **rm** - remove intermediate containers after a successful build (default behavior)
 -   **forcerm** - always remove intermediate containers (includes rm)
 -   **forcerm** - always remove intermediate containers (includes rm)
+-   **memory** - set memory limit for build
+-   **memswap** - Total memory (memory + swap), `-1` to disable swap
+-   **cpushares** - CPU shares (relative weight)
+-   **cpusetcpus** - CPUs in which to allow exection, e.g., `0-3`, `0,1`
 
 
     Request Headers:
     Request Headers:
 
 

+ 4 - 0
docs/sources/reference/commandline/cli.md

@@ -515,6 +515,10 @@ is returned by the `docker attach` command to its caller too:
       -q, --quiet=false        Suppress the verbose output generated by the containers
       -q, --quiet=false        Suppress the verbose output generated by the containers
       --rm=true                Remove intermediate containers after a successful build
       --rm=true                Remove intermediate containers after a successful build
       -t, --tag=""             Repository name (and optionally a tag) for the image
       -t, --tag=""             Repository name (and optionally a tag) for the image
+      -m, --memory=""          Memory limit for all build containers
+      --memory-swap=""         Total memory (memory + swap), `-1` to disable swap
+      -c, --cpu-shares         CPU Shares (relative weight)
+      --cpuset-cpus=""         CPUs in which to allow exection, e.g. `0-3`, `0,1`
 
 
 Builds Docker images from a Dockerfile and a "context". A build's context is
 Builds Docker images from a Dockerfile and a "context". A build's context is
 the files located in the specified `PATH` or `URL`.  The build process can
 the files located in the specified `PATH` or `URL`.  The build process can

+ 72 - 1
integration-cli/docker_cli_build_test.go

@@ -4455,7 +4455,7 @@ func TestBuildExoticShellInterpolation(t *testing.T) {
 
 
 	_, err := buildImage(name, `
 	_, err := buildImage(name, `
 		FROM busybox
 		FROM busybox
-		
+
 		ENV SOME_VAR a.b.c
 		ENV SOME_VAR a.b.c
 
 
 		RUN [ "$SOME_VAR"       = 'a.b.c' ]
 		RUN [ "$SOME_VAR"       = 'a.b.c' ]
@@ -5276,3 +5276,74 @@ RUN [ "/hello" ]`, map[string]string{})
 
 
 	logDone("build - RUN with one JSON arg")
 	logDone("build - RUN with one JSON arg")
 }
 }
+
+func TestBuildResourceConstraintsAreUsed(t *testing.T) {
+	name := "testbuildresourceconstraints"
+	defer deleteAllContainers()
+	defer deleteImages(name)
+
+	ctx, err := fakeContext(`
+	FROM hello-world:frozen
+	RUN ["/hello"]
+	`, map[string]string{})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	cmd := exec.Command(dockerBinary, "build", "--rm=false", "--memory=64m", "--memory-swap=-1", "--cpuset-cpus=1", "--cpu-shares=100", "-t", name, ".")
+	cmd.Dir = ctx.Dir
+
+	out, _, err := runCommandWithOutput(cmd)
+	if err != nil {
+		t.Fatal(err, out)
+	}
+	out, _, err = dockerCmd(t, "ps", "-lq")
+	if err != nil {
+		t.Fatal(err, out)
+	}
+
+	cID := stripTrailingCharacters(out)
+
+	type hostConfig struct {
+		Memory     float64 // Use float64 here since the json decoder sees it that way
+		MemorySwap int
+		CpusetCpus string
+		CpuShares  int
+	}
+
+	cfg, err := inspectFieldJSON(cID, "HostConfig")
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	var c1 hostConfig
+	if err := json.Unmarshal([]byte(cfg), &c1); err != nil {
+		t.Fatal(err, cfg)
+	}
+	mem := int64(c1.Memory)
+	if mem != 67108864 || c1.MemorySwap != -1 || c1.CpusetCpus != "1" || c1.CpuShares != 100 {
+		t.Fatalf("resource constraints not set properly:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d",
+			mem, c1.MemorySwap, c1.CpusetCpus, c1.CpuShares)
+	}
+
+	// Make sure constraints aren't saved to image
+	_, _, err = dockerCmd(t, "run", "--name=test", name)
+	if err != nil {
+		t.Fatal(err)
+	}
+	cfg, err = inspectFieldJSON("test", "HostConfig")
+	if err != nil {
+		t.Fatal(err)
+	}
+	var c2 hostConfig
+	if err := json.Unmarshal([]byte(cfg), &c2); err != nil {
+		t.Fatal(err, cfg)
+	}
+	mem = int64(c2.Memory)
+	if mem == 67108864 || c2.MemorySwap == -1 || c2.CpusetCpus == "1" || c2.CpuShares == 100 {
+		t.Fatalf("resource constraints leaked from build:\nMemory: %d, MemSwap: %d, CpusetCpus: %s, CpuShares: %d",
+			mem, c2.MemorySwap, c2.CpusetCpus, c2.CpuShares)
+	}
+
+	logDone("build - resource constraints applied")
+}