diff --git a/Chart.yaml b/Chart.yaml index 45fdf27..0fd93e7 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -1,4 +1,4 @@ apiVersion: v1 name: pgdog -version: v0.95 +version: v0.96 appVersion: "v0.1.63" diff --git a/templates/deployment.yaml b/templates/deployment.yaml index 1131ae3..0ae2b6b 100644 --- a/templates/deployment.yaml +++ b/templates/deployment.yaml @@ -44,6 +44,12 @@ spec: {{- end }} spec: serviceAccountName: {{ include "pgdog.serviceAccountName" . }} + {{- if hasKey .Values "automountServiceAccountToken" }} + automountServiceAccountToken: {{ .Values.automountServiceAccountToken }} + {{- end }} + {{- if .Values.shareProcessNamespace }} + shareProcessNamespace: true + {{- end }} {{- if .Values.priorityClassName }} priorityClassName: {{ .Values.priorityClassName }} {{- end }} @@ -149,7 +155,16 @@ spec: containerPort: {{ .Values.port }} - name: metrics containerPort: {{ .Values.openMetricsPort }} + {{- if .Values.lifecycle }} {{- if .Values.preStopSleepSeconds }} + {{- fail "lifecycle replaces the preStop that preStopSleepSeconds renders; set one of them" }} + {{- end }} + {{- if and .Values.lifecycle.preStop (not .Values.terminationGracePeriodSeconds) }} + {{- fail "lifecycle.preStop needs an explicit terminationGracePeriodSeconds that covers the preStop plus shutdownTimeout" }} + {{- end }} + lifecycle: + {{- toYaml .Values.lifecycle | nindent 12 }} + {{- else if .Values.preStopSleepSeconds }} lifecycle: preStop: sleep: @@ -233,6 +248,9 @@ spec: - name: prom mountPath: /etc/prom {{- end }} + {{- with .Values.extraContainers }} + {{- toYaml . | nindent 8 }} + {{- end }} volumes: - name: config {{- if .Values.configSecret.name }} diff --git a/templates/service.yaml b/templates/service.yaml index e8daf97..137f0a8 100644 --- a/templates/service.yaml +++ b/templates/service.yaml @@ -55,10 +55,12 @@ spec: protocol: TCP port: {{ .Values.port }} targetPort: {{ .Values.port }} + {{- if or (not (hasKey .Values.service "exposeMetrics")) .Values.service.exposeMetrics }} - name: openmetrics protocol: TCP port: {{ .Values.openMetricsPort }} targetPort: {{ .Values.openMetricsPort }} + {{- end }} {{- if .Values.healthcheckPort }} - name: healthcheck protocol: TCP diff --git a/templates/servicemonitor.yaml b/templates/servicemonitor.yaml index 8383cc3..1661221 100644 --- a/templates/servicemonitor.yaml +++ b/templates/servicemonitor.yaml @@ -1,4 +1,7 @@ {{- if .Values.serviceMonitor.enabled }} +{{- if and (hasKey .Values.service "exposeMetrics") (not .Values.service.exposeMetrics) (not .Values.statefulSet.enabled) }} +{{- fail "serviceMonitor scrapes the Service's openmetrics port; it needs service.exposeMetrics: true" }} +{{- end }} apiVersion: monitoring.coreos.com/v1 kind: ServiceMonitor metadata: diff --git a/test/test.sh b/test/test.sh index 5c9d3a4..9f6a546 100755 --- a/test/test.sh +++ b/test/test.sh @@ -99,5 +99,53 @@ else exit 1 fi +echo "" +echo "==> Validating pod extension points..." +pod_spec() { + helm template test-release "$CHART_DIR" -f "$@" \ + | yq -o=json -I=0 'select(.kind == "Deployment") | .spec.template.spec | {"automount": .automountServiceAccountToken, "shareProcessNamespace": .shareProcessNamespace, "containers": (.containers | map(.name)), "lifecycle": .containers[0].lifecycle}' +} +service_ports() { + helm template test-release "$CHART_DIR" -f "$@" \ + | yq -o=json -I=0 'select(.kind == "Service" and .metadata.name == "test-release-pgdog") | .spec.ports | map(.name)' +} +expect() { + if [ "$2" != "$3" ]; then + echo " FAIL: $1" + echo " Expected: $3" + echo " Got: $2" + exit 1 + fi +} +expect_fail() { + if helm template test-release "$CHART_DIR" "${@:2}" >/dev/null 2>&1; then + echo " FAIL: rendered although $1" + exit 1 + fi +} + +expect "extension points render as set" "$(pod_spec "$TEST_DIR/values-pod-extensions.yaml")" \ + '{"automount":false,"shareProcessNamespace":true,"containers":["pgdog","reloader"],"lifecycle":{"preStop":{"exec":{"command":["/bin/sh","-c","sleep 30"]}}}}' +expect "defaults render none of them" "$(pod_spec "$TEST_DIR/values-default.yaml")" \ + '{"automount":null,"shareProcessNamespace":null,"containers":["pgdog"],"lifecycle":null}' +expect "preStopSleepSeconds still renders its preStop" "$(pod_spec "$TEST_DIR/values-default.yaml" --set preStopSleepSeconds=25 | yq -o=json -I=0 .lifecycle)" \ + '{"preStop":{"sleep":{"seconds":25}}}' +expect "automountServiceAccountToken true renders" "$(pod_spec "$TEST_DIR/values-default.yaml" --set automountServiceAccountToken=true | yq .automount)" 'true' +expect "extraContainers follow the prometheus sidecar" "$(pod_spec "$TEST_DIR/values-pod-extensions.yaml" --set prometheusPort=9091 | yq -o=json -I=0 .containers)" \ + '["pgdog","prom","reloader"]' + +expect "exposeMetrics false drops the metrics port" "$(service_ports "$TEST_DIR/values-pod-extensions.yaml")" '["pgdog"]' +expect "metrics port is on the Service by default" "$(service_ports "$TEST_DIR/values-default.yaml")" '["pgdog","openmetrics"]' +# helm upgrade --reuse-values keeps the old release's values, which lack the key. +expect "missing exposeMetrics keeps the metrics port" "$(service_ports "$TEST_DIR/values-default.yaml" --set service.exposeMetrics=null)" '["pgdog","openmetrics"]' +helm template test-release "$CHART_DIR" -f "$TEST_DIR/values-monitoring.yaml" --set service.exposeMetrics=null >/dev/null +helm template test-release "$CHART_DIR" -f "$TEST_DIR/values-monitoring.yaml" -f "$TEST_DIR/values-statefulset.yaml" --set service.exposeMetrics=false >/dev/null + +expect_fail "serviceMonitor has no Service metrics port" -f "$TEST_DIR/values-monitoring.yaml" --set service.exposeMetrics=false +expect_fail "lifecycle and preStopSleepSeconds are both set" -f "$TEST_DIR/values-pod-extensions.yaml" --set preStopSleepSeconds=25 +helm template test-release "$CHART_DIR" --set-json 'lifecycle={"postStart":{"exec":{"command":["true"]}}}' >/dev/null +expect_fail "lifecycle.preStop has no explicit terminationGracePeriodSeconds" -f "$TEST_DIR/values-pod-extensions.yaml" --set terminationGracePeriodSeconds=null +echo " lifecycle, extraContainers, shareProcessNamespace, automountServiceAccountToken and service.exposeMetrics render as set, and only when set" + echo "" echo "==> All tests passed!" diff --git a/test/values-pod-extensions.yaml b/test/values-pod-extensions.yaml new file mode 100644 index 0000000..3be65fc --- /dev/null +++ b/test/values-pod-extensions.yaml @@ -0,0 +1,15 @@ +# Custom preStop, a sidecar sharing the process namespace, no API token, and +# metrics kept off the Service. +terminationGracePeriodSeconds: 120 +lifecycle: + preStop: + exec: + command: ["/bin/sh", "-c", "sleep 30"] +extraContainers: + - name: reloader + image: busybox + command: ["sh", "-c", "sleep infinity"] +shareProcessNamespace: true +automountServiceAccountToken: false +service: + exposeMetrics: false diff --git a/values.yaml b/values.yaml index 55f1c0d..7c15f6a 100644 --- a/values.yaml +++ b/values.yaml @@ -91,6 +91,21 @@ replicas: 2 # This is useful in combination with short client connection lifetimes to avoid errors during pod shutdown. # preStopSleepSeconds: 25 +# Optional: lifecycle hooks for the pgdog container, instead of preStopSleepSeconds +# (set one, not both). A preStop requires terminationGracePeriodSeconds covering +# it plus shutdownTimeout. The image's stop signal is SIGINT, PgDog's graceful +# shutdown, so a preStop only needs to wait, e.g. until clients have left: +# lifecycle: +# preStop: +# exec: +# command: +# - /bin/sh +# - -c +# - | +# sleep 30 +# until curl -fsS localhost:9090 | awk '$1 == "pgdog_clients" && $2 == 0 {f = 1} END {exit !f}'; do sleep 5; done +# terminationGracePeriodSeconds: 3600 + # Optional: Deployment rollout strategy (Deployment only) # strategy: {} @@ -140,6 +155,34 @@ env: [] # mountPath: /pki extraInitContainers: [] +# extraContainers adds sidecar containers next to pgdog (and the prometheus +# sidecar, if enabled), e.g. one that sends SIGHUP to reload a renewed TLS +# certificate. Combine with shareProcessNamespace to signal the pgdog process. +# Sidecars get the pod's service account token unless automountServiceAccountToken +# is false, and inherit nothing from securityContext: set their own. Run only +# trusted images pinned by digest. +# Example: +# extraContainers: +# - name: sidecar +# image: busybox:1.37@sha256: +# command: ['sh', '-c', 'sleep infinity'] +# securityContext: +# allowPrivilegeEscalation: false +# capabilities: +# drop: [ALL] +extraContainers: [] + +# shareProcessNamespace lets containers in the pod see and signal each other's +# processes. PID 1 is then the pause container, not pgdog: signal pgdog by name +# (pidof -s pgdog). A container running as pgdog's user can read its environment +# and files through /proc, users.toml passwords included. +# shareProcessNamespace: false + +# automountServiceAccountToken controls whether the pod gets a Kubernetes API +# token. PgDog does not call the Kubernetes API; set to false when nothing else +# in the pod does. Omitted from the pod spec unless set. +# automountServiceAccountToken: false + # extraVolumes allows adding custom volumes to the pod # These volumes can be referenced by extraVolumeMounts or extraInitContainers # Example: @@ -387,6 +430,10 @@ service: # Leave empty to use cluster defaults. # Note: Order matters - first family is primary. ipFamilies: [] + # exposeMetrics adds the openMetricsPort to the Service. Set to false for any + # internet-facing LoadBalancer and scrape the pods directly (e.g. a PodMonitor); + # serviceMonitor needs it true, except with statefulSet (headless Service). + exposeMetrics: true # aws configures AWS Load Balancer Controller annotations # When enabled, service type is automatically set to LoadBalancer aws: