Allow setting resources for kubernetes on a per-step basis (#1767)

This add a simple implementation of requests/limits for individual
steps. There is no validation of what the resource actually is beyond
checking that it can successfully be converted to a Quantity, so it can
be used for things other than just memory/CPU.

close #1809
This commit is contained in:
Stephen Muth
2023-06-03 18:50:08 -04:00
committed by GitHub
parent 14177635b6
commit 2941e508b3
10 changed files with 273 additions and 131 deletions

View File

@@ -123,15 +123,19 @@ Configures if the gRPC server certificate should be verified, only valid when `W
### `WOODPECKER_BACKEND` ### `WOODPECKER_BACKEND`
> Default: `auto-detect` > Default: `auto-detect`
Configures the backend engine to run pipelines on. Possible values are `auto-detect`, `docker`, `local` or `ssh`. Configures the backend engine to run pipelines on. Possible values are `auto-detect`, `docker`, `local`, `ssh` or `kubernetes`.
### `WOODPECKER_BACKEND_DOCKER_*` ### `WOODPECKER_BACKEND_DOCKER_*`
See [Docker backend configuration](backends/docker/#configuration) See [Docker backend configuration](./22-backends/10-docker.md#configuration)
### `WOODPECKER_BACKEND_SSH_*` ### `WOODPECKER_BACKEND_SSH_*`
See [SSH backend configuration](backends/ssh/#configuration) See [SSH backend configuration](./22-backends/30-ssh.md#configuration)
### `WOODPECKER_BACKEND_K8S_*`
See [Kubernetes backend configuration](./22-backends/40-kubernetes.md#configuration)
## Advanced Settings ## Advanced Settings

View File

@@ -0,0 +1,66 @@
# Kubernetes backend
:::caution
Kubernetes support is still experimental and not all pipeline features are fully supported yet.
Check the [current state](https://github.com/woodpecker-ci/woodpecker/issues/1513)
:::
The kubernetes backend executes each step inside a newly created pod. A PVC is also created for the lifetime of the pipeline, for transferring files between steps.
## Configuration
### `WOODPECKER_BACKEND_K8S_NAMESPACE`
> Default: `woodpecker`
The namespace to create worker pods in.
### `WOODPECKER_BACKEND_K8S_VOLUME_SIZE`
> Default: `10G`
The volume size of the pipeline volume.
### `WOODPECKER_BACKEND_K8S_STORAGE_CLASS`
> Default: empty
The storage class to use for the pipeline volume.
### `WOODPECKER_BACKEND_K8S_STORAGE_RWX`
> Default: `true`
Determines if `RWX` should be used for the pipeline volume's [access mode](https://kubernetes.io/docs/concepts/storage/persistent-volumes/#access-modes). If false, `RWO` is used instead.
### `WOODPECKER_BACKEND_K8S_POD_LABELS`
> Default: empty
Additional labels to apply to worker pods. Must be a YAML object, e.g. `{"example.com/test-label":"test-value"}`.
### `WOODPECKER_BACKEND_K8S_POD_ANNOTATIONS`
> Default: empty
Additional annotations to apply to worker pods. Must be a YAML object, e.g. `{"example.com/test-annotation":"test-value"}`.
## Resources
The kubernetes backend also allows for specifying requests and limits on a per-step basic, most commonly for CPU and memory.
Example pipeline configuration:
```yaml
pipeline:
build:
image: golang
commands:
- go get
- go build
- go test
backend_options:
kubernetes:
resources:
requests:
memory: 128Mi
cpu: 1000m
limits:
memory: 256Mi
```
See the [kubernetes documentation](https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/) for more information on using resources.

View File

@@ -1,6 +1,6 @@
# Kubernetes # Kubernetes
Woodpecker does support Kubernetes as a backend. Woodpecker does support Kubernetes as a backend. See the [Kubernetes backend configuration](./22-backends/40-kubernetes.md#configuration) for backend-specific options.
:::caution :::caution
Kubernetes support is still experimental and not all pipeline features are fully supported yet. Kubernetes support is still experimental and not all pipeline features are fully supported yet.

View File

@@ -1,6 +1,7 @@
package kubernetes package kubernetes
import ( import (
"fmt"
"strings" "strings"
"github.com/woodpecker-ci/woodpecker/pipeline/backend/common" "github.com/woodpecker-ci/woodpecker/pipeline/backend/common"
@@ -62,35 +63,21 @@ func Pod(namespace string, step *types.Step, labels, annotations map[string]stri
hostAliases = append(hostAliases, v1.HostAlias{IP: host[1], Hostnames: []string{host[0]}}) hostAliases = append(hostAliases, v1.HostAlias{IP: host[1], Hostnames: []string{host[0]}})
} }
// TODO: add support for resource limits resourceRequirements := v1.ResourceRequirements{Requests: v1.ResourceList{}, Limits: v1.ResourceList{}}
// if step.Resources.CPULimit == "" { var err error
// step.Resources.CPULimit = "2" for key, val := range step.BackendOptions.Kubernetes.Resources.Requests {
// } resourceKey := v1.ResourceName(key)
// if step.Resources.MemoryLimit == "" { resourceRequirements.Requests[resourceKey], err = resource.ParseQuantity(val)
// step.Resources.MemoryLimit = "2G" if err != nil {
// } return nil, fmt.Errorf("resource request '%v' quantity '%v': %w", key, val, err)
// memoryLimit := resource.MustParse(step.Resources.MemoryLimit) }
// CPULimit := resource.MustParse(step.Resources.CPULimit) }
for key, val := range step.BackendOptions.Kubernetes.Resources.Limits {
memoryLimit := resource.MustParse("2G") resourceKey := v1.ResourceName(key)
CPULimit := resource.MustParse("2") resourceRequirements.Limits[resourceKey], err = resource.ParseQuantity(val)
if err != nil {
memoryLimitValue, _ := memoryLimit.AsInt64() return nil, fmt.Errorf("resource limit '%v' quantity '%v': %w", key, val, err)
CPULimitValue, _ := CPULimit.AsInt64() }
loadfactor := 0.5
memoryRequest := resource.NewQuantity(int64(float64(memoryLimitValue)*loadfactor), resource.DecimalSI)
CPURequest := resource.NewQuantity(int64(float64(CPULimitValue)*loadfactor), resource.DecimalSI)
resources := v1.ResourceRequirements{
Requests: v1.ResourceList{
v1.ResourceMemory: *memoryRequest,
v1.ResourceCPU: *CPURequest,
},
Limits: v1.ResourceList{
v1.ResourceMemory: memoryLimit,
v1.ResourceCPU: CPULimit,
},
} }
podName, err := dnsName(step.Name) podName, err := dnsName(step.Name)
@@ -130,7 +117,7 @@ func Pod(namespace string, step *types.Step, labels, annotations map[string]stri
WorkingDir: step.WorkingDir, WorkingDir: step.WorkingDir,
Env: mapToEnvVars(step.Environment), Env: mapToEnvVars(step.Environment),
VolumeMounts: volMounts, VolumeMounts: volMounts,
Resources: resources, Resources: resourceRequirements,
SecurityContext: &v1.SecurityContext{ SecurityContext: &v1.SecurityContext{
Privileged: &step.Privileged, Privileged: &step.Privileged,
}, },

View File

@@ -0,0 +1,6 @@
package types
// BackendOptions defines advanced options for specific backends
type BackendOptions struct {
Kubernetes KubernetesBackendOptions `json:"kubernetes,omitempty"`
}

View File

@@ -0,0 +1,12 @@
package types
// KubernetesBackendOptions defines all the advanced options for the kubernetes backend
type KubernetesBackendOptions struct {
Resources Resources `json:"resouces,omitempty"`
}
// Resources defines two maps for kubernetes resource definitions
type Resources struct {
Requests map[string]string `json:"requests,omitempty"`
Limits map[string]string `json:"limits,omitempty"`
}

View File

@@ -33,4 +33,5 @@ type Step struct {
NetworkMode string `json:"network_mode,omitempty"` NetworkMode string `json:"network_mode,omitempty"`
IpcMode string `json:"ipc_mode,omitempty"` IpcMode string `json:"ipc_mode,omitempty"`
Sysctls map[string]string `json:"sysctls,omitempty"` Sysctls map[string]string `json:"sysctls,omitempty"`
BackendOptions BackendOptions `json:"backend_options,omitempty"`
} }

View File

@@ -110,6 +110,16 @@ func (c *Compiler) createProcess(name string, container *yaml.Container, section
} }
} }
// Kubernetes advanced settings
backendOptions := backend.BackendOptions{
Kubernetes: backend.KubernetesBackendOptions{
Resources: backend.Resources{
Limits: container.BackendOptions.Kubernetes.Resources.Limits,
Requests: container.BackendOptions.Kubernetes.Resources.Requests,
},
},
}
memSwapLimit := int64(container.MemSwapLimit) memSwapLimit := int64(container.MemSwapLimit)
if c.reslimit.MemSwapLimit != 0 { if c.reslimit.MemSwapLimit != 0 {
memSwapLimit = c.reslimit.MemSwapLimit memSwapLimit = c.reslimit.MemSwapLimit
@@ -176,6 +186,7 @@ func (c *Compiler) createProcess(name string, container *yaml.Container, section
Failure: failure, Failure: failure,
NetworkMode: networkMode, NetworkMode: networkMode,
IpcMode: ipcMode, IpcMode: ipcMode,
BackendOptions: backendOptions,
} }
} }

View File

@@ -19,6 +19,20 @@ type (
Email string Email string
} }
// Advanced backend options
BackendOptions struct {
Kubernetes KubernetesBackendOptions `yaml:"kubernetes,omitempty"`
}
KubernetesBackendOptions struct {
Resources Resources `yaml:"resources,omitempty"`
}
Resources struct {
Requests map[string]string `yaml:"requests,omitempty"`
Limits map[string]string `yaml:"limits,omitempty"`
}
// Containers denotes an ordered collection of containers. // Containers denotes an ordered collection of containers.
Containers struct { Containers struct {
Containers []*Container Containers []*Container
@@ -62,6 +76,7 @@ type (
Sysctls types.SliceorMap `yaml:"sysctls,omitempty"` Sysctls types.SliceorMap `yaml:"sysctls,omitempty"`
When constraint.When `yaml:"when,omitempty"` When constraint.When `yaml:"when,omitempty"`
Settings map[string]interface{} `yaml:"settings"` Settings map[string]interface{} `yaml:"settings"`
BackendOptions BackendOptions `yaml:"backend_options,omitempty"`
} }
) )

View File

@@ -243,6 +243,9 @@
"type": "string", "type": "string",
"enum": ["fail", "ignore"], "enum": ["fail", "ignore"],
"default": "fail" "default": "fail"
},
"backend_options": {
"$ref": "#/definitions/step_backend_options"
} }
} }
}, },
@@ -480,6 +483,43 @@
"description": "Read more: https://woodpecker-ci.org/docs/usage/pipeline-syntax#directory", "description": "Read more: https://woodpecker-ci.org/docs/usage/pipeline-syntax#directory",
"type": "string" "type": "string"
}, },
"step_backend_options": {
"description": "Advanced options for the different agent backends",
"type": "object",
"properties": {
"kubernetes" :{
"$ref": "#/definitions/step_backend_kubernetes_resources"
}
}
},
"step_backend_kubernetes": {
"description": "Advanced options for the kubernetes agent backends",
"type": "object",
"properties": {
"resources" :{
"$ref": "#/definitions/step_backend_kubernetes_resources"
}
}
},
"step_backend_kubernetes_resources": {
"description": "Resources for the kubernetes backend. Read more: https://woodpecker-ci.org/docs/administration/backends/kubernetes",
"type": "object",
"properties": {
"requests": {
"$ref": "#/definitions/step_kubernetes_resources_object"
},
"limits": {
"$ref": "#/definitions/step_kubernetes_resources_object"
}
}
},
"step_kubernetes_resources_object": {
"description": "A list of kubernetes resource mappings",
"type": "object",
"additionalProperties": {
"type": "string"
}
},
"services": { "services": {
"description": "Read more: https://woodpecker-ci.org/docs/usage/services", "description": "Read more: https://woodpecker-ci.org/docs/usage/services",
"type": "object", "type": "object",