diff --git a/.github/workflows/cicd.yaml b/.github/workflows/cicd.yaml index 85b4ff52..4a0f5ce8 100644 --- a/.github/workflows/cicd.yaml +++ b/.github/workflows/cicd.yaml @@ -66,3 +66,4 @@ jobs: with: helm-version: latest charts: helm/ + unittest-version: v0.6.3 diff --git a/docs/user-guide/deployment.md b/docs/user-guide/deployment.md index 1416cc55..c850ddae 100644 --- a/docs/user-guide/deployment.md +++ b/docs/user-guide/deployment.md @@ -83,19 +83,48 @@ helm install stac-auth-proxy oci://ghcr.io/developmentseed/stac-auth-proxy/chart ### Configuration -| Parameter | Description | Required | Default | -| ------------------------ | --------------------------------------------- | -------- | ------- | -| `env.UPSTREAM_URL` | URL of the STAC API to proxy | Yes | - | -| `env.OIDC_DISCOVERY_URL` | OpenID Connect discovery document URL | Yes | - | -| `env` | Environment variables passed to the container | No | `{}` | -| `ingress.enabled` | Enable ingress | No | `true` | -| `ingress.className` | Ingress class name | No | `nginx` | -| `ingress.host` | Hostname for the ingress | No | `""` | -| `ingress.tls.enabled` | Enable TLS for ingress | No | `true` | -| `replicaCount` | Number of replicas | No | `1` | +| Parameter | Description | Required | Default | +| -------------------------------------------- | ------------------------------------------------ | -------- | ------- | +| `env.UPSTREAM_URL` | URL of the STAC API to proxy | Yes | - | +| `env.OIDC_DISCOVERY_URL` | OpenID Connect discovery document URL | Yes | - | +| `env` | Environment variables passed to the container | No | `{}` | +| `ingress.enabled` | Enable ingress | No | `true` | +| `ingress.className` | Ingress class name | No | `nginx` | +| `ingress.host` | Hostname for the ingress | No | `""` | +| `ingress.tls.enabled` | Enable TLS for ingress | No | `true` | +| `replicaCount` | Number of replicas (ignored when HPA is enabled) | No | `1` | +| `autoscaling.enabled` | Enable Horizontal Pod Autoscaler | No | `false` | +| `autoscaling.minReplicas` | Minimum replicas managed by HPA | No | `1` | +| `autoscaling.maxReplicas` | Maximum replicas managed by HPA | No | `10` | +| `autoscaling.targetCPUUtilizationPercentage` | Target average CPU utilization (%) | No | `80` | For a complete list of values, see the [values.yaml](https://github.com/developmentseed/stac-auth-proxy/blob/main/helm/values.yaml) file. +### Autoscaling + +When autoscaling is enabled, the HPA manages replica count and `replicaCount` is not applied to the Deployment (so `helm upgrade` does not reset scaling). Chart defaults use `minReplicas: 1`; use at least `2` for high availability. Scaling uses CPU utilization only; I/O-bound workloads may need a lower target or custom metrics. + +Enable Horizontal Pod Autoscaler to handle variable load: + +```yaml +autoscaling: + enabled: true + minReplicas: 2 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + +# Override defaults if needed; CPU requests are required for utilization-based HPA +resources: + requests: + cpu: 500m + memory: 512Mi + limits: + cpu: 2000m + memory: 1Gi +``` + +You also need to make sure Kubernetes Metrics Server is installed. + ### Management ```bash diff --git a/helm/README.md b/helm/README.md index f8260c9a..84f44af4 100644 --- a/helm/README.md +++ b/helm/README.md @@ -1,6 +1,6 @@ # STAC Auth Proxy Helm Chart -For documentation, see [Kubernetes Deployment](https://developmentseed.org/stac-auth-proxy/user-guide/kubernetes). +For documentation, see [Kubernetes Deployment](https://developmentseed.org/stac-auth-proxy/user-guide/deployment/). ## Local Installation diff --git a/helm/templates/_helpers.tpl b/helm/templates/_helpers.tpl index f0232261..d5137ef9 100644 --- a/helm/templates/_helpers.tpl +++ b/helm/templates/_helpers.tpl @@ -78,3 +78,14 @@ Validate terminationGracePeriodSeconds > preStopSleepSeconds {{- fail "terminationGracePeriodSeconds must be greater than preStopSleepSeconds" -}} {{- end -}} {{- end -}} + +{{/* +Validate autoscaling replica bounds when HPA is enabled +*/}} +{{- define "stac-auth-proxy.validateAutoscaling" -}} +{{- if .Values.autoscaling.enabled -}} +{{- if lt (int .Values.autoscaling.maxReplicas) (int .Values.autoscaling.minReplicas) -}} +{{- fail "autoscaling.maxReplicas must be greater than or equal to autoscaling.minReplicas" -}} +{{- end -}} +{{- end -}} +{{- end -}} diff --git a/helm/templates/deployment.yaml b/helm/templates/deployment.yaml index bd19c144..76cb3801 100644 --- a/helm/templates/deployment.yaml +++ b/helm/templates/deployment.yaml @@ -1,4 +1,5 @@ {{- include "stac-auth-proxy.validateTerminationGracePeriod" . -}} +{{- include "stac-auth-proxy.validateAutoscaling" . -}} apiVersion: apps/v1 kind: Deployment metadata: @@ -6,7 +7,9 @@ metadata: labels: {{- include "stac-auth-proxy.labels" . | nindent 4 }} spec: + {{- if not .Values.autoscaling.enabled }} replicas: {{ .Values.replicaCount }} + {{- end }} selector: matchLabels: {{- include "stac-auth-proxy.selectorLabels" . | nindent 6 }} diff --git a/helm/templates/hpa.yaml b/helm/templates/hpa.yaml new file mode 100644 index 00000000..05056a56 --- /dev/null +++ b/helm/templates/hpa.yaml @@ -0,0 +1,26 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "stac-auth-proxy.fullname" . }} + labels: + {{- include "stac-auth-proxy.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "stac-auth-proxy.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- with .Values.autoscaling.behavior }} + behavior: + {{- toYaml . | nindent 4 }} + {{- end }} +{{- end }} diff --git a/helm/tests/deployment_test.yaml b/helm/tests/deployment_test.yaml index f082b65a..8f0c7d02 100644 --- a/helm/tests/deployment_test.yaml +++ b/helm/tests/deployment_test.yaml @@ -23,6 +23,27 @@ tests: path: spec.replicas value: 3 + - it: should omit replica count when autoscaling is enabled + set: + autoscaling.enabled: true + replicaCount: 5 + env.UPSTREAM_URL: "https://example.com" + env.OIDC_DISCOVERY_URL: "https://example.com/.well-known/openid-configuration" + asserts: + - isNull: + path: spec.replicas + + - it: should fail when maxReplicas is less than minReplicas + set: + autoscaling.enabled: true + autoscaling.minReplicas: 5 + autoscaling.maxReplicas: 2 + env.UPSTREAM_URL: "https://example.com" + env.OIDC_DISCOVERY_URL: "https://example.com/.well-known/openid-configuration" + asserts: + - failedTemplate: + errorMessage: autoscaling.maxReplicas must be greater than or equal to autoscaling.minReplicas + - it: should set required environment variables set: env.UPSTREAM_URL: "https://stac-api.example.com" diff --git a/helm/tests/hpa_test.yaml b/helm/tests/hpa_test.yaml new file mode 100644 index 00000000..175adddd --- /dev/null +++ b/helm/tests/hpa_test.yaml @@ -0,0 +1,64 @@ +suite: test horizontal pod autoscaler +templates: + - hpa.yaml +tests: + - it: should not create HPA when disabled + set: + autoscaling.enabled: false + env.UPSTREAM_URL: "https://example.com" + env.OIDC_DISCOVERY_URL: "https://example.com/.well-known/openid-configuration" + asserts: + - hasDocuments: + count: 0 + + - it: should create HPA when enabled + set: + autoscaling.enabled: true + env.UPSTREAM_URL: "https://example.com" + env.OIDC_DISCOVERY_URL: "https://example.com/.well-known/openid-configuration" + asserts: + - isKind: + of: HorizontalPodAutoscaler + - isAPIVersion: + of: autoscaling/v2 + + - it: should configure replicas and CPU target + set: + autoscaling.enabled: true + autoscaling.minReplicas: 2 + autoscaling.maxReplicas: 15 + autoscaling.targetCPUUtilizationPercentage: 75 + env.UPSTREAM_URL: "https://example.com" + env.OIDC_DISCOVERY_URL: "https://example.com/.well-known/openid-configuration" + asserts: + - equal: + path: spec.minReplicas + value: 2 + - equal: + path: spec.maxReplicas + value: 15 + - equal: + path: spec.metrics[0].resource.target.averageUtilization + value: 75 + + - it: should target the deployment and configure scale behavior + set: + autoscaling.enabled: true + env.UPSTREAM_URL: "https://example.com" + env.OIDC_DISCOVERY_URL: "https://example.com/.well-known/openid-configuration" + asserts: + - equal: + path: spec.scaleTargetRef.apiVersion + value: apps/v1 + - equal: + path: spec.scaleTargetRef.kind + value: Deployment + - matchRegex: + path: spec.scaleTargetRef.name + pattern: ^RELEASE-NAME-stac-auth-proxy$ + - equal: + path: spec.behavior.scaleDown.stabilizationWindowSeconds + value: 300 + - equal: + path: spec.behavior.scaleUp.selectPolicy + value: Max diff --git a/helm/values.schema.json b/helm/values.schema.json index 6aca133b..66c6f696 100644 --- a/helm/values.schema.json +++ b/helm/values.schema.json @@ -5,7 +5,7 @@ "replicaCount": { "type": "integer", "minimum": 1, - "description": "Number of replicas for the deployment" + "description": "Number of replicas for the deployment (ignored when autoscaling.enabled is true)" }, "image": { "type": "object", @@ -317,6 +317,49 @@ "additionalProperties": true, "description": "Pod affinity rules" }, + "autoscaling": { + "type": "object", + "properties": { + "enabled": { + "type": "boolean", + "description": "Enable Horizontal Pod Autoscaler", + "default": false + }, + "minReplicas": { + "type": "integer", + "minimum": 1, + "description": "Minimum number of replicas", + "default": 1 + }, + "maxReplicas": { + "type": "integer", + "minimum": 1, + "description": "Maximum number of replicas (must be >= minReplicas when enabled)", + "default": 10 + }, + "targetCPUUtilizationPercentage": { + "type": "integer", + "minimum": 1, + "maximum": 100, + "description": "Target CPU utilization percentage for autoscaling", + "default": 80 + }, + "behavior": { + "type": "object", + "description": "Scaling behavior configuration", + "properties": { + "scaleDown": { + "type": "object", + "additionalProperties": true + }, + "scaleUp": { + "type": "object", + "additionalProperties": true + } + } + } + } + }, "initContainers": { "type": "array", "items": { diff --git a/helm/values.yaml b/helm/values.yaml index 89b38a4b..68f75a15 100644 --- a/helm/values.yaml +++ b/helm/values.yaml @@ -1,5 +1,6 @@ # Default values for stac-auth-proxy +# NOTE: When autoscaling.enabled is true, replicaCount is ignored and the HPA manages replicas. replicaCount: 1 image: @@ -73,6 +74,31 @@ readinessProbe: periodSeconds: 5 failureThreshold: 3 +# Horizontal Pod Autoscaler configuration +# NOTE: Requires Kubernetes Metrics Server to be installed in the cluster. +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 10 + targetCPUUtilizationPercentage: 80 + behavior: + scaleDown: + stabilizationWindowSeconds: 300 + policies: + - type: Percent + value: 50 + periodSeconds: 60 + scaleUp: + stabilizationWindowSeconds: 0 + policies: + - type: Percent + value: 100 + periodSeconds: 30 + - type: Pods + value: 2 + periodSeconds: 30 + selectPolicy: Max + nodeSelector: {} tolerations: [] affinity: {}