Skip to content

Commit fd75f02

Browse files
Romuald Atchadéstalb
authored andcommitted
Merge branch 'feature/build-pod-resource' into 'main'
Teach runner how to set pod-level resources for build pods Closes #39085 See merge request https://gitlab.com/gitlab-org/gitlab-runner/-/merge_requests/5922 Merged-by: Romuald Atchadé <ratchade@gitlab.com> Approved-by: Evan Read <eread@gitlab.com> Approved-by: Sam Roque-Worcel <sroque-worcel@gitlab.com> Approved-by: Romuald Atchadé <ratchade@gitlab.com> Approved-by: Roshni Sarangadharan <rsarangadharan@gitlab.com> Reviewed-by: Stéphane Talbot <stephane.talbot@univ-savoie.fr> Reviewed-by: Alper Akgun <aakgun@gitlab.com> Reviewed-by: Romuald Atchadé <ratchade@gitlab.com> Reviewed-by: GitLab Duo <gitlab-duo@gitlab.com> Reviewed-by: Sam Roque-Worcel <sroque-worcel@gitlab.com> Co-authored-by: Stephane Talbot <Stephane.Talbot@univ-savoie.fr> Co-authored-by: Stéphane Talbot <stephane.talbot@univ-savoie.fr>
2 parents a1db62d + e562863 commit fd75f02

8 files changed

Lines changed: 307 additions & 1 deletion

File tree

