Merge pull request #8523 from dokku/3826-extending-resource-management-to-include-build-containers
Support resource limits on the build container
This commit is contained in:
@@ -32,7 +32,7 @@ Valid resource options include:
|
||||
|
||||
See the [Supported Resource Management Properties](/docs/deployment/schedulers/docker-local.md#supported-resource-management-properties) section of the docker local scheduler documentation for more information on how each resource limit maps to Docker.
|
||||
|
||||
Resource limits and reservations are applied only during the `run` and `deploy` phases of an application, and will not impact the `build` phase of an application.
|
||||
Resource reservations are applied only during the `run` and `deploy` phases of an application. Resource limits also apply during the `build` phase when explicitly configured against the `build` process type - see [Build-time Resource Limits](#build-time-resource-limits) below.
|
||||
|
||||
### Resource Limits
|
||||
|
||||
@@ -120,6 +120,40 @@ dokku resource:limit --process-type web node-js-app
|
||||
nvidia-gpu:
|
||||
```
|
||||
|
||||
#### Build-time Resource Limits
|
||||
|
||||
> [!IMPORTANT]
|
||||
> New as of 0.38.0
|
||||
|
||||
Resource limits may be applied to the build container by using the special `build` process type. This allows constraining memory and CPU usage during the `build` phase, which is otherwise unconstrained.
|
||||
|
||||
```shell
|
||||
dokku resource:limit --memory 4g --process-type build node-js-app
|
||||
```
|
||||
|
||||
```
|
||||
=====> Setting resource limits for node-js-app (build)
|
||||
memory: 4g
|
||||
```
|
||||
|
||||
Build-time limits are applied via the `docker-args-process-build` plugin trigger. They do not inherit from default (`_default_`) limits - only limits explicitly set against the `build` process type are applied at build time. This is intentional: build phases often require more memory than the runtime process, and silently inheriting a small runtime limit would cause confusing OOM failures.
|
||||
|
||||
> [!WARNING]
|
||||
> Do not use `build` as a runtime process type in your `Procfile`. The `build` name is reserved by the resource plugin to scope limits to the build container, and using it as a Procfile entry will cause those limits to be applied to that runtime container as well.
|
||||
|
||||
Supported limits per builder:
|
||||
|
||||
| Builder | `--cpu` | `--memory` | `--memory-swap` | `--nvidia-gpu` |
|
||||
|------------|---------|------------|-----------------|----------------|
|
||||
| herokuish | yes | yes | yes | yes |
|
||||
| dockerfile | no | yes | yes | no |
|
||||
| pack | no | no | no | no |
|
||||
| nixpacks | no | no | no | no |
|
||||
| railpack | no | no | no | no |
|
||||
| lambda | no | no | no | no |
|
||||
|
||||
Resource keys outside the supported set for a builder are silently ignored. Reservations (`resource:reserve`) are never applied at build time - only limits.
|
||||
|
||||
#### Clearing Resource Limits
|
||||
|
||||
In cases where the values are incorrect - or there is no desire to limit resources - resource limits may be cleared using the `resource:limit-clear` command.
|
||||
|
||||
@@ -141,6 +141,28 @@ trigger-builder-dockerfile-builder-build() {
|
||||
DOCKERFILE_ARGS+=("--label" "${1#--label=}")
|
||||
shift 1
|
||||
;;
|
||||
--memory | -m)
|
||||
DOCKERFILE_ARGS+=("--memory")
|
||||
DOCKERFILE_ARGS+=("$2")
|
||||
shift 2
|
||||
;;
|
||||
--memory=* | -m=*)
|
||||
if [[ "$1" == "--memory=*" ]]; then
|
||||
DOCKERFILE_ARGS+=("--memory" "${1#--memory=}")
|
||||
elif [[ "$1" == "-m=*" ]]; then
|
||||
DOCKERFILE_ARGS+=("--memory" "${1#-m=}")
|
||||
fi
|
||||
shift 1
|
||||
;;
|
||||
--memory-swap)
|
||||
DOCKERFILE_ARGS+=("--memory-swap")
|
||||
DOCKERFILE_ARGS+=("$2")
|
||||
shift 2
|
||||
;;
|
||||
--memory-swap=*)
|
||||
DOCKERFILE_ARGS+=("--memory-swap" "${1#--memory-swap=}")
|
||||
shift 1
|
||||
;;
|
||||
--network)
|
||||
DOCKERFILE_ARGS+=("--network")
|
||||
DOCKERFILE_ARGS+=("$2")
|
||||
@@ -205,7 +227,7 @@ trigger-builder-dockerfile-builder-build() {
|
||||
shift 1
|
||||
;;
|
||||
--ssh)
|
||||
DOCKERFILE_ARGS+=("--platform")
|
||||
DOCKERFILE_ARGS+=("--ssh")
|
||||
DOCKERFILE_ARGS+=("$2")
|
||||
shift 2
|
||||
;;
|
||||
@@ -213,6 +235,15 @@ trigger-builder-dockerfile-builder-build() {
|
||||
DOCKERFILE_ARGS+=("--ssh" "${1#--ssh=}")
|
||||
shift 1
|
||||
;;
|
||||
--tag)
|
||||
DOCKERFILE_ARGS+=("--tag")
|
||||
DOCKERFILE_ARGS+=("$2")
|
||||
shift 2
|
||||
;;
|
||||
--tag=*)
|
||||
DOCKERFILE_ARGS+=("--tag" "${1#--tag=}")
|
||||
shift 1
|
||||
;;
|
||||
--target)
|
||||
DOCKERFILE_ARGS+=("--target")
|
||||
DOCKERFILE_ARGS+=("$2")
|
||||
@@ -223,12 +254,12 @@ trigger-builder-dockerfile-builder-build() {
|
||||
shift 1
|
||||
;;
|
||||
--ulimit)
|
||||
DOCKERFILE_ARGS+=("--tag")
|
||||
DOCKERFILE_ARGS+=("--ulimit")
|
||||
DOCKERFILE_ARGS+=("$2")
|
||||
shift 2
|
||||
;;
|
||||
--tag=*)
|
||||
DOCKERFILE_ARGS+=("--tag" "${1#--tag=}")
|
||||
--ulimit=*)
|
||||
DOCKERFILE_ARGS+=("--ulimit" "${1#--ulimit=}")
|
||||
shift 1
|
||||
;;
|
||||
--check | -D | --debug | --no-cache)
|
||||
|
||||
1
plugins/resource/.gitignore
vendored
1
plugins/resource/.gitignore
vendored
@@ -5,6 +5,7 @@
|
||||
/install
|
||||
/post-*
|
||||
/report
|
||||
/docker-args-process-build
|
||||
/docker-args-process-deploy
|
||||
/docker-args-process-run
|
||||
/resource-get-property
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
SUBCOMMANDS = subcommands/limit subcommands/limit-clear subcommands/report subcommands/reserve subcommands/reserve-clear
|
||||
TRIGGERS = triggers/docker-args-process-deploy triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/report triggers/resource-get-property
|
||||
TRIGGERS = triggers/docker-args-process-build triggers/docker-args-process-deploy triggers/install triggers/post-app-clone-setup triggers/post-app-rename-setup triggers/post-delete triggers/report triggers/resource-get-property
|
||||
BUILD = commands subcommands triggers
|
||||
PLUGIN_NAME = resource
|
||||
|
||||
|
||||
@@ -18,6 +18,10 @@ func main() {
|
||||
|
||||
var err error
|
||||
switch trigger {
|
||||
case "docker-args-process-build":
|
||||
appName := flag.Arg(0)
|
||||
builderType := flag.Arg(1)
|
||||
err = resource.TriggerDockerArgsProcessBuild(appName, builderType)
|
||||
case "docker-args-process-deploy":
|
||||
appName := flag.Arg(0)
|
||||
processType := flag.Arg(3)
|
||||
|
||||
@@ -91,6 +91,84 @@ func TriggerDockerArgsProcessDeploy(appName string, processType string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerDockerArgsProcessBuild outputs build-phase docker options for a builder
|
||||
func TriggerDockerArgsProcessBuild(appName string, builderType string) error {
|
||||
stdin, err := io.ReadAll(os.Stdin)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if os.Getenv("DOKKU_OMIT_RESOURCE_ARGS") == "1" {
|
||||
fmt.Print(string(stdin))
|
||||
return nil
|
||||
}
|
||||
|
||||
allowed := validBuildLimitsForBuilder(builderType)
|
||||
if len(allowed) == 0 {
|
||||
fmt.Print(string(stdin))
|
||||
return nil
|
||||
}
|
||||
|
||||
resources, err := common.PropertyGetAll("resource", appName)
|
||||
if err != nil {
|
||||
fmt.Print(string(stdin))
|
||||
return nil
|
||||
}
|
||||
|
||||
limits := make(map[string]string)
|
||||
prefix := "build.limit."
|
||||
for key, value := range resources {
|
||||
if !strings.HasPrefix(key, prefix) {
|
||||
continue
|
||||
}
|
||||
resourceKey := strings.TrimPrefix(key, prefix)
|
||||
if !allowed[resourceKey] {
|
||||
continue
|
||||
}
|
||||
|
||||
flagName := resourceKey
|
||||
if resourceKey == "cpu" {
|
||||
flagName = "cpus"
|
||||
}
|
||||
if resourceKey == "nvidia-gpu" {
|
||||
flagName = "gpus"
|
||||
}
|
||||
limits[flagName] = value
|
||||
}
|
||||
|
||||
for key, value := range limits {
|
||||
if value == "" {
|
||||
continue
|
||||
}
|
||||
value = addMemorySuffixForDocker(key, value)
|
||||
fmt.Printf(" --%s=%s ", key, value)
|
||||
}
|
||||
|
||||
fmt.Print(string(stdin))
|
||||
return nil
|
||||
}
|
||||
|
||||
// validBuildLimitsForBuilder returns the resource keys (in property-key form)
|
||||
// that the named builder can apply during the build phase. An empty map means
|
||||
// the builder does not support build-phase resource limits.
|
||||
func validBuildLimitsForBuilder(builderType string) map[string]bool {
|
||||
switch builderType {
|
||||
case "herokuish":
|
||||
return map[string]bool{
|
||||
"cpu": true,
|
||||
"memory": true,
|
||||
"memory-swap": true,
|
||||
"nvidia-gpu": true,
|
||||
}
|
||||
case "dockerfile":
|
||||
return map[string]bool{
|
||||
"memory": true,
|
||||
"memory-swap": true,
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// TriggerInstall runs the install step for the resource plugin
|
||||
func TriggerInstall() error {
|
||||
if err := common.PropertySetup("resource"); err != nil {
|
||||
|
||||
131
tests/unit/resource_3.bats
Normal file
131
tests/unit/resource_3.bats
Normal file
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env bats
|
||||
|
||||
load test_helper
|
||||
|
||||
setup() {
|
||||
global_setup
|
||||
create_app
|
||||
}
|
||||
|
||||
teardown() {
|
||||
destroy_app
|
||||
global_teardown
|
||||
}
|
||||
|
||||
@test "(resource) resource:limit --process-type build (herokuish)" {
|
||||
run /bin/bash -c "dokku resource:limit --memory 256m --cpu 1 --memory-swap 512m --nvidia-gpu 1 --process-type build $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku resource:report --resource-build.limit.memory $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "256m"
|
||||
|
||||
run /bin/bash -c "PLUGIN_PATH=$PLUGIN_PATH PLUGIN_CORE_AVAILABLE_PATH=$PLUGIN_CORE_AVAILABLE_PATH DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT plugn trigger docker-args-process-build $TEST_APP herokuish < /dev/null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "--memory=256m"
|
||||
assert_output_contains "--cpus=1"
|
||||
assert_output_contains "--memory-swap=512m"
|
||||
assert_output_contains "--gpus=1"
|
||||
}
|
||||
|
||||
@test "(resource) resource:limit --process-type build (dockerfile filters cpu and gpu)" {
|
||||
run /bin/bash -c "dokku resource:limit --memory 512m --cpu 1 --nvidia-gpu 1 --memory-swap 1g --process-type build $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "PLUGIN_PATH=$PLUGIN_PATH PLUGIN_CORE_AVAILABLE_PATH=$PLUGIN_CORE_AVAILABLE_PATH DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT plugn trigger docker-args-process-build $TEST_APP dockerfile < /dev/null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_contains "--memory=512m"
|
||||
assert_output_contains "--memory-swap=1g"
|
||||
assert_output_not_contains "--cpus="
|
||||
assert_output_not_contains "--gpus="
|
||||
}
|
||||
|
||||
@test "(resource) resource:limit --process-type build (pack/lambda emit nothing)" {
|
||||
run /bin/bash -c "dokku resource:limit --memory 512m --process-type build $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "PLUGIN_PATH=$PLUGIN_PATH PLUGIN_CORE_AVAILABLE_PATH=$PLUGIN_CORE_AVAILABLE_PATH DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT plugn trigger docker-args-process-build $TEST_APP pack < /dev/null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "--memory"
|
||||
|
||||
run /bin/bash -c "PLUGIN_PATH=$PLUGIN_PATH PLUGIN_CORE_AVAILABLE_PATH=$PLUGIN_CORE_AVAILABLE_PATH DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT plugn trigger docker-args-process-build $TEST_APP lambda < /dev/null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "--memory"
|
||||
}
|
||||
|
||||
@test "(resource) resource:limit defaults do not leak into build trigger" {
|
||||
run /bin/bash -c "dokku resource:limit --memory 128m $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "PLUGIN_PATH=$PLUGIN_PATH PLUGIN_CORE_AVAILABLE_PATH=$PLUGIN_CORE_AVAILABLE_PATH DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT plugn trigger docker-args-process-build $TEST_APP herokuish < /dev/null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "--memory"
|
||||
}
|
||||
|
||||
@test "(resource) resource:limit reservations do not apply at build time" {
|
||||
run /bin/bash -c "dokku resource:reserve --memory 128m --process-type build $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "PLUGIN_PATH=$PLUGIN_PATH PLUGIN_CORE_AVAILABLE_PATH=$PLUGIN_CORE_AVAILABLE_PATH DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT plugn trigger docker-args-process-build $TEST_APP herokuish < /dev/null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "--memory-reservation"
|
||||
assert_output_not_contains "--memory"
|
||||
}
|
||||
|
||||
@test "(resource) DOKKU_OMIT_RESOURCE_ARGS suppresses build trigger" {
|
||||
run /bin/bash -c "dokku resource:limit --memory 256m --process-type build $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "PLUGIN_PATH=$PLUGIN_PATH PLUGIN_CORE_AVAILABLE_PATH=$PLUGIN_CORE_AVAILABLE_PATH DOKKU_LIB_ROOT=$DOKKU_LIB_ROOT DOKKU_OMIT_RESOURCE_ARGS=1 plugn trigger docker-args-process-build $TEST_APP herokuish < /dev/null"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
assert_output_not_contains "--memory"
|
||||
}
|
||||
|
||||
@test "(resource) resource:limit-clear --process-type build" {
|
||||
run /bin/bash -c "dokku resource:limit --memory 256m --process-type build $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku resource:report --resource-build.limit.memory $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_output "256m"
|
||||
|
||||
run /bin/bash -c "dokku resource:limit-clear --process-type build $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_success
|
||||
|
||||
run /bin/bash -c "dokku resource:report --resource-build.limit.memory $TEST_APP"
|
||||
echo "output: $output"
|
||||
echo "status: $status"
|
||||
assert_failure
|
||||
}
|
||||
Reference in New Issue
Block a user