diff --git a/Chart.yaml b/Chart.yaml index 918e1af..96e5769 100644 --- a/Chart.yaml +++ b/Chart.yaml @@ -2,5 +2,5 @@ apiVersion: v2 name: pgdog-control description: PgDog Control type: application -version: 0.2.12 -appVersion: "1a6d7fd0" +version: 0.2.13 +appVersion: "29a6513b" diff --git a/README.md b/README.md index f42bbe6..d3429a5 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,7 @@ When `control.rbac.create` is `true` (default), the chart renders: - A `ServiceAccount` for the control pod. If `control.aws.roleArn` is set, the ServiceAccount also carries the `eks.amazonaws.com/role-arn` annotation, which is what EKS IRSA looks for when handing the pod temporary AWS credentials. - A `ClusterRole` and `ClusterRoleBinding` granting **read-only** access cluster-wide. This is enough for the dashboard to list namespaces and read deployments, statefulsets, pods, services, configmaps, and secrets in any namespace. It cannot change anything. Pod logs are included so the deployment log view works. +- A namespace-scoped `Role` and `RoleBinding` in the release namespace granting access to `coordination.k8s.io` `Lease` objects for control-plane leader election. - For each namespace you list in `control.rbac.writeNamespaces`, a namespace-scoped `Role` and `RoleBinding` granting **write** access (create, update, patch, delete) on the resources PgDog actually manages: deployments, statefulsets, services, configmaps, secrets, service accounts, roles, role bindings, and pod disruption budgets. Namespaces not on the list stay strictly read-only. A typical setup grants write access only to the namespaces where you want PgDog clusters to live: diff --git a/templates/configmap.yaml b/templates/configmap.yaml index 92cfdbd..f1edb25 100644 --- a/templates/configmap.yaml +++ b/templates/configmap.yaml @@ -106,6 +106,26 @@ data: {{- end }} {{- end }} + {{- with $config.leader }} + + [leader] + {{- if hasKey . "enabled" }} + enabled = {{ .enabled }} + {{- end }} + {{- with .lease_name }} + lease_name = {{ . | quote }} + {{- end }} + {{- with .lease_duration_secs }} + lease_duration_secs = {{ . }} + {{- end }} + {{- with .renew_interval_secs }} + renew_interval_secs = {{ . }} + {{- end }} + {{- with .release_timeout_secs }} + release_timeout_secs = {{ . }} + {{- end }} + {{- end }} + {{- with $config.helm }} [helm] diff --git a/templates/deployment.yaml b/templates/deployment.yaml index 2b428fd..7719694 100644 --- a/templates/deployment.yaml +++ b/templates/deployment.yaml @@ -9,9 +9,22 @@ spec: selector: matchLabels: {{- include "pgdog-control.selectorLabels" . | nindent 6 }} + # Readiness is leader-aware so only the elected control pod receives + # Service traffic. New pods normally start as followers, so they do not + # become Ready while the old leader still holds the Lease. Allowing the + # whole old ReplicaSet to be unavailable lets Kubernetes terminate the + # old leader, release the Lease, and promote a new-version pod instead + # of waiting forever for a follower to become Ready. + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 0 + maxUnavailable: 100% template: metadata: annotations: + meta.helm.sh/release-name: {{ .Release.Name | quote }} + meta.helm.sh/release-namespace: {{ .Release.Namespace | quote }} # Roll the deployment when any rendered config / secret content # changes. Each annotation hashes the *template output*, not # values directly, so transformations applied inside the @@ -169,12 +182,12 @@ spec: failureThreshold: 3 readinessProbe: httpGet: - path: /healthz + path: /readyz port: {{ .Values.control.port }} initialDelaySeconds: 30 - periodSeconds: 10 - timeoutSeconds: 3 - failureThreshold: 3 + periodSeconds: 2 + timeoutSeconds: 1 + failureThreshold: 1 volumes: - name: config configMap: diff --git a/templates/rbac.yaml b/templates/rbac.yaml index 443f0b4..bf7112d 100644 --- a/templates/rbac.yaml +++ b/templates/rbac.yaml @@ -54,6 +54,35 @@ subjects: - kind: ServiceAccount name: {{ include "pgdog-control.control.serviceAccountName" . }} namespace: {{ .Release.Namespace }} +--- +# Namespace-scoped access for control-plane leader election. +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: {{ include "pgdog-control.control.clusterFullname" . }}-leader + namespace: {{ .Release.Namespace }} + labels: + {{- include "pgdog-control.labels" . | nindent 4 }} +rules: + - apiGroups: ["coordination.k8s.io"] + resources: ["leases"] + verbs: ["get", "list", "watch", "create", "update", "patch", "delete"] +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: {{ include "pgdog-control.control.clusterFullname" . }}-leader + namespace: {{ .Release.Namespace }} + labels: + {{- include "pgdog-control.labels" . | nindent 4 }} +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: Role + name: {{ include "pgdog-control.control.clusterFullname" . }}-leader +subjects: + - kind: ServiceAccount + name: {{ include "pgdog-control.control.serviceAccountName" . }} + namespace: {{ .Release.Namespace }} {{- range $ns := .Values.control.rbac.writeNamespaces }} --- # Namespace-scoped write access for workloads the control plane manages. diff --git a/test/values-full.yaml b/test/values-full.yaml index 4e271fa..1b79fb7 100644 --- a/test/values-full.yaml +++ b/test/values-full.yaml @@ -35,6 +35,12 @@ control: autoreload: "immediately" autoscaling: pool_size: true + leader: + enabled: true + lease_name: pgdog-control-leader + lease_duration_secs: 20 + renew_interval_secs: 7 + release_timeout_secs: 4 helm: chart: pgdog repo: pgdogdev diff --git a/values.yaml b/values.yaml index a24b032..11da4fe 100644 --- a/values.yaml +++ b/values.yaml @@ -107,6 +107,12 @@ control: # autoreload: off autoscaling: {} # pool_size: false + leader: {} + # enabled: true + # lease_name: "" # Empty means derive from Helm release. + # lease_duration_secs: 15 + # renew_interval_secs: 5 + # release_timeout_secs: 3 helm: {} # chart: pgdog # repo: pgdogdev