diff --git a/.github/workflows/render.yml b/.github/workflows/render.yml index 10f9d0c..d38a381 100644 --- a/.github/workflows/render.yml +++ b/.github/workflows/render.yml @@ -63,7 +63,6 @@ jobs: -e COOKIE_SECRET=test-cookie-secret \ -e INCIDENT_IO_API_KEY=inc_test \ -e RAFT_TOKEN=test-raft-token \ - -e REDIS_URL=redis://redis.example.com:6379 \ -v /tmp/control.toml:/tmp/control.toml:ro \ "$CONTROL2_IMAGE" \ /usr/local/bin/control2 configcheck --config /tmp/control.toml diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..b1089a1 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,14 @@ +# Changelog + +## 0.4.0 + +### Breaking changes + +The redis service has been removed from the chart since control plane no longer needs it. + +### Upgrading from 0.3.x + +- Do `helm upgrade` to delete the `-redis` resources. +- Remove `redis.*` and `control.config.redis.*` from your values file. Helm ignores these keys and does not show an error. +- If you use an external Redis, the control plane stops using it after the upgrade. You can remove the `redis-url` key from your Secret. +- If you pin `image.tag`, use a control image that accepts a `control.toml` without a `[redis]` section (strictly newer than `v2026-11-03`). diff --git a/Chart.yaml b/Chart.yaml index 10316ee..c917891 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: pgdog-control description: PgDog Control type: application -version: 0.3.5 +version: 0.4.0 appVersion: "main-ent" diff --git a/README.md b/README.md index 75095d9..8edb52f 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The three somewhat complex steps are: ## Chart summary -This chart installs the PgDog control plane and, by default, a Redis instance. +This chart installs the PgDog control plane. The PgDog deployment contains the following components: @@ -50,44 +50,6 @@ The PgDog deployment contains the following components: | Service account, Cluster role, Cluster role bindings | Service account with RBAC to access select Kube APIs. See [RBAC](#rbac) for more details. | | NetworkPolicy | Optional; restricts ingress/egress traffic. See [NetworkPolicy](#networkpolicy) for more details. | -By default, the chart also deploys a single-replica Redis instance. The control plane uses Redis for storing metrics. Set `redis.enabled: false` and provide `redis.url` to use an external Redis instead. The chart-managed Redis has the following components: - -| Components | Description | -| ---------- | ---------------------------------------------------------------------------------------- | -| Deployment | Redis deployment with one replica. | -| Service | Redis service pointing to the deployment, with selector labels configured automatically. | - -```yaml -redis: - enabled: true - url: "" # defaults to redis://-redis..svc.cluster.local:6379 - image: - repository: redis - tag: "7-alpine" - pullPolicy: IfNotPresent - pullSecrets: [] -``` - -| Option | Description | -| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | -| `redis.enabled` | Deploy the chart-managed Redis resources (bool, default `true`). | -| `redis.pdb.enabled` | Create the Redis PodDisruptionBudget with `minAvailable: 1` (bool, default `true`). | -| `redis.url` | Redis connection string written to `[redis].url` in `control.toml`. When empty, defaults to the chart-managed Redis Service (string, default `""`). | -| `redis.image.repository` | Redis image repository (string, default `redis`). | -| `redis.image.tag` | Redis image tag (string, default `7-alpine`). | -| `redis.image.pullPolicy` | Redis image pull policy (string, default `IfNotPresent`). | -| `redis.image.pullSecrets` | Image pull secrets attached to the Redis pod (list, default `[]`). | - -The Redis PodDisruptionBudget blocks voluntary evictions of the single Redis replica, including during node drains and managed node upgrades. Set `redis.pdb.enabled: false` to allow these evictions while keeping Redis deployed: - -```yaml -redis: - pdb: - enabled: false -``` - -Redis will be unavailable while its pod is replaced. The pod retains its `cluster-autoscaler.kubernetes.io/safe-to-evict: "false"` annotation. - ### Raft Enable Raft with a top-level setting: @@ -363,11 +325,10 @@ If your cluster manages RBAC out-of-band (a platform team's controller, GitOps, ## NetworkPolicy -When `networkPolicy.enabled` is `true`, the chart renders a `NetworkPolicy` for the control pod and, when `redis.enabled` is true, one for Redis, restricting traffic to what the control plane actually needs: +When `networkPolicy.enabled` is `true`, the chart renders a `NetworkPolicy` for the control pod, restricting traffic to what the control plane actually needs: - Ingress on `control.port` from the `ingress-nginx` namespace only. -- Egress to Redis on port 6379, to `kube-system` for DNS, and to the public internet on 5432 (Postgres) and 443 (HTTPS, e.g. the AWS/CloudWatch/RDS APIs), excluding RFC1918 private ranges. When chart-managed Redis is enabled, the Redis rule is restricted to its pods; otherwise it permits egress to any destination on port 6379 so the external Redis can be reached. -- Chart-managed Redis accepts ingress only from the control pod and allows no egress. +- Egress to `kube-system` for DNS, and to the public internet on 5432 (Postgres) and 443 (HTTPS, e.g. the AWS/CloudWatch/RDS APIs), excluding RFC1918 private ranges. In clusters that deny pod-to-pod traffic by default, the built-in ingress-nginx rule alone often isn't enough — for example, PgDog pods calling the control plane's API need their own rule. Use `networkPolicy.extraIngress` to add any number of additional ingress rules to the control `NetworkPolicy`: @@ -389,7 +350,7 @@ networkPolicy: | Option | Description | | ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `networkPolicy.enabled` | Render the control and, when enabled, Redis `NetworkPolicy` resources (bool, default `false`). | +| `networkPolicy.enabled` | Render the control `NetworkPolicy` (bool, default `false`). | | `networkPolicy.extraIngress` | Additional ingress rules appended to the control `NetworkPolicy`, on top of the built-in ingress-nginx rule. Each entry follows the standard `NetworkPolicyIngressRule` schema (`from`/`ports`) and is passed through verbatim (list, default `[]`). | ## AWS access (EKS / IRSA) @@ -644,7 +605,6 @@ Secrets are injected as environment variables. Leave corresponding inline values | `COOKIE_SECRET` | Key used to sign session and CSRF cookies. | | `INCIDENT_IO_API_KEY` | incident.io API key for creating incidents. | | `RAFT_TOKEN` | Shared token authenticating Raft peers. | -| `REDIS_URL` | Redis connection URL, including credentials if required. | Create `secrets.yaml` separately from the Helm release, replacing the example values: @@ -663,7 +623,6 @@ stringData: cookie-secret: "replace-with-a-random-key-of-at-least-32-bytes" incident-io-api-key: "inc_your-api-key" raft-token: "replace-with-a-shared-random-token" - redis-url: "redis://user:password@redis.example.com:6379" ``` Apply it in the release namespace with `kubectl apply -n -f secrets.yaml`, then reference its keys in `values.yaml`: @@ -696,14 +655,9 @@ raft: secret: name: control-secrets tokenKey: raft-token -redis: - enabled: false - secret: - name: control-secrets - urlKey: redis-url ``` -Cookie and Raft references disable their generated Secrets. A Redis reference omits the default URL; set `redis.enabled: false` for external Redis. Raft requires `raft.enabled: true`. The last four variables require a control image containing commit `80895477` or later. +Cookie and Raft references disable their generated Secrets. Raft requires `raft.enabled: true`. The last three variables require a control image containing commit `80895477` or later. After rotating a Secret, run `kubectl rollout restart deployment/-control` (use `statefulset` when Raft is enabled). @@ -838,31 +792,6 @@ control: | `bot_token` | Slack bot token with `chat:write` permission (string, optional). | | `channel` | Slack channel ID or name for status updates (string, optional). | -### Redis persistence - -`control.config.redis` controls how often the in-memory store is snapshotted to Redis between process restarts. The chart provisions an in-cluster Redis (`-redis`) by default and writes its connection string to `[redis].url` in `control.toml`. - -```yaml -control: - config: - redis: - save_interval_secs: 60 -``` - -| Option | Description | -| -------------------- | ------------------------------------------------------------------------------- | -| `save_interval_secs` | How often the background task snapshots the store to Redis (int, default `60`). | - -To use an external Redis, disable all chart-managed Redis resources and set its URL: - -```yaml -redis: - enabled: false - url: redis://my-redis.cache:6379 -``` - -For backwards compatibility, `control.config.redis.url` is still accepted, but `redis.url` is preferred. - ## Examples ```sh diff --git a/templates/_helpers.tpl b/templates/_helpers.tpl index ae10bac..8d3768e 100644 --- a/templates/_helpers.tpl +++ b/templates/_helpers.tpl @@ -47,10 +47,6 @@ the same cluster don't collide. {{- printf "%s-%s-control" .Release.Name .Release.Namespace | trunc 63 | trimSuffix "-" }} {{- end }} -{{- define "pgdog-control.redis.fullname" -}} -{{- printf "%s-redis" .Release.Name | trunc 63 | trimSuffix "-" }} -{{- end }} - {{/* Stable DNS service and token Secret for the Raft members. */}} {{- define "pgdog-control.raft.fullname" -}} {{- printf "%s-raft" .Release.Name | trunc 63 | trimSuffix "-" -}} @@ -80,16 +76,6 @@ render context so the Secret, ConfigMap, and pod checksum all agree. {{- end -}} {{- end }} -{{/* -Redis URL used by the control plane. redis.url is the public chart setting; -control.config.redis.url remains supported for backwards compatibility. -*/}} -{{- define "pgdog-control.redis.url" -}} -{{- $config := .Values.control.config | default dict -}} -{{- $redisConfig := $config.redis | default dict -}} -{{- .Values.redis.url | default $redisConfig.url | default (printf "redis://%s.%s.svc.cluster.local:6379" (include "pgdog-control.redis.fullname" .) .Release.Namespace) -}} -{{- end }} - {{/* ServiceAccount name for the control component. Falls back to the control fullname when not explicitly set in values. @@ -131,19 +117,3 @@ app.kubernetes.io/instance: {{ .Release.Name }} app.kubernetes.io/component: control {{- end }} -{{/* -Labels for the redis component. -*/}} -{{- define "pgdog-control.redis.labels" -}} -{{ include "pgdog-control.commonLabels" . }} -{{ include "pgdog-control.redis.selectorLabels" . }} -{{- end }} - -{{/* -Selector labels for the redis component. -*/}} -{{- define "pgdog-control.redis.selectorLabels" -}} -app.kubernetes.io/name: {{ include "pgdog-control.name" . }} -app.kubernetes.io/instance: {{ .Release.Name }} -app.kubernetes.io/component: redis -{{- end }} diff --git a/templates/configmap.yaml b/templates/configmap.yaml index 96b80f5..68c4e36 100644 --- a/templates/configmap.yaml +++ b/templates/configmap.yaml @@ -247,13 +247,3 @@ data: channel = {{ .channel | quote }} {{- end }} {{- end }} - - {{- $redis := $config.redis | default dict }} - - [redis] - {{- if or .Values.redis.url $redis.url (not ((.Values.redis.secret | default dict).urlKey)) }} - url = {{ include "pgdog-control.redis.url" . | quote }} - {{- end }} - {{- with $redis.save_interval_secs }} - save_interval_secs = {{ . }} - {{- end }} diff --git a/templates/deployment.yaml b/templates/deployment.yaml index 5883b5c..600920f 100644 --- a/templates/deployment.yaml +++ b/templates/deployment.yaml @@ -189,15 +189,6 @@ spec: {{- end }} {{- end }} {{- end }} - {{- with .Values.redis.secret }} - {{- if .urlKey }} - - name: REDIS_URL - valueFrom: - secretKeyRef: - name: {{ required "redis.secret.name is required when urlKey is set" .name | quote }} - key: {{ .urlKey | quote }} - {{- end }} - {{- end }} {{- with $auth.github }} {{- with .secret }} # OAuth credentials sourced from an existing Secret. The control diff --git a/templates/networkpolicy.yaml b/templates/networkpolicy.yaml index ffcf3f7..9530e07 100644 --- a/templates/networkpolicy.yaml +++ b/templates/networkpolicy.yaml @@ -42,19 +42,6 @@ spec: - protocol: TCP port: {{ .Values.control.port }} {{- end }} - {{- if .Values.redis.enabled }} - - to: - - podSelector: - matchLabels: - {{- include "pgdog-control.redis.selectorLabels" . | nindent 10 }} - ports: - - protocol: TCP - port: 6379 - {{- else }} - - ports: - - protocol: TCP - port: 6379 - {{- end }} - to: - ipBlock: cidr: 0.0.0.0/0 @@ -84,29 +71,4 @@ spec: port: 53 - protocol: TCP port: 53 -{{- if .Values.redis.enabled }} ---- -apiVersion: networking.k8s.io/v1 -kind: NetworkPolicy -metadata: - name: {{ include "pgdog-control.redis.fullname" . }} - labels: - {{- include "pgdog-control.redis.labels" . | nindent 4 }} -spec: - podSelector: - matchLabels: - {{- include "pgdog-control.redis.selectorLabels" . | nindent 6 }} - policyTypes: - - Ingress - - Egress - ingress: - - from: - - podSelector: - matchLabels: - {{- include "pgdog-control.selectorLabels" . | nindent 10 }} - ports: - - protocol: TCP - port: 6379 - egress: [] -{{- end }} {{- end }} diff --git a/templates/redis-deployment.yaml b/templates/redis-deployment.yaml deleted file mode 100644 index 4abfc61..0000000 --- a/templates/redis-deployment.yaml +++ /dev/null @@ -1,55 +0,0 @@ -{{- if .Values.redis.enabled }} -apiVersion: apps/v1 -kind: Deployment -metadata: - name: {{ include "pgdog-control.redis.fullname" . }} - labels: - {{- include "pgdog-control.redis.labels" . | nindent 4 }} -spec: - replicas: 1 - selector: - matchLabels: - {{- include "pgdog-control.redis.selectorLabels" . | nindent 6 }} - template: - metadata: - labels: - {{- include "pgdog-control.redis.selectorLabels" . | nindent 8 }} - annotations: - cluster-autoscaler.kubernetes.io/safe-to-evict: "false" - spec: - automountServiceAccountToken: false - {{- with .Values.nodeSelector }} - nodeSelector: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.tolerations }} - tolerations: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.redis.image.pullSecrets }} - imagePullSecrets: - {{- toYaml . | nindent 8 }} - {{- end }} - {{- with .Values.redis.podSecurityContext }} - securityContext: - {{- toYaml . | nindent 8 }} - {{- end }} - containers: - - name: redis - image: "{{ .Values.redis.image.repository }}:{{ .Values.redis.image.tag }}" - imagePullPolicy: {{ .Values.redis.image.pullPolicy }} - {{- with .Values.redis.containerSecurityContext }} - securityContext: - {{- toYaml . | nindent 10 }} - {{- end }} - ports: - - containerPort: 6379 - name: redis - resources: - requests: - memory: {{ .Values.redis.resources.requests.memory | quote }} - cpu: {{ .Values.redis.resources.requests.cpu | quote }} - limits: - memory: {{ .Values.redis.resources.limits.memory | quote }} - cpu: {{ .Values.redis.resources.limits.cpu | quote }} -{{- end }} diff --git a/templates/redis-pdb.yaml b/templates/redis-pdb.yaml deleted file mode 100644 index 937ea76..0000000 --- a/templates/redis-pdb.yaml +++ /dev/null @@ -1,13 +0,0 @@ -{{- if and .Values.redis.enabled .Values.redis.pdb.enabled }} -apiVersion: policy/v1 -kind: PodDisruptionBudget -metadata: - name: {{ include "pgdog-control.redis.fullname" . }} - labels: - {{- include "pgdog-control.redis.labels" . | nindent 4 }} -spec: - minAvailable: 1 - selector: - matchLabels: - {{- include "pgdog-control.redis.selectorLabels" . | nindent 6 }} -{{- end }} diff --git a/templates/redis-service.yaml b/templates/redis-service.yaml deleted file mode 100644 index 36bec57..0000000 --- a/templates/redis-service.yaml +++ /dev/null @@ -1,17 +0,0 @@ -{{- if .Values.redis.enabled }} -apiVersion: v1 -kind: Service -metadata: - name: {{ include "pgdog-control.redis.fullname" . }} - labels: - {{- include "pgdog-control.redis.labels" . | nindent 4 }} -spec: - type: ClusterIP - ports: - - port: 6379 - targetPort: 6379 - protocol: TCP - name: redis - selector: - {{- include "pgdog-control.redis.selectorLabels" . | nindent 4 }} -{{- end }} diff --git a/test/generate-config.sh b/test/generate-config.sh index ca43a44..d9b88fa 100755 --- a/test/generate-config.sh +++ b/test/generate-config.sh @@ -12,14 +12,16 @@ fi VALUES_FILE="$1" -output=$(helm template test-release "$CHART_DIR" -f "$VALUES_FILE" \ - | yq -r 'select(.kind == "ConfigMap" and has("data") and .data["control.toml"]) | .data["control.toml"]') +rendered=$(helm template test-release "$CHART_DIR" -f "$VALUES_FILE") +configmap='select(.kind == "ConfigMap" and has("data") and (.data | has("control.toml")))' -if [ -z "$output" ]; then +if [ -z "$(yq -r "$configmap | .metadata.name" <<< "$rendered")" ]; then echo "FAIL: control.toml not found in rendered ConfigMap" exit 1 fi +output=$(yq -r "$configmap | .data[\"control.toml\"]" <<< "$rendered") + if [ -z "$2" ]; then echo "$output" else diff --git a/test/test.sh b/test/test.sh index d2ab612..7e8d5af 100755 --- a/test/test.sh +++ b/test/test.sh @@ -21,25 +21,5 @@ grep -q '^ query_plans_limit = 75$' <<< "$full_render" grep -q '^ slow_queries_threshold = 2500$' <<< "$full_render" grep -q '^ slow_queries = true$' <<< "$full_render" -echo "" -echo "==> Verifying external Redis rendering..." -external_render=$(helm template test-release "$CHART_DIR" -f "$TEST_DIR/values-redis-external.yaml") -if grep -q 'app.kubernetes.io/component: redis' <<< "$external_render"; then - echo "chart-managed Redis resources rendered while redis.enabled=false" >&2 - exit 1 -fi -if grep -q -- '- name: REDIS_URL' <<< "$external_render"; then - echo "REDIS_URL environment variable rendered without a Secret reference" >&2 - exit 1 -fi -grep -A1 '^ \[redis\]$' <<< "$external_render" | grep -q 'url = "redis://external-redis.example.com:6379"' - -echo "" -echo "==> Verifying configurable Redis image..." -image_render=$(helm template test-release "$CHART_DIR" -f "$TEST_DIR/values-redis-image.yaml") -grep -q 'image: "registry.example.com/platform/redis:8-alpine"' <<< "$image_render" -grep -q 'imagePullPolicy: Always' <<< "$image_render" -grep -q 'name: registry-credentials' <<< "$image_render" - echo "" echo "==> All chart tests passed!" diff --git a/test/values-full.yaml b/test/values-full.yaml index a1135fc..e0be824 100644 --- a/test/values-full.yaml +++ b/test/values-full.yaml @@ -76,9 +76,6 @@ control: slack: bot_token: slack-token channel: "#alerts" - redis: - url: redis://redis.example.com:6379 - save_interval_secs: 30 nodeSelector: node-type: database diff --git a/test/values-redis-external.yaml b/test/values-redis-external.yaml deleted file mode 100644 index e38c39f..0000000 --- a/test/values-redis-external.yaml +++ /dev/null @@ -1,9 +0,0 @@ -ingress: - enabled: false - -networkPolicy: - enabled: true - -redis: - enabled: false - url: redis://external-redis.example.com:6379 diff --git a/test/values-redis-image.yaml b/test/values-redis-image.yaml deleted file mode 100644 index 89d8cbe..0000000 --- a/test/values-redis-image.yaml +++ /dev/null @@ -1,10 +0,0 @@ -ingress: - enabled: false - -redis: - image: - repository: registry.example.com/platform/redis - tag: "8-alpine" - pullPolicy: Always - pullSecrets: - - name: registry-credentials diff --git a/test/values-secrets.yaml b/test/values-secrets.yaml index 85a2aa9..b104747 100644 --- a/test/values-secrets.yaml +++ b/test/values-secrets.yaml @@ -25,8 +25,3 @@ raft: secret: name: control-secrets tokenKey: raft-token -redis: - enabled: false - secret: - name: control-secrets - urlKey: redis-url diff --git a/values.yaml b/values.yaml index 498dee4..d1103ae 100644 --- a/values.yaml +++ b/values.yaml @@ -197,54 +197,6 @@ control: slack: {} # bot_token: "" # optional; defaults to SLACK_BOT_TOKEN env when absent # channel: "" # optional; defaults to SLACK_CHANNEL env when absent - redis: {} - # url: "" # deprecated; use redis.url instead - # save_interval_secs: 60 - -redis: - # Deploy the chart-managed Redis instance. Disable this when using an - # external Redis and set redis.url to its connection string. - enabled: true - pdb: - # Keep at least one Redis pod available. With a single replica, this - # blocks node drains; disable to allow Redis to be evicted during maintenance. - enabled: true - # Connection string written to [redis].url in control.toml. When empty, - # it points to the chart-managed Redis service. - url: "" - # Source REDIS_URL from an existing Secret instead; leave redis.url and - # control.config.redis.url unset. Disable redis.enabled for external Redis. - secret: {} - # name: "" - # urlKey: "" - image: - repository: redis - tag: "7-alpine" - pullPolicy: IfNotPresent - pullSecrets: [] - resources: - requests: - memory: "128Mi" - cpu: "100m" - limits: - memory: "256Mi" - cpu: "500m" - # Pod-level securityContext for the Redis deployment. - # Defaults are PSS Restricted compliant. - podSecurityContext: - runAsNonRoot: true - runAsUser: 999 - runAsGroup: 999 - fsGroup: 999 - seccompProfile: - type: RuntimeDefault - # Container-level securityContext for the Redis container. - # Defaults are PSS Restricted compliant. - containerSecurityContext: - allowPrivilegeEscalation: false - readOnlyRootFilesystem: true - capabilities: - drop: ["ALL"] networkPolicy: enabled: false