common/config.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -669,6 +669,14 @@ type KubernetesConfig struct {
669669
HelperEphemeralStorageLimitOverwriteMaxAllowed string `toml:"helper_ephemeral_storage_limit_overwrite_max_allowed,omitempty" json:"helper_ephemeral_storage_limit_overwrite_max_allowed" long:"helper-ephemeral_storage-limit-overwrite-max-allowed" env:"KUBERNETES_HELPER_EPHEMERAL_STORAGE_LIMIT_OVERWRITE_MAX_ALLOWED" description:"If set, the max amount the helper ephemeral storage limit can be set to. Used with the KUBERNETES_HELPER_EPHEMERAL_STORAGE_LIMIT variable in the build."`
670670
HelperEphemeralStorageRequest string `toml:"helper_ephemeral_storage_request,omitempty" json:"helper_ephemeral_storage_request" long:"helper-ephemeral_storage-request" env:"KUBERNETES_HELPER_EPHEMERAL_STORAGE_REQUEST" description:"The amount of ephemeral storage requested for build helper containers"`
671671
HelperEphemeralStorageRequestOverwriteMaxAllowed string `toml:"helper_ephemeral_storage_request_overwrite_max_allowed,omitempty" json:"helper_ephemeral_storage_request_overwrite_max_allowed" long:"helper-ephemeral_storage-request-overwrite-max-allowed" env:"KUBERNETES_HELPER_EPHEMERAL_STORAGE_REQUEST_OVERWRITE_MAX_ALLOWED" description:"If set, the max amount the helper ephemeral storage request can be set to. Used with the KUBERNETES_HELPER_EPHEMERAL_STORAGE_REQUEST variable in the build."`
672+
PodCPULimit string `toml:"pod_cpu_limit,omitempty" json:"pod_cpu_limit" long:"pod-cpu-limit" env:"KUBERNETES_POD_CPU_LIMIT" description:"The CPU allocation given to the build pod"`
673+
PodCPULimitOverwriteMaxAllowed string `toml:"pod_cpu_limit_overwrite_max_allowed,omitempty" json:"pod_cpu_limit_overwrite_max_allowed" long:"pod-cpu-limit-overwrite-max-allowed" env:"KUBERNETES_POD_CPU_LIMIT_OVERWRITE_MAX_ALLOWED" description:"If set, the maximum amount the pod CPU limit can be set to. Used with the KUBERNETES_POD_CPU_LIMIT variable in the build."`
674+
PodCPURequest string `toml:"pod_cpu_request,omitempty" json:"pod_cpu_request" long:"pod-cpu-request" env:"KUBERNETES_POD_CPU_REQUEST" description:"The CPU allocation requested for the build pod"`
675+
PodCPURequestOverwriteMaxAllowed string `toml:"pod_cpu_request_overwrite_max_allowed,omitempty" json:"pod_cpu_request_overwrite_max_allowed" long:"pod-cpu-request-overwrite-max-allowed" env:"KUBERNETES_POD_CPU_REQUEST_OVERWRITE_MAX_ALLOWED" description:"If set, the maximum amount the pod CPU request can be set to. Used with the KUBERNETES_POD_CPU_REQUEST variable in the build."`
676+
PodMemoryLimit string `toml:"pod_memory_limit,omitempty" json:"pod_memory_limit" long:"pod-memory-limit" env:"KUBERNETES_POD_MEMORY_LIMIT" description:"The amount of memory allocated to the build pod"`
677+
PodMemoryLimitOverwriteMaxAllowed string `toml:"pod_memory_limit_overwrite_max_allowed,omitempty" json:"pod_memory_limit_overwrite_max_allowed" long:"pod-memory-limit-overwrite-max-allowed" env:"KUBERNETES_POD_MEMORY_LIMIT_OVERWRITE_MAX_ALLOWED" description:"If set, the maximum amount the pod memory limit can be set to. Used with the KUBERNETES_POD_MEMORY_LIMIT variable in the build."`
678+
PodMemoryRequest string `toml:"pod_memory_request,omitempty" json:"pod_memory_request" long:"pod-memory-request" env:"KUBERNETES_POD_MEMORY_REQUEST" description:"The amount of memory requested from the build pod"`
679+
PodMemoryRequestOverwriteMaxAllowed string `toml:"pod_memory_request_overwrite_max_allowed,omitempty" json:"pod_memory_request_overwrite_max_allowed" long:"pod-memory-request-overwrite-max-allowed" env:"KUBERNETES_POD_MEMORY_REQUEST_OVERWRITE_MAX_ALLOWED" description:"If set, the maximum amount the pod memory request can be set to. Used with the KUBERNETES_POD_MEMORY_REQUEST variable in the build."`
672680
AllowedImages []string `toml:"allowed_images,omitempty" json:"allowed_images,omitempty" long:"allowed-images" env:"KUBERNETES_ALLOWED_IMAGES" description:"Image allowlist"`
673681
AllowedPullPolicies []DockerPullPolicy `toml:"allowed_pull_policies,omitempty" json:"allowed_pull_policies,omitempty" long:"allowed-pull-policies" env:"KUBERNETES_ALLOWED_PULL_POLICIES" description:"Pull policy allowlist"`
674682
AllowedServices []string `toml:"allowed_services,omitempty" json:"allowed_services,omitempty" long:"allowed-services" env:"KUBERNETES_ALLOWED_SERVICES" description:"Service allowlist"`

docs/executors/kubernetes/_index.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,16 @@ Use the following settings in the `config.toml` file to configure the Kubernetes
234234
| `service_cpu_limit_overwrite_max_allowed` | The maximum amount that the CPU allocation can be written to for service containers. When empty, it disables the CPU limit overwrite feature. |
235235
| `service_cpu_request` | The CPU allocation requested for build service containers. |
236236
| `service_cpu_request_overwrite_max_allowed` | The maximum amount that the CPU allocation request can be written to for service containers. When empty, it disables the CPU request overwrite feature. |
237+
| `pod_cpu_limit` | The CPU allocation given to build pod. |
238+
| `pod_cpu_limit_overwrite_max_allowed` | The maximum amount that the CPU allocation can be written to for build pod. When empty, it disables the CPU limit overwrite feature. |
239+
| `pod_cpu_request` | The CPU allocation requested for build pod. |
240+
| `pod_cpu_request_overwrite_max_allowed` | The maximum amount that the CPU allocation request can be written to for build pod. When empty, it disables the CPU request overwrite feature. |
241+
242+
{{< alert type="note" >}}
243+
244+
Pod-level resource specifications have been introduced as alpha features in [Kubernetes v1.32](https://v1-32.docs.kubernetes.io/blog/2024/12/11/kubernetes-v1-32-release/#pod-level-resource-specifications) and graduated to beta in [Kubernetes v1.34](https://kubernetes.io/blog/2025/09/22/kubernetes-v1-34-pod-level-resources/).
245+
246+
{{< /alert >}}
237247

238248
### Memory requests and limits
239249

@@ -251,6 +261,10 @@ Use the following settings in the `config.toml` file to configure the Kubernetes
251261
| `service_memory_limit_overwrite_max_allowed` | The maximum amount that the memory allocation can be written to for service containers. When empty, it disables the memory limit overwrite feature. |
252262
| `service_memory_request` | The amount of memory requested for build service containers. |
253263
| `service_memory_request_overwrite_max_allowed` | The maximum amount that the memory allocation request can be written to for service containers. When empty, it disables the memory request overwrite feature. |
264+
| `pod_memory_limit` | The amount of memory allocated to build pod. |
265+
| `pod_memory_limit_overwrite_max_allowed` | The maximum amount that the memory allocation can be written to for build pod. When empty, it disables the memory limit overwrite feature. |
266+
| `pod_memory_request` | The amount of memory requested for build pod. |
267+
| `pod_memory_request_overwrite_max_allowed` | The maximum amount that the memory allocation request can be written to for build pod. When empty, it disables the memory request overwrite feature. |
254268

255269
#### Helper container memory sizing recommendations
256270

executors/kubernetes/internal/watchers/pod_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,7 @@ func TestPodWatcherWrongObject(t *testing.T) {
187187
handler := podWatcher.resourceHandler()
188188

189189
assert.NotPanics(t, func() {
190-
handler.OnAdd(test.object, true)
190+
handler.OnAdd(test.object, false)
191191
})
192192
})
193193
}

