extras/postgres-hpa/kubernetes-manifests/pgpool-operator.yaml (183 lines of code) (raw):

# Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. --- apiVersion: v1 kind: ServiceAccount metadata: namespace: "default" name: pgpool-operator --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRole metadata: name: pgpool-operator-cluster-role rules: - apiGroups: [""] resources: - namespaces verbs: ["*"] - apiGroups: [apiextensions.k8s.io] resources: [customresourcedefinitions] verbs: [watch, list] --- apiVersion: rbac.authorization.k8s.io/v1 kind: Role metadata: namespace: "default" name: pgpool-operator-role rules: - apiGroups: ["apps"] resources: ["deployments", "statefulsets"] verbs: ["*"] --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding metadata: name: pgpool-operator-cluster-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: ClusterRole name: pgpool-operator-cluster-role subjects: - kind: ServiceAccount name: pgpool-operator namespace: "default" --- apiVersion: rbac.authorization.k8s.io/v1 kind: RoleBinding metadata: namespace: "default" name: pgpool-operator-rolebinding roleRef: apiGroup: rbac.authorization.k8s.io kind: Role name: pgpool-operator-role subjects: - kind: ServiceAccount namespace: "default" name: pgpool-operator --- apiVersion: v1 kind: ConfigMap metadata: name: pgpool-operator-script data: pgpool.py: | # Copyright 2022 Google LLC # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import asyncio import datetime import logging import kopf from kubernetes import client from kubernetes.client.rest import ApiException LOCK: asyncio.Lock @kopf.on.startup() async def startup(**_): """ uses the running asyncio loop by default """ global LOCK LOCK = asyncio.Lock() @kopf.on.startup() def configure(settings: kopf.OperatorSettings, **_): settings.posting.level = logging.WARNING settings.watching.connect_timeout = 1 * 60 settings.watching.server_timeout = 10 * 60 @kopf.on.probe(id='now') def get_current_timestamp(**kwargs): return datetime.datetime.utcnow().isoformat() @kopf.on.login() def login(**kwargs): global api conn = kopf.login_via_client(**kwargs) api = client.AppsV1Api() return conn def replicas_changed(old, new, **_): new_replicas = new.get('spec', {}).get('replicas', 0) if new else 0 old_replicas = old.get('spec', {}).get('replicas', 0) if old else 0 return new_replicas != old_replicas @kopf.on.update(kind="StatefulSet", when=replicas_changed, labels={ "app.kubernetes.io/component": "postgresql", "app.kubernetes.io/instance": "accounts-db", }) def reconcile_backend_nodes(logger, namespace, new, **_): replicas = new.get('spec', {}).get('replicas', 0) if new else 0 hosts = [ f"{i}:accounts-db-postgresql-{i}.accounts-db-postgresql-headless:5432" \ for i in range(replicas) ] def propagate_hostenv(envvar, hosts): if envvar.name == "PGPOOL_BACKEND_NODES": return { "name": "PGPOOL_BACKEND_NODES", "value": ",".join(hosts), } return envvar try: pgpool = api.read_namespaced_deployment(name="accounts-db-pgpool", namespace=namespace) for container in pgpool.spec.template.spec.containers: container.env = [propagate_hostenv(envvar, hosts) for envvar in container.env] api.patch_namespaced_deployment( name="accounts-db-pgpool", namespace=namespace, body=pgpool ) logger.info("PGPool deployment updated") except ApiException as e: raise kopf.TemporaryError("Error when calling AppsV1Api: %s\n" % e, delay=60) --- apiVersion: apps/v1 kind: Deployment metadata: name: pgpool-operator spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: pgpool-operator template: metadata: labels: app: pgpool-operator spec: serviceAccountName: pgpool-operator containers: - name: operator image: python:3.12-bullseye@sha256:deda19eaa3781e480eeb65f0463cf8d84874c4aa8c8997cdcb4d12c4ff2db957 env: - name: NAMESPACE valueFrom: fieldRef: fieldPath: metadata.namespace command: ["/bin/sh", "-c", "pip3 install kopf kubernetes && kopf run /app/pgpool.py --standalone --namespace=$(NAMESPACE) --log-format=plain --liveness=http://0.0.0.0:8080/healthz"] volumeMounts: - name: pgpool-operator-script mountPath: /app livenessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 60 periodSeconds: 30 timeoutSeconds: 30 readinessProbe: httpGet: path: /healthz port: 8080 initialDelaySeconds: 60 periodSeconds: 30 timeoutSeconds: 30 resources: requests: cpu: 250m memory: 512Mi ephemeral-storage: 1Gi limits: cpu: 250m memory: 512Mi ephemeral-storage: 1Gi volumes: - name: pgpool-operator-script configMap: name: pgpool-operator-script