From f2ee8621300c0660b696917d1ee5b4754daac666 Mon Sep 17 00:00:00 2001 From: Kyle Gato Date: Tue, 18 Aug 2026 10:45:08 -0400 Subject: [PATCH] chore(helm): configurable lifecycle, terminationGracePeriodSeconds and autoscaling behavior Adds three pass-through values so the chart can express graceful pod shutdown and HPA scaling behavior. All three default to empty, so rendered output is unchanged for existing users (verified by diffing `helm template` against the previous chart -- the only difference is the `helm.sh/chart` version label). lifecycle: {} -> container `lifecycle` terminationGracePeriodSeconds: '' -> pod `terminationGracePeriodSeconds` autoscaling.behavior: {} -> HPA `spec.behavior` Motivation: without a `preStop` hook there is no way to drain a pod gracefully, and HPA scale-down deletes pods directly rather than through the eviction API, so a PodDisruptionBudget does not apply to it. On a deployment of this chart behind an ingress controller that produced HTTP 504s on every scale-down -- the container received SIGTERM while the ingress was still routing to it, because Service endpoint removal is asynchronous. A `preStop` sleep keeps the pod serving until that propagates, and `autoscaling.behavior` lets operators damp the scale-down oscillation that triggers a drain in the first place. These were the only knobs missing to solve it from values; everything else needed was already configurable. Example: terminationGracePeriodSeconds: 60 lifecycle: preStop: exec: command: ["/bin/sh", "-c", "sleep 30"] autoscaling: enabled: true behavior: scaleDown: stabilizationWindowSeconds: 900 policies: - type: Pods value: 1 periodSeconds: 300 Note `terminationGracePeriodSeconds` should exceed any `preStop` sleep, or the kubelet SIGKILLs the container mid-hook. Verified: `helm lint` clean; default render byte-identical to the previous chart apart from the version label; the three values render into the expected fields; and the rendered objects are accepted by a Kubernetes 1.35 API server via `kubectl apply --dry-run=server`. Chart version 1.2.0 -> 1.3.0 (additive features, no breaking change). --- .../github-actions-cache-server/Chart.yaml | 2 +- .../templates/deployment.yaml | 7 +++++++ .../templates/hpa.yaml | 4 ++++ .../github-actions-cache-server/values.yaml | 19 +++++++++++++++++++ 4 files changed, 31 insertions(+), 1 deletion(-) diff --git a/install/kubernetes/github-actions-cache-server/Chart.yaml b/install/kubernetes/github-actions-cache-server/Chart.yaml index 3365c67..637ceee 100644 --- a/install/kubernetes/github-actions-cache-server/Chart.yaml +++ b/install/kubernetes/github-actions-cache-server/Chart.yaml @@ -15,7 +15,7 @@ type: application # This is the chart version. This version number should be incremented each time you make changes # to the chart and its templates, including the app version. # Versions are expected to follow Semantic Versioning (https://semver.org/) -version: 1.2.0 +version: 1.3.0 # This is the version number of the application being deployed. This version number should be # incremented each time you make changes to the application. Versions are not expected to diff --git a/install/kubernetes/github-actions-cache-server/templates/deployment.yaml b/install/kubernetes/github-actions-cache-server/templates/deployment.yaml index bf977d9..97498e9 100644 --- a/install/kubernetes/github-actions-cache-server/templates/deployment.yaml +++ b/install/kubernetes/github-actions-cache-server/templates/deployment.yaml @@ -38,6 +38,9 @@ spec: {{- if .Values.topologySpreadConstraints }} topologySpreadConstraints: {{- toYaml .Values.topologySpreadConstraints | nindent 8 }} {{- end }} + {{- if .Values.terminationGracePeriodSeconds }} + terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }} + {{- end }} containers: - name: {{ .Chart.Name }} securityContext: @@ -54,6 +57,10 @@ spec: {{- toYaml .Values.readinessProbe | nindent 12 }} resources: {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.lifecycle }} + lifecycle: + {{- toYaml . | nindent 12 }} + {{- end }} {{- if or (eq $pvcEnabled "true") .Values.extraVolumeMounts }} volumeMounts: {{- if eq $pvcEnabled "true" }} diff --git a/install/kubernetes/github-actions-cache-server/templates/hpa.yaml b/install/kubernetes/github-actions-cache-server/templates/hpa.yaml index e51cc12..1fde30e 100644 --- a/install/kubernetes/github-actions-cache-server/templates/hpa.yaml +++ b/install/kubernetes/github-actions-cache-server/templates/hpa.yaml @@ -13,6 +13,10 @@ spec: name: {{ include "github-actions-cache-server.fullname" . }} minReplicas: {{ .Values.autoscaling.minReplicas }} maxReplicas: {{ .Values.autoscaling.maxReplicas }} + {{- with .Values.autoscaling.behavior }} + behavior: + {{- toYaml . | nindent 4 }} + {{- end }} metrics: {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} - type: Resource diff --git a/install/kubernetes/github-actions-cache-server/values.yaml b/install/kubernetes/github-actions-cache-server/values.yaml index 591d300..de121b7 100644 --- a/install/kubernetes/github-actions-cache-server/values.yaml +++ b/install/kubernetes/github-actions-cache-server/values.yaml @@ -217,6 +217,25 @@ autoscaling: maxReplicas: 10 targetCPUUtilizationPercentage: 70 targetMemoryUtilizationPercentage: 70 + # -- Scaling behavior for the HPA (`spec.behavior`), passed through verbatim. + # Empty by default, so Kubernetes applies its own defaults. + # Useful to damp scale-down flapping, e.g. + # `scaleDown: {stabilizationWindowSeconds: 900, policies: [{type: Pods, value: 1, periodSeconds: 300}]}`. + behavior: {} + +# -- Container lifecycle hooks (`lifecycle`), passed through verbatim. +# Empty by default. A `preStop` hook is the usual way to let in-flight requests +# finish: the container keeps serving while the Service endpoint removal +# propagates to ingress controllers, which is asynchronous. For example +# `preStop: {exec: {command: [/bin/sh, -c, sleep 30]}}` — pair it with a +# terminationGracePeriodSeconds larger than the sleep, or the kubelet will +# SIGKILL the container mid-hook. +lifecycle: {} + +# -- Grace period for pod termination (`terminationGracePeriodSeconds`). +# Empty by default, so Kubernetes applies its default of 30. +# Must exceed any `lifecycle.preStop` sleep above. +terminationGracePeriodSeconds: '' # -- Pod Disruption Budget configuration. # Disabled by default. When enabled, defaults to maxUnavailable: 1.