executors/kubernetes/kubernetes.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1140,6 +1140,17 @@ func (s *executor) initContainerResources() api.ResourceRequirements {
11401140
return resources
11411141
}
11421142

1143+
func (s *executor) podResourcesReference() *api.ResourceRequirements {
1144+
resources := api.ResourceRequirements{}
1145+
1146+
if s.configurationOverwrites != nil {
1147+
resources.Limits = s.configurationOverwrites.podLimits
1148+
resources.Requests = s.configurationOverwrites.podRequests
1149+
}
1150+
1151+
return &resources
1152+
}
1153+
11431154
func (s *executor) buildPermissionsInitContainer(os string) (api.Container, error) {
11441155
pullPolicy, err := s.pullManager.GetPullPolicyFor(helperContainerName)
11451156
if err != nil {
@@ -2368,6 +2379,7 @@ func (s *executor) preparePodConfig(opts podConfigPrepareOpts) (api.Pod, error)
23682379
DNSConfig: s.Config.Kubernetes.GetDNSConfig(),
23692380
RuntimeClassName: s.Config.Kubernetes.RuntimeClassName,
23702381
PriorityClassName: s.Config.Kubernetes.PriorityClassName,
2382+
Resources: s.podResourcesReference(),
23712383
},
23722384
}
23732385

