From ffffe9d3c84b29a3b35b2d625a252b9fd10ecc79 Mon Sep 17 00:00:00 2001 From: "Dustin C. Hatch" Date: Sun, 30 Apr 2023 22:08:02 -0500 Subject: [PATCH] postgresql: Deploy Postgres Operator While I was preparing to deploy PostgreSQL for Firefly III, I was thinking it would be a neat idea to write an operator that uses custom resources to manage PostgreSQL roles and databases. Then I though, surely something like that must exist already. As it turns out, the [Postgres Operator][0] does exactly that, and a whole lot more. The *Postgres Operator* handles deploying PostgreSQL server instances, including primary/standby replication with load balancers. It uses custom resources to manage the databases and users (roles) in an instance, and stores role passwords in Secret resources. It supports backing up instances using `pg_basebackup` and WAL archives (i.e. physical backups) via [WAL-E][1]/[WAL-G][2]. While various backup storage targets are supported, *Postgres Operator* really only works well with the cloud storage services like S3, Azure, and Google Cloud Platform. Fortunately, S3-compatible on-premises solutions like MinIO are just fine. I think for my use cases, a single PostgreSQL cluster with multiple databases will be sufficient. I know *Firefly III* will need a PostgreSQL database, and I will likely want to migrate *Paperless-ngx* to PostgreSQL eventually too. Having a single instance will save on memory resources, at the cost of per-application point-in-time recovery. For now, just one server in the cluster is probably sufficient, but luckily adding standby servers appears to be really easy should the need arise. [0]: https://postgres-operator.readthedocs.io/en/latest/ [1]: https://github.com/wal-e/wal-e [2]: https://github.com/wal-g/wal-g --- postgresql/.gitignore | 1 + postgresql/api-service.yaml | 12 + postgresql/default-cluster.yaml | 18 + postgresql/kustomization.yaml | 33 + postgresql/namespace.yaml | 4 + postgresql/operator-service-account-rbac.yaml | 290 ++++++++ postgresql/operatorconfiguration.crd.yaml | 677 ++++++++++++++++++ postgresql/pod.env | 2 + postgresql/postgres-operator.yaml | 45 ++ .../postgresql-operator-configuration.yaml | 213 ++++++ postgresql/postgresteam.crd.yaml | 68 ++ postgresql/ui/kustomization.yaml | 38 + 12 files changed, 1401 insertions(+) create mode 100644 postgresql/.gitignore create mode 100644 postgresql/api-service.yaml create mode 100644 postgresql/default-cluster.yaml create mode 100644 postgresql/kustomization.yaml create mode 100644 postgresql/namespace.yaml create mode 100644 postgresql/operator-service-account-rbac.yaml create mode 100644 postgresql/operatorconfiguration.crd.yaml create mode 100644 postgresql/pod.env create mode 100644 postgresql/postgres-operator.yaml create mode 100644 postgresql/postgresql-operator-configuration.yaml create mode 100644 postgresql/postgresteam.crd.yaml create mode 100644 postgresql/ui/kustomization.yaml diff --git a/postgresql/.gitignore b/postgresql/.gitignore new file mode 100644 index 0000000..2ed1a52 --- /dev/null +++ b/postgresql/.gitignore @@ -0,0 +1 @@ +pod.secrets diff --git a/postgresql/api-service.yaml b/postgresql/api-service.yaml new file mode 100644 index 0000000..6164481 --- /dev/null +++ b/postgresql/api-service.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: Service +metadata: + name: postgres-operator +spec: + type: ClusterIP + ports: + - port: 8080 + protocol: TCP + targetPort: 8080 + selector: + name: postgres-operator diff --git a/postgresql/default-cluster.yaml b/postgresql/default-cluster.yaml new file mode 100644 index 0000000..652b41f --- /dev/null +++ b/postgresql/default-cluster.yaml @@ -0,0 +1,18 @@ +apiVersion: acid.zalan.do/v1 +kind: postgresql +metadata: + name: default + namespace: postgresql +spec: + teamId: acid + volume: + size: 10Gi + numberOfInstances: 1 + postgresql: + version: '15' + users: + dustin: + - superuser + - createdb + databases: + dustin: dustin diff --git a/postgresql/kustomization.yaml b/postgresql/kustomization.yaml new file mode 100644 index 0000000..505fc03 --- /dev/null +++ b/postgresql/kustomization.yaml @@ -0,0 +1,33 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: postgresql + +resources: +- namespace.yaml +- operatorconfiguration.crd.yaml +- postgresteam.crd.yaml +- postgresql-operator-configuration.yaml +- operator-service-account-rbac.yaml +- postgres-operator.yaml +- api-service.yaml +- default-cluster.yaml + +secretGenerator: +- name: ssh-auth + files: + - ssh-backup.key + options: + disableNameSuffixHash: true +- name: pod-secrets + envs: + - pod.secrets + options: + disableNameSuffixHash: true + +configMapGenerator: +- name: pod-env + envs: + - pod.env + options: + disableNameSuffixHash: true diff --git a/postgresql/namespace.yaml b/postgresql/namespace.yaml new file mode 100644 index 0000000..a6bb64a --- /dev/null +++ b/postgresql/namespace.yaml @@ -0,0 +1,4 @@ +apiVersion: v1 +kind: Namespace +metadata: + name: postgresql diff --git a/postgresql/operator-service-account-rbac.yaml b/postgresql/operator-service-account-rbac.yaml new file mode 100644 index 0000000..c10dc5f --- /dev/null +++ b/postgresql/operator-service-account-rbac.yaml @@ -0,0 +1,290 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: postgres-operator + namespace: default + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: postgres-operator +rules: +# all verbs allowed for custom operator resources +- apiGroups: + - acid.zalan.do + resources: + - postgresqls + - postgresqls/status + - operatorconfigurations + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +# operator only reads PostgresTeams +- apiGroups: + - acid.zalan.do + resources: + - postgresteams + verbs: + - get + - list + - watch +# all verbs allowed for event streams (Zalando-internal feature) +# - apiGroups: +# - zalando.org +# resources: +# - fabriceventstreams +# verbs: +# - create +# - delete +# - deletecollection +# - get +# - list +# - patch +# - update +# - watch +# to create or get/update CRDs when starting up +- apiGroups: + - apiextensions.k8s.io + resources: + - customresourcedefinitions + verbs: + - create + - get + - patch + - update +# to read configuration from ConfigMaps +- apiGroups: + - "" + resources: + - configmaps + verbs: + - get +# to send events to the CRs +- apiGroups: + - "" + resources: + - events + verbs: + - create + - get + - list + - patch + - update + - watch +# to manage endpoints which are also used by Patroni +- apiGroups: + - "" + resources: + - endpoints + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +# to CRUD secrets for database access +- apiGroups: + - "" + resources: + - secrets + verbs: + - create + - delete + - get + - update +# to check nodes for node readiness label +- apiGroups: + - "" + resources: + - nodes + verbs: + - get + - list + - watch +# to read or delete existing PVCs. Creation via StatefulSet +- apiGroups: + - "" + resources: + - persistentvolumeclaims + verbs: + - delete + - get + - list + - patch + - update + # to read existing PVs. Creation should be done via dynamic provisioning +- apiGroups: + - "" + resources: + - persistentvolumes + verbs: + - get + - list + - update # only for resizing AWS volumes +# to watch Spilo pods and do rolling updates. Creation via StatefulSet +- apiGroups: + - "" + resources: + - pods + verbs: + - delete + - get + - list + - patch + - update + - watch +# to resize the filesystem in Spilo pods when increasing volume size +- apiGroups: + - "" + resources: + - pods/exec + verbs: + - create +# to CRUD services to point to Postgres cluster instances +- apiGroups: + - "" + resources: + - services + verbs: + - create + - delete + - get + - patch + - update +# to CRUD the StatefulSet which controls the Postgres cluster instances +- apiGroups: + - apps + resources: + - statefulsets + - deployments + verbs: + - create + - delete + - get + - list + - patch +# to CRUD cron jobs for logical backups +- apiGroups: + - batch + resources: + - cronjobs + verbs: + - create + - delete + - get + - list + - patch + - update +# to get namespaces operator resources can run in +- apiGroups: + - "" + resources: + - namespaces + verbs: + - get +# to define PDBs. Update happens via delete/create +- apiGroups: + - policy + resources: + - poddisruptionbudgets + verbs: + - create + - delete + - get +# to create ServiceAccounts in each namespace the operator watches +- apiGroups: + - "" + resources: + - serviceaccounts + verbs: + - get + - create +# to create role bindings to the postgres-pod service account +- apiGroups: + - rbac.authorization.k8s.io + resources: + - rolebindings + verbs: + - get + - create +# to grant privilege to run privileged pods (not needed by default) +#- apiGroups: +# - extensions +# resources: +# - podsecuritypolicies +# resourceNames: +# - privileged +# verbs: +# - use + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: postgres-operator +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: postgres-operator +subjects: +- kind: ServiceAccount + name: postgres-operator + namespace: default + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: postgres-pod +rules: +# Patroni needs to watch and manage endpoints +- apiGroups: + - "" + resources: + - endpoints + verbs: + - create + - delete + - deletecollection + - get + - list + - patch + - update + - watch +# Patroni needs to watch pods +- apiGroups: + - "" + resources: + - pods + verbs: + - get + - list + - patch + - update + - watch +# to let Patroni create a headless service +- apiGroups: + - "" + resources: + - services + verbs: + - create +# to grant privilege to run privileged pods (not needed by default) +#- apiGroups: +# - extensions +# resources: +# - podsecuritypolicies +# resourceNames: +# - privileged +# verbs: +# - use diff --git a/postgresql/operatorconfiguration.crd.yaml b/postgresql/operatorconfiguration.crd.yaml new file mode 100644 index 0000000..f60d842 --- /dev/null +++ b/postgresql/operatorconfiguration.crd.yaml @@ -0,0 +1,677 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: operatorconfigurations.acid.zalan.do +spec: + group: acid.zalan.do + names: + kind: OperatorConfiguration + listKind: OperatorConfigurationList + plural: operatorconfigurations + singular: operatorconfiguration + shortNames: + - opconfig + categories: + - all + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + additionalPrinterColumns: + - name: Image + type: string + description: Spilo image to be used for Pods + jsonPath: .configuration.docker_image + - name: Cluster-Label + type: string + description: Label for K8s resources created by operator + jsonPath: .configuration.kubernetes.cluster_name_label + - name: Service-Account + type: string + description: Name of service account to be used + jsonPath: .configuration.kubernetes.pod_service_account_name + - name: Min-Instances + type: integer + description: Minimum number of instances per Postgres cluster + jsonPath: .configuration.min_instances + - name: Age + type: date + jsonPath: .metadata.creationTimestamp + schema: + openAPIV3Schema: + type: object + required: + - kind + - apiVersion + - configuration + properties: + kind: + type: string + enum: + - OperatorConfiguration + apiVersion: + type: string + enum: + - acid.zalan.do/v1 + configuration: + type: object + properties: + crd_categories: + type: array + nullable: true + items: + type: string + docker_image: + type: string + default: "ghcr.io/zalando/spilo-15:3.0-p1" + enable_crd_registration: + type: boolean + default: true + enable_crd_validation: + type: boolean + description: deprecated + default: true + enable_lazy_spilo_upgrade: + type: boolean + default: false + enable_pgversion_env_var: + type: boolean + default: true + enable_shm_volume: + type: boolean + default: true + enable_spilo_wal_path_compat: + type: boolean + default: false + enable_team_id_clustername_prefix: + type: boolean + default: false + etcd_host: + type: string + default: "" + ignore_instance_limits_annotation_key: + type: string + kubernetes_use_configmaps: + type: boolean + default: false + max_instances: + type: integer + description: "-1 = disabled" + minimum: -1 + default: -1 + min_instances: + type: integer + description: "-1 = disabled" + minimum: -1 + default: -1 + resync_period: + type: string + default: "30m" + repair_period: + type: string + default: "5m" + set_memory_request_to_limit: + type: boolean + default: false + sidecar_docker_images: + type: object + additionalProperties: + type: string + sidecars: + type: array + nullable: true + items: + type: object + x-kubernetes-preserve-unknown-fields: true + workers: + type: integer + minimum: 1 + default: 8 + users: + type: object + properties: + additional_owner_roles: + type: array + nullable: true + items: + type: string + enable_password_rotation: + type: boolean + default: false + password_rotation_interval: + type: integer + default: 90 + password_rotation_user_retention: + type: integer + default: 180 + replication_username: + type: string + default: standby + super_username: + type: string + default: postgres + major_version_upgrade: + type: object + properties: + major_version_upgrade_mode: + type: string + default: "off" + major_version_upgrade_team_allow_list: + type: array + items: + type: string + minimal_major_version: + type: string + default: "11" + target_major_version: + type: string + default: "15" + kubernetes: + type: object + properties: + additional_pod_capabilities: + type: array + items: + type: string + cluster_domain: + type: string + default: "cluster.local" + cluster_labels: + type: object + additionalProperties: + type: string + default: + application: spilo + cluster_name_label: + type: string + default: "cluster-name" + custom_pod_annotations: + type: object + additionalProperties: + type: string + delete_annotation_date_key: + type: string + delete_annotation_name_key: + type: string + downscaler_annotations: + type: array + items: + type: string + enable_cross_namespace_secret: + type: boolean + default: false + enable_init_containers: + type: boolean + default: true + enable_pod_antiaffinity: + type: boolean + default: false + enable_pod_disruption_budget: + type: boolean + default: true + enable_readiness_probe: + type: boolean + default: false + enable_sidecars: + type: boolean + default: true + ignored_annotations: + type: array + items: + type: string + infrastructure_roles_secret_name: + type: string + infrastructure_roles_secrets: + type: array + nullable: true + items: + type: object + required: + - secretname + - userkey + - passwordkey + properties: + secretname: + type: string + userkey: + type: string + passwordkey: + type: string + rolekey: + type: string + defaultuservalue: + type: string + defaultrolevalue: + type: string + details: + type: string + template: + type: boolean + inherited_annotations: + type: array + items: + type: string + inherited_labels: + type: array + items: + type: string + master_pod_move_timeout: + type: string + default: "20m" + node_readiness_label: + type: object + additionalProperties: + type: string + node_readiness_label_merge: + type: string + enum: + - "AND" + - "OR" + oauth_token_secret_name: + type: string + default: "postgresql-operator" + pdb_name_format: + type: string + default: "postgres-{cluster}-pdb" + pod_antiaffinity_preferred_during_scheduling: + type: boolean + default: false + pod_antiaffinity_topology_key: + type: string + default: "kubernetes.io/hostname" + pod_environment_configmap: + type: string + pod_environment_secret: + type: string + pod_management_policy: + type: string + enum: + - "ordered_ready" + - "parallel" + default: "ordered_ready" + pod_priority_class_name: + type: string + pod_role_label: + type: string + default: "spilo-role" + pod_service_account_definition: + type: string + default: "" + pod_service_account_name: + type: string + default: "postgres-pod" + pod_service_account_role_binding_definition: + type: string + default: "" + pod_terminate_grace_period: + type: string + default: "5m" + secret_name_template: + type: string + default: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}" + share_pgsocket_with_sidecars: + type: boolean + default: false + spilo_allow_privilege_escalation: + type: boolean + default: true + spilo_runasuser: + type: integer + spilo_runasgroup: + type: integer + spilo_fsgroup: + type: integer + spilo_privileged: + type: boolean + default: false + storage_resize_mode: + type: string + enum: + - "ebs" + - "mixed" + - "pvc" + - "off" + default: "pvc" + toleration: + type: object + additionalProperties: + type: string + watched_namespace: + type: string + postgres_pod_resources: + type: object + properties: + default_cpu_limit: + type: string + pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "1" + default_cpu_request: + type: string + pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "100m" + default_memory_limit: + type: string + pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "500Mi" + default_memory_request: + type: string + pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "100Mi" + max_cpu_request: + type: string + pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + max_memory_request: + type: string + pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + min_cpu_limit: + type: string + pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "250m" + min_memory_limit: + type: string + pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "250Mi" + timeouts: + type: object + properties: + patroni_api_check_interval: + type: string + default: "1s" + patroni_api_check_timeout: + type: string + default: "5s" + pod_label_wait_timeout: + type: string + default: "10m" + pod_deletion_wait_timeout: + type: string + default: "10m" + ready_wait_interval: + type: string + default: "4s" + ready_wait_timeout: + type: string + default: "30s" + resource_check_interval: + type: string + default: "3s" + resource_check_timeout: + type: string + default: "10m" + load_balancer: + type: object + properties: + custom_service_annotations: + type: object + additionalProperties: + type: string + db_hosted_zone: + type: string + default: "db.example.com" + enable_master_load_balancer: + type: boolean + default: true + enable_master_pooler_load_balancer: + type: boolean + default: false + enable_replica_load_balancer: + type: boolean + default: false + enable_replica_pooler_load_balancer: + type: boolean + default: false + external_traffic_policy: + type: string + enum: + - "Cluster" + - "Local" + default: "Cluster" + master_dns_name_format: + type: string + default: "{cluster}.{namespace}.{hostedzone}" + master_legacy_dns_name_format: + type: string + default: "{cluster}.{team}.{hostedzone}" + replica_dns_name_format: + type: string + default: "{cluster}-repl.{namespace}.{hostedzone}" + replica_legacy_dns_name_format: + type: string + default: "{cluster}-repl.{team}.{hostedzone}" + aws_or_gcp: + type: object + properties: + additional_secret_mount: + type: string + additional_secret_mount_path: + type: string + default: "/meta/credentials" + aws_region: + type: string + default: "eu-central-1" + enable_ebs_gp3_migration: + type: boolean + default: false + enable_ebs_gp3_migration_max_size: + type: integer + default: 1000 + gcp_credentials: + type: string + kube_iam_role: + type: string + log_s3_bucket: + type: string + wal_az_storage_account: + type: string + wal_gs_bucket: + type: string + wal_s3_bucket: + type: string + logical_backup: + type: object + properties: + logical_backup_azure_storage_account_name: + type: string + logical_backup_azure_storage_container: + type: string + logical_backup_azure_storage_account_key: + type: string + logical_backup_cpu_limit: + type: string + pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + logical_backup_cpu_request: + type: string + pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + logical_backup_docker_image: + type: string + default: "registry.opensource.zalan.do/acid/logical-backup:v1.10.0" + logical_backup_google_application_credentials: + type: string + logical_backup_job_prefix: + type: string + default: "logical-backup-" + logical_backup_memory_limit: + type: string + pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + logical_backup_memory_request: + type: string + pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + logical_backup_provider: + type: string + enum: + - "az" + - "gcs" + - "s3" + default: "s3" + logical_backup_s3_access_key_id: + type: string + logical_backup_s3_bucket: + type: string + logical_backup_s3_endpoint: + type: string + logical_backup_s3_region: + type: string + logical_backup_s3_secret_access_key: + type: string + logical_backup_s3_sse: + type: string + logical_backup_s3_retention_time: + type: string + logical_backup_schedule: + type: string + pattern: '^(\d+|\*)(/\d+)?(\s+(\d+|\*)(/\d+)?){4}$' + default: "30 00 * * *" + debug: + type: object + properties: + debug_logging: + type: boolean + default: true + enable_database_access: + type: boolean + default: true + teams_api: + type: object + properties: + enable_admin_role_for_users: + type: boolean + default: true + enable_postgres_team_crd: + type: boolean + default: true + enable_postgres_team_crd_superusers: + type: boolean + default: false + enable_team_member_deprecation: + type: boolean + default: false + enable_team_superuser: + type: boolean + default: false + enable_teams_api: + type: boolean + default: true + pam_configuration: + type: string + default: "https://info.example.com/oauth2/tokeninfo?access_token= uid realm=/employees" + pam_role_name: + type: string + default: "zalandos" + postgres_superuser_teams: + type: array + items: + type: string + protected_role_names: + type: array + items: + type: string + default: + - admin + - cron_admin + role_deletion_suffix: + type: string + default: "_deleted" + team_admin_role: + type: string + default: "admin" + team_api_role_configuration: + type: object + additionalProperties: + type: string + default: + log_statement: all + teams_api_url: + type: string + default: "https://teams.example.com/api/" + logging_rest_api: + type: object + properties: + api_port: + type: integer + default: 8080 + cluster_history_entries: + type: integer + default: 1000 + ring_log_lines: + type: integer + default: 100 + scalyr: # deprecated + type: object + properties: + scalyr_api_key: + type: string + scalyr_cpu_limit: + type: string + pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "1" + scalyr_cpu_request: + type: string + pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "100m" + scalyr_image: + type: string + scalyr_memory_limit: + type: string + pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "500Mi" + scalyr_memory_request: + type: string + pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "50Mi" + scalyr_server_url: + type: string + default: "https://upload.eu.scalyr.com" + connection_pooler: + type: object + properties: + connection_pooler_schema: + type: string + default: "pooler" + connection_pooler_user: + type: string + default: "pooler" + connection_pooler_image: + type: string + default: "registry.opensource.zalan.do/acid/pgbouncer:master-27" + connection_pooler_max_db_connections: + type: integer + default: 60 + connection_pooler_mode: + type: string + enum: + - "session" + - "transaction" + default: "transaction" + connection_pooler_number_of_instances: + type: integer + minimum: 1 + default: 2 + connection_pooler_default_cpu_limit: + type: string + pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "1" + connection_pooler_default_cpu_request: + type: string + pattern: '^(\d+m|\d+(\.\d{1,3})?)$' + default: "500m" + connection_pooler_default_memory_limit: + type: string + pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "100Mi" + connection_pooler_default_memory_request: + type: string + pattern: '^(\d+(e\d+)?|\d+(\.\d+)?(e\d+)?[EPTGMK]i?)$' + default: "100Mi" + patroni: + type: object + properties: + enable_patroni_failsafe_mode: + type: boolean + default: false + status: + type: object + additionalProperties: + type: string diff --git a/postgresql/pod.env b/postgresql/pod.env new file mode 100644 index 0000000..8c48719 --- /dev/null +++ b/postgresql/pod.env @@ -0,0 +1,2 @@ +WAL_S3_BUCKET=pgbackup +AWS_ENDPOINT=https://burp.pyrocufflink.blue:9000 diff --git a/postgresql/postgres-operator.yaml b/postgresql/postgres-operator.yaml new file mode 100644 index 0000000..6c323d5 --- /dev/null +++ b/postgresql/postgres-operator.yaml @@ -0,0 +1,45 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: postgres-operator + labels: + application: postgres-operator +spec: + replicas: 1 + strategy: + type: "Recreate" + selector: + matchLabels: + name: postgres-operator + template: + metadata: + labels: + name: postgres-operator + spec: + serviceAccountName: postgres-operator + containers: + - name: postgres-operator + image: registry.opensource.zalan.do/acid/postgres-operator:v1.10.0 + imagePullPolicy: IfNotPresent + resources: + requests: + cpu: 100m + memory: 250Mi + limits: + cpu: 500m + memory: 500Mi + securityContext: + runAsUser: 1000 + runAsNonRoot: true + readOnlyRootFilesystem: true + allowPrivilegeEscalation: false + env: + # provided additional ENV vars can overwrite individual config map entries + #- name: CONFIG_MAP_NAME + # value: "postgres-operator" + # In order to use the CRD OperatorConfiguration instead, uncomment these lines and comment out the two lines above + - name: POSTGRES_OPERATOR_CONFIGURATION_OBJECT + value: postgresql-operator-configuration + # Define an ID to isolate controllers from each other + # - name: CONTROLLER_ID + # value: "second-operator" diff --git a/postgresql/postgresql-operator-configuration.yaml b/postgresql/postgresql-operator-configuration.yaml new file mode 100644 index 0000000..92d3809 --- /dev/null +++ b/postgresql/postgresql-operator-configuration.yaml @@ -0,0 +1,213 @@ +apiVersion: "acid.zalan.do/v1" +kind: OperatorConfiguration +metadata: + name: postgresql-operator-configuration +configuration: + docker_image: ghcr.io/zalando/spilo-15:3.0-p1 + # enable_crd_registration: true + # crd_categories: + # - all + # enable_lazy_spilo_upgrade: false + enable_pgversion_env_var: true + # enable_shm_volume: true + enable_spilo_wal_path_compat: false + enable_team_id_clustername_prefix: false + etcd_host: "" + # ignore_instance_limits_annotation_key: "" + # kubernetes_use_configmaps: false + max_instances: -1 + min_instances: -1 + resync_period: 30m + repair_period: 5m + # set_memory_request_to_limit: false + # sidecars: + # - image: image:123 + # name: global-sidecar-1 + # ports: + # - containerPort: 80 + # protocol: TCP + workers: 2 + users: + # additional_owner_roles: + # - cron_admin + enable_password_rotation: false + password_rotation_interval: 90 + password_rotation_user_retention: 180 + replication_username: standby + super_username: postgres + major_version_upgrade: + major_version_upgrade_mode: "off" + # major_version_upgrade_team_allow_list: + # - acid + minimal_major_version: "11" + target_major_version: "15" + kubernetes: + # additional_pod_capabilities: + # - "SYS_NICE" + cluster_domain: cluster.local + cluster_labels: + application: spilo + cluster_name_label: cluster-name + # custom_pod_annotations: + # keya: valuea + # keyb: valueb + # delete_annotation_date_key: delete-date + # delete_annotation_name_key: delete-clustername + # downscaler_annotations: + # - deployment-time + # - downscaler/* + enable_cross_namespace_secret: true + enable_init_containers: true + enable_pod_antiaffinity: false + enable_pod_disruption_budget: true + enable_readiness_probe: false + enable_sidecars: true + # ignored_annotations: + # - k8s.v1.cni.cncf.io/network-status + # infrastructure_roles_secret_name: "postgresql-infrastructure-roles" + # infrastructure_roles_secrets: + # - secretname: "monitoring-roles" + # userkey: "user" + # passwordkey: "password" + # rolekey: "inrole" + # - secretname: "other-infrastructure-role" + # userkey: "other-user-key" + # passwordkey: "other-password-key" + # inherited_annotations: + # - owned-by + # inherited_labels: + # - application + # - environment + master_pod_move_timeout: 20m + # node_readiness_label: + # status: ready + # node_readiness_label_merge: "OR" + oauth_token_secret_name: postgresql-operator + pdb_name_format: "postgres-{cluster}-pdb" + pod_antiaffinity_preferred_during_scheduling: false + pod_antiaffinity_topology_key: "kubernetes.io/hostname" + pod_environment_configmap: postgresql/pod-env + pod_environment_secret: pod-secrets + pod_management_policy: "ordered_ready" + # pod_priority_class_name: "postgres-pod-priority" + pod_role_label: spilo-role + # pod_service_account_definition: "" + pod_service_account_name: postgres-pod + # pod_service_account_role_binding_definition: "" + pod_terminate_grace_period: 5m + secret_name_template: "{username}.{cluster}.credentials.{tprkind}.{tprgroup}" + share_pgsocket_with_sidecars: false + spilo_allow_privilege_escalation: true + # spilo_runasuser: 101 + # spilo_runasgroup: 103 + # spilo_fsgroup: 103 + spilo_privileged: false + storage_resize_mode: pvc + # toleration: + # key: db-only + # operator: Exists + # effect: NoSchedule + # watched_namespace: "" + postgres_pod_resources: + default_cpu_limit: "1" + default_cpu_request: 100m + default_memory_limit: 500Mi + default_memory_request: 100Mi + # max_cpu_request: "1" + # max_memory_request: 4Gi + # min_cpu_limit: 250m + # min_memory_limit: 250Mi + timeouts: + patroni_api_check_interval: 1s + patroni_api_check_timeout: 5s + pod_label_wait_timeout: 10m + pod_deletion_wait_timeout: 10m + ready_wait_interval: 4s + ready_wait_timeout: 30s + resource_check_interval: 3s + resource_check_timeout: 10m + load_balancer: + # custom_service_annotations: + # keyx: valuex + # keyy: valuey + # db_hosted_zone: "" + enable_master_load_balancer: false + enable_master_pooler_load_balancer: false + enable_replica_load_balancer: false + enable_replica_pooler_load_balancer: false + external_traffic_policy: "Cluster" + master_dns_name_format: "{cluster}.{namespace}.{hostedzone}" + # master_legacy_dns_name_format: "{cluster}.{team}.{hostedzone}" + replica_dns_name_format: "{cluster}-repl.{namespace}.{hostedzone}" + # replica_dns_old_name_format: "{cluster}-repl.{team}.{hostedzone}" + aws_or_gcp: + additional_secret_mount: ssh-auth + additional_secret_mount_path: /run/secrets/ssh-auth + aws_region: eu-central-1 + enable_ebs_gp3_migration: false + # enable_ebs_gp3_migration_max_size: 1000 + # gcp_credentials: "" + # kube_iam_role: "" + # log_s3_bucket: "" + # wal_az_storage_account: "" + # wal_gs_bucket: "" + # wal_s3_bucket: "" + logical_backup: + # logical_backup_azure_storage_account_name: "" + # logical_backup_azure_storage_container: "" + # logical_backup_azure_storage_account_key: "" + # logical_backup_cpu_limit: "" + # logical_backup_cpu_request: "" + # logical_backup_memory_limit: "" + # logical_backup_memory_request: "" + logical_backup_docker_image: "registry.opensource.zalan.do/acid/logical-backup:v1.10.0" + # logical_backup_google_application_credentials: "" + logical_backup_job_prefix: "logical-backup-" + logical_backup_provider: "s3" + # logical_backup_s3_access_key_id: "" + logical_backup_s3_bucket: "my-bucket-url" + # logical_backup_s3_endpoint: "" + # logical_backup_s3_region: "" + # logical_backup_s3_secret_access_key: "" + logical_backup_s3_sse: "AES256" + # logical_backup_s3_retention_time: "" + logical_backup_schedule: "30 00 * * *" + debug: + debug_logging: true + enable_database_access: true + teams_api: + # enable_admin_role_for_users: true + # enable_postgres_team_crd: false + # enable_postgres_team_crd_superusers: false + enable_team_member_deprecation: false + enable_team_superuser: false + enable_teams_api: false + # pam_configuration: "" + pam_role_name: zalandos + # postgres_superuser_teams: + # - postgres_superusers + protected_role_names: + - admin + - cron_admin + role_deletion_suffix: "_deleted" + team_admin_role: admin + team_api_role_configuration: + log_statement: all + # teams_api_url: "" + logging_rest_api: + api_port: 8080 + cluster_history_entries: 1000 + ring_log_lines: 100 + connection_pooler: + connection_pooler_default_cpu_limit: "1" + connection_pooler_default_cpu_request: "500m" + connection_pooler_default_memory_limit: 100Mi + connection_pooler_default_memory_request: 100Mi + connection_pooler_image: "registry.opensource.zalan.do/acid/pgbouncer:master-27" + # connection_pooler_max_db_connections: 60 + connection_pooler_mode: "transaction" + connection_pooler_number_of_instances: 2 + # connection_pooler_schema: "pooler" + # connection_pooler_user: "pooler" + patroni: + enable_patroni_failsafe_mode: false diff --git a/postgresql/postgresteam.crd.yaml b/postgresql/postgresteam.crd.yaml new file mode 100644 index 0000000..2588e53 --- /dev/null +++ b/postgresql/postgresteam.crd.yaml @@ -0,0 +1,68 @@ +apiVersion: apiextensions.k8s.io/v1 +kind: CustomResourceDefinition +metadata: + name: postgresteams.acid.zalan.do +spec: + group: acid.zalan.do + names: + kind: PostgresTeam + listKind: PostgresTeamList + plural: postgresteams + singular: postgresteam + shortNames: + - pgteam + categories: + - all + scope: Namespaced + versions: + - name: v1 + served: true + storage: true + subresources: + status: {} + schema: + openAPIV3Schema: + type: object + required: + - kind + - apiVersion + - spec + properties: + kind: + type: string + enum: + - PostgresTeam + apiVersion: + type: string + enum: + - acid.zalan.do/v1 + spec: + type: object + properties: + additionalSuperuserTeams: + type: object + description: "Map for teamId and associated additional superuser teams" + additionalProperties: + type: array + nullable: true + description: "List of teams to become Postgres superusers" + items: + type: string + additionalTeams: + type: object + description: "Map for teamId and associated additional teams" + additionalProperties: + type: array + nullable: true + description: "List of teams whose members will also be added to the Postgres cluster" + items: + type: string + additionalMembers: + type: object + description: "Map for teamId and associated additional users" + additionalProperties: + type: array + nullable: true + description: "List of users who will also be added to the Postgres cluster" + items: + type: string diff --git a/postgresql/ui/kustomization.yaml b/postgresql/ui/kustomization.yaml new file mode 100644 index 0000000..dd17eb6 --- /dev/null +++ b/postgresql/ui/kustomization.yaml @@ -0,0 +1,38 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization + +namespace: postgresql + +resources: +- github.com/zalando/postgres-operator/ui/manifests?ref=v1.9.0 + +patches: +- patch: |- + apiVersion: apps/v1 + kind: Deployment + metadata: + name: postgres-operator-ui + spec: + template: + spec: + containers: + - name: service + env: + - name: TARGET_NAMESPACE + value: '*' +- target: + kind: Ingress + labelSelector: application=postgres-operator-ui + patch: |- + - op: replace + path: /spec/rules/0/host + value: psqlui.pyrocufflink.blue +- patch: |- + apiVersion: networking.k8s.io/v1 + kind: Ingress + metadata: + name: postgres-operator-ui + annotations: + nginx.ingress.kubernetes.io/auth-method: GET + nginx.ingress.kubernetes.io/auth-url: http://authelia.authelia.svc.cluster.local:9091/api/verify + nginx.ingress.kubernetes.io/auth-signin: https://auth.pyrocufflink.blue/?rm=$request_method