executors/kubernetes/kubernetes_integration_test.go

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,10 @@ import (
3333
"github.com/stretchr/testify/require"
3434
v1 "k8s.io/api/core/v1"
3535
policyv1 "k8s.io/api/policy/v1"
36+
"k8s.io/apimachinery/pkg/api/resource"
3637
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3738
"k8s.io/apimachinery/pkg/labels"
39+
versionutil "k8s.io/apimachinery/pkg/util/version"
3840
"k8s.io/apimachinery/pkg/watch"
3941
k8s "k8s.io/client-go/kubernetes"
4042
"k8s.io/client-go/rest"
@@ -2883,6 +2885,130 @@ func runMultiPullPolicyBuild(t *testing.T, build *common.Build) error {
28832885
return err
28842886
}
28852887

2888+
func mustCreateResourceList(t *testing.T, cpu, memory string) v1.ResourceList {
2889+
var rCPU, rMemory resource.Quantity
2890+
var err error
2891+
if cpu != "" {
2892+
rCPU, err = resource.ParseQuantity(cpu)
2893+
}
2894+
require.NoError(t, err)
2895+
2896+
if memory != "" {
2897+
rMemory, err = resource.ParseQuantity(memory)
2898+
}
2899+
require.NoError(t, err)
2900+
2901+
resources := make(v1.ResourceList)
2902+
q := resource.Quantity{}
2903+
2904+
if rCPU != q {
2905+
resources[v1.ResourceCPU] = rCPU
2906+
}
2907+
if rMemory != q {
2908+
resources[v1.ResourceMemory] = rMemory
2909+
}
2910+
2911+
return resources
2912+
}
2913+
2914+
func skipKubectlIntegrationTestsIfNotOnLinux(t *testing.T, client *k8s.Clientset) {
2915+
nodes, err := client.CoreV1().Nodes().List(context.TODO(), metav1.ListOptions{})
2916+
require.NoError(t, err)
2917+
2918+
os := nodes.Items[0].Status.NodeInfo.OperatingSystem
2919+
2920+
// skip tests on windows cluster
2921+
if os != "linux" {
2922+
t.Skip("Non linux -- skipping tests")
2923+
}
2924+
}
2925+
2926+
func skipKubectlIntegrationTestsIfOnOldCluster(t *testing.T, client *k8s.Clientset, minimalVersion string) {
2927+
serverVersion, err := client.Discovery().ServerVersion()
2928+
require.NoError(t, err)
2929+
2930+
version, err := versionutil.Parse(serverVersion.String())
2931+
require.NoError(t, err)
2932+
2933+
res, err := version.Compare(minimalVersion)
2934+
require.NoError(t, err)
2935+
2936+
// skip tests if cluster is below minimalVersion
2937+
if res == -1 {
2938+
t.Skipf("Kubernetes server (%s) is older than %s -- skipping tests", serverVersion.String(), minimalVersion)
2939+
}
2940+
}
2941+
2942+
func TestKubernetesBuildPodResources(t *testing.T) {
2943+
t.Parallel()
2944+
2945+
kubernetes.SkipKubectlIntegrationTests(t, "kubectl", "cluster-info")
2946+
2947+
client := getTestKubeClusterClient(t)
2948+
2949+
// Pod Level Resources Graduated to Beta in kubernetes v1.34
2950+
skipKubectlIntegrationTestsIfOnOldCluster(t, client, "1.34.0")
2951+
// Pod-level resources are not supported for Windows pods
2952+
skipKubectlIntegrationTestsIfNotOnLinux(t, client)
2953+
2954+
ctxTimeout := time.Minute
2955+
2956+
tests := map[string]struct {
2957+
resources map[string]string
2958+
verifyFn func(*testing.T, v1.Pod)
2959+
}{
2960+
"set all pod-level resources": {
2961+
resources: map[string]string{
2962+
"PodCPURequest": "1",
2963+
"PodCPULimit": "4",
2964+
"PodMemoryRequest": "1Gi",
2965+
"PodMemoryLimit": "8Gi",
2966+
},
2967+
verifyFn: func(t *testing.T, pod v1.Pod) {
2968+
resources := pod.Spec.Resources
2969+
expectedRequests := mustCreateResourceList(t, "1", "1Gi")
2970+
expectedLimits := mustCreateResourceList(t, "4", "8Gi")
2971+
2972+
require.NotNil(t, resources)
2973+
assert.Equal(t, expectedRequests, resources.Requests)
2974+
assert.Equal(t, expectedLimits, resources.Limits)
2975+
},
2976+
},
2977+
}
2978+
2979+
for name, test := range tests {
2980+
t.Run(name, func(t *testing.T) {
2981+
t.Parallel()
2982+
2983+
build := getTestBuild(t, common.GetRemoteSuccessfulBuild)
2984+
2985+
build.Runner.Kubernetes.PodCPURequest = test.resources["PodCPURequest"]
2986+
build.Runner.Kubernetes.PodCPULimit = test.resources["PodCPULimit"]
2987+
build.Runner.Kubernetes.PodMemoryRequest = test.resources["PodMemoryRequest"]
2988+
build.Runner.Kubernetes.PodMemoryLimit = test.resources["PodMemoryLimit"]
2989+
2990+
defer buildtest.OnUserStage(build, func() {
2991+
ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout)
2992+
defer cancel()
2993+
pods, err := client.CoreV1().Pods(ciNamespace).List(
2994+
ctx,
2995+
metav1.ListOptions{
2996+
LabelSelector: labels.Set(build.Runner.Kubernetes.PodLabels).String(),
2997+
},
2998+
)
2999+
require.NoError(t, err)
3000+
require.NotEmpty(t, pods.Items)
3001+
pod := pods.Items[0]
3002+
3003+
test.verifyFn(t, pod)
3004+
})()
3005+
3006+
err := build.Run(&common.Config{}, &common.Trace{Writer: os.Stdout})
3007+
assert.NoError(t, err)
3008+
})
3009+
}
3010+
}
3011+
28863012
func TestKubernetesAllowedImages(t *testing.T) {
28873013
t.Parallel()
28883014

0 commit comments

Comments
 (0)