Nächste Überarbeitung | Vorhergehende Überarbeitung |
it-wiki:kubernetes:postgresql_mit_zalando_bereitstellen [2024/03/19 10:06] – angelegt marko | it-wiki:kubernetes:postgresql_mit_zalando_bereitstellen [2024/04/25 05:31] (aktuell) – marko |
---|
====== PostgreSQL mit Zalando in Kubernetes bereitstellen ====== | ====== PostgreSQL mit Zalando Operator in Kubernetes bereitstellen ====== |
In dieser Anleitung erfahren Sie, wie Sie mit dem [[https://github.com/zalando/postgres-operator|Zalando Postgres-Operator]] Postgres-Cluster in bereitstellen. | In dieser Anleitung erfahren Sie, wie Sie mit dem [[https://github.com/zalando/postgres-operator|Zalando Postgres-Operator]] Postgres-Cluster in bereitstellen. |
| |
\\ | \\ |
\\ | \\ |
Im Diagramm wird die Postgres-''StatefulSet'' auf drei Knoten in drei verschiedenen Zonen bereitgestellt. Sie können steuern, wie GKE auf Knoten bereitgestellt wird. Dazu legen Sie den erforderlichen Pod-Regeln für Affinität und Anti-Affinität für die postgresql benutzerdefinierte Ressourcenspezifikation. Wenn eine Zone gemäß der empfohlenen Konfiguration ausfällt, verschiebt GKE die Pods auf andere verfügbare Knoten in Ihrem Cluster. Zum Speichern von Daten verwenden Sie SSD-Laufwerke (premium-rwo StorageClass). Diese werden in den meisten Fällen für stark ausgelastete Datenbanken aufgrund ihrerniedrigen Latenz und hohen IOPS empfohlen. | Im Diagramm wird die Postgres-''StatefulSet'' auf drei Knoten in drei verschiedenen Zonen bereitgestellt. Sie können steuern, wie Kubernetes auf Knoten bereitgestellt wird. Dazu legen Sie den erforderlichen Pod-Regeln für [[https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity|Affinität und Anti-Affinität]] für die [[https://github.com/zalando/postgres-operator/blob/master/manifests/complete-postgres-manifest.yaml|postgresql]] benutzerdefinierte Ressourcenspezifikation. Wenn eine Zone gemäß der empfohlenen Konfiguration ausfällt, verschiebt Kubernetes die Pods auf andere verfügbare Knoten in Ihrem Cluster. Zum Speichern von Daten verwenden Sie SSD-Laufwerke (''premium-rwo'' StorageClass). Diese werden in den meisten Fällen für stark ausgelastete Datenbanken aufgrund ihrerniedrigen Latenz und hohen IOPS empfohlen. |
| \\ |
| \\ |
| \\ |
| ===== Zalando-Operator in Ihrem Cluster bereitstellen ===== |
| Stellen Sie den Zalando-Operator mithilfe eines Helm-Diagramms in Ihrem Kubernetes-Cluster bereit. |
| - Fügen Sie das Helm-Diagramm-Repository des Zalando-Operators hinzu: <code bash>helm repo add postgres-operator-charts https://opensource.zalando.com/postgres-operator/charts/postgres-operator</code> |
| - Erstellen Sie einen Namespace für den Zalando-Operator und den Postgres-Cluster: <code bash>kubectl create ns postgres |
| kubectl create ns zalando</code> |
| - Stellen Sie den Zalando-Operator mit dem Helm-Befehlszeilentool bereit: <code bash>helm install postgres-operator postgres-operator-charts/postgres-operator -n zalando \ |
| --set configKubernetes.enable_pod_antiaffinity=true \ |
| --set configKubernetes.pod_antiaffinity_preferred_during_scheduling=true \ |
| --set configKubernetes.pod_antiaffinity_topology_key="topology.kubernetes.io/zone" \ |
| --set configKubernetes.spilo_fsgroup="103"</code> Sie können die ''podAntiAffinity''-Einstellungen nicht direkt für die benutzerdefinierte Ressource konfigurieren, die den Postgres-Cluster darstellt. Legen Sie stattdessen global die ''podAntiAffinity''-Einstellungen für alle Postgres-Cluster in den Operatoreinstellungen fest. |
| - Prüfen Sie den Bereitstellungsstatus des Zalando-Operators mit Helm: <code bash>helm ls -n zalando</code> Die Ausgabe sieht in etwa so aus: <code bash>NAME NAMESPACE REVISION UPDATED STATUS CHART APP VERSION |
| postgres-operator zalando 1 2023-10-13 16:04:13.945614 +0200 CEST deployed postgres-operator-1.10.1 1.10.1</code> |
| |
| ===== Postgres bereitstellen ===== |
| Die grundlegende Konfiguration für die Postgres-Clusterinstanz umfasst die folgenden Komponenten: |
| * Drei Postgres-Replikate: ein Leader und zwei Standby-Replikate. |
| * CPU-Ressourcenzuweisung: eine CPU-Anfrage und zwei CPU-Limits mit 4 GB Speicheranfragen und -Limits. |
| * Toleranzen, nodeAffinities und topologySpreadConstraints, die für jede Arbeitslast konfiguriert sind und eine ordnungsgemäße Verteilung auf Kubernetes-Knoten mithilfe ihrer jeweiligen Knotenpools und verschiedenen Verfügbarkeitszonen gewährleisten. |
| |
| Diese Konfiguration stellt die minimale Einrichtung dar, die zum Erstellen eines produktionsfertigen Postgres-Clusters erforderlich ist. |
| |
| Das folgende Manifest beschreibt einen Postgres-Cluster: |
| [[https://github.com/GoogleCloudPlatform/kubernetes-engine-samples/blob/HEAD/databases/postgres-zalando/manifests/01-basic-cluster/my-cluster.yaml|databases/postgres-zalando/manifests/01-basic-cluster/my-cluster.yaml]] |
| <code yaml> |
| apiVersion: "acid.zalan.do/v1" |
| kind: postgresql |
| metadata: |
| name: my-cluster |
| spec: |
| dockerImage: ghcr.io/zalando/spilo-15:3.0-p1 |
| teamId: "my-team" |
| numberOfInstances: 3 |
| users: |
| mydatabaseowner: |
| - superuser |
| - createdb |
| myuser: [] |
| databases: |
| mydatabase: mydatabaseowner |
| postgresql: |
| version: "15" |
| parameters: |
| shared_buffers: "32MB" |
| max_connections: "10" |
| log_statement: "all" |
| password_encryption: scram-sha-256 |
| volume: |
| size: 5Gi |
| storageClass: premium-rwo |
| enableShmVolume: true |
| podAnnotations: |
| cluster-autoscaler.kubernetes.io/safe-to-evict: "true" |
| tolerations: |
| - key: "app.stateful/component" |
| operator: "Equal" |
| value: "postgres-operator" |
| effect: NoSchedule |
| nodeAffinity: |
| preferredDuringSchedulingIgnoredDuringExecution: |
| - weight: 1 |
| preference: |
| matchExpressions: |
| - key: "app.stateful/component" |
| operator: In |
| values: |
| - "postgres-operator" |
| resources: |
| requests: |
| cpu: "1" |
| memory: 4Gi |
| limits: |
| cpu: "2" |
| memory: 4Gi |
| sidecars: |
| - name: exporter |
| image: quay.io/prometheuscommunity/postgres-exporter:v0.14.0 |
| args: |
| - --collector.stat_statements |
| ports: |
| - name: exporter |
| containerPort: 9187 |
| protocol: TCP |
| resources: |
| limits: |
| cpu: 500m |
| memory: 256M |
| requests: |
| cpu: 100m |
| memory: 256M |
| env: |
| - name: "DATA_SOURCE_URI" |
| value: "localhost/postgres?sslmode=require" |
| - name: "DATA_SOURCE_USER" |
| value: "$(POSTGRES_USER)" |
| - name: "DATA_SOURCE_PASS" |
| value: "$(POSTGRES_PASSWORD)" |
| </code> |
| |
| Dieses Manifest hat die folgenden Felder: |
| * ''spec.teamId'' ist ein Präfix für die von Ihnen ausgewählten Clusterobjekte |
| * ''spec.numberOfInstances'' ist die Gesamtzahl der Instanzen für einen Cluster |
| * ''spec.users'' ist die Nutzerliste mit [[https://www.postgresql.org/docs/current/sql-createrole.html|Berechtigungen]] |
| * ''spec.databases'' ist die Datenbankliste im Format dbname: ownername |
| * ''spec.postgresql'' sind die Postgres-Parameter |
| * ''spec.volume'' sind die Parameter für den nichtflüchtigen Speicher |
| * ''spec.tolerations'' ist die Toleranz-Pod-Vorlage, mit der Cluster-Pods auf pool-postgres-Knoten geplant werden können |
| * ''spec.nodeAffinity'' ist die Pod-Vorlage nodeAffinity, die Kubernetes mitteilt, dass Cluster-Pods lieber auf pool-postgres-Knoten geplant werden sollen. |
| * ''spec.resources'' sind Anfragen und Limits für Cluster-Pods |
| * ''spec.sidecars'' ist eine Liste der Sidecar-Container, die postgres-exporter enthält |
| |
| Weitere Informationen finden Sie in der Postgres-Dokumentation unter [[https://postgres-operator.readthedocs.io/en/latest/reference/cluster_manifest/|Referenz zu Clustermanifesten]]. |
| |
| ===== Einfachen Postgres-Cluster erstellen ===== |
| - Erstellen Sie mithilfe der grundlegenden Konfiguration einen neuen Postgres-Cluster: <code bash>kubectl apply -n postgres -f manifests/01-basic-cluster/my-cluster.yaml</code> Mit diesem Befehl wird eine benutzerdefinierte PostgreSQL-Ressource des Zalando-Operators erstellt:\\ * CPU- und Speicheranforderungen und -limits\\ * Markierungen und Affinitäten zum Verteilen der bereitgestellten Pod-Replikate auf Kubernetes-Knoten.\\ * Eine Datenbank\\ * Zwei Nutzer mit Datenbankinhaberberechtigungen\\ * Ein Nutzer ohne Berechtigungen |
| - Warten Sie, bis Kubernetes die erforderlichen Arbeitslasten gestartet hat: <code bash>kubectl wait pods -l cluster-name=my-cluster --for condition=Ready --timeout=300s -n postgres</code> Die Verarbeitung dieses Befehls kann einige Minuten dauern. |
| - Prüfen Sie, ob Kubernetes die Postgres-Arbeitslasten erstellt hat: <code bash>kubectl get pod,svc,statefulset,deploy,pdb,secret -n postgres</code> Die Ausgabe sieht in etwa so aus: <code bash>NAME READY STATUS RESTARTS AGE |
| pod/my-cluster-0 1/1 Running 0 6m41s |
| pod/my-cluster-1 1/1 Running 0 5m56s |
| pod/my-cluster-2 1/1 Running 0 5m16s |
| pod/postgres-operator-db9667d4d-rgcs8 1/1 Running 0 12m |
| |
| NAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGE |
| service/my-cluster ClusterIP 10.52.12.109 <none> 5432/TCP 6m43s |
| service/my-cluster-config ClusterIP None <none> <none> 5m55s |
| service/my-cluster-repl ClusterIP 10.52.6.152 <none> 5432/TCP 6m43s |
| service/postgres-operator ClusterIP 10.52.8.176 <none> 8080/TCP 12m |
| |
| NAME READY AGE |
| statefulset.apps/my-cluster 3/3 6m43s |
| |
| NAME READY UP-TO-DATE AVAILABLE AGE |
| deployment.apps/postgres-operator 1/1 1 1 12m |
| |
| NAME MIN AVAILABLE MAX UNAVAILABLE ALLOWED DISRUPTIONS AGE |
| poddisruptionbudget.policy/postgres-my-cluster-pdb 1 N/A 0 6m44s |
| |
| NAME TYPE DATA AGE |
| secret/my-user.my-cluster.credentials.postgresql.acid.zalan.do Opaque 2 6m45s |
| secret/postgres.my-cluster.credentials.postgresql.acid.zalan.do Opaque 2 6m44s |
| secret/sh.helm.release.v1.postgres-operator.v1 helm.sh/release.v1 1 12m |
| secret/standby.my-cluster.credentials.postgresql.acid.zalan.do Opaque 2 6m44s |
| secret/zalando.my-cluster.credentials.postgresql.acid.zalan.do Opaque 2 6m44s</code> |
| |
| Der Operator erstellt die folgenden Ressourcen: |
| * Ein Postgres-StatefulSet, das drei Pod-Replikate für Postgres steuert |
| * Einen ''PodDisruptionBudgets'', wodurch mindestens ein verfügbares Replikat garantiert wird |
| * Den ''my-cluster''-Dienst, der nur auf das Leader-Replikat ausgerichtet ist |
| * Den ''my-cluster-repl''-Dienst, der den Postgres-Port für eingehende Verbindungen und für die Replikation zwischen Postgres-Replikaten verfügbar macht |
| * Den monitorlosen Dienst ''my-cluster-config'' zum Abrufen der Liste der laufenden Postgres-Pod-Replikate |
| * Secrets mit Nutzeranmeldedaten für den Zugriff auf die Datenbank und die Replikation zwischen Postgres-Knoten |
| |
| ===== Bei Postgres authentifizieren ===== |
| Sie können Postgres-Nutzer erstellen und ihnen Datenbankberechtigungen zuweisen. Das folgende Manifest beschreibt beispielsweise eine benutzerdefinierte Ressource, die Nutzer und Rollen zuweist: |
| <code yaml> |
| apiVersion: "acid.zalan.do/v1" |
| kind: postgresql |
| metadata: |
| name: my-cluster |
| spec: |
| ... |
| users: |
| mydatabaseowner: |
| - superuser |
| - createdb |
| myuser: [] |
| databases: |
| mydatabase: mydatabaseowner |
| </code> |
| |
| In diesem Manifest: |
| * Der ''mydatabaseowner''-Nutzer hat die Rollen [[https://www.postgresql.org/docs/16/sql-createrole.html|SUPERUSER und CREATEDB]], die uneingeschränkte Administratorrechte gewähren (Verwaltung der Postgres-Konfiguration, neue Datenbanken, Tabellen und Nutzer erstellen, usw). |
| * Dem Nutzer ''myuser'' wurden keine Rollen zugewiesen. Dies folgt der [[https://wiki.postgresql.org/wiki/Client_Authentication|Best Practice]] zur Verwendung von ''SUPERUSER'', um Nutzer mit den geringsten Berechtigungen zu erstellen. Detaillierte Rechte werden ''myuser'' von ''mydatabaseowner'' gewährt. Aus Sicherheitsgründen sollten Sie ''myuser''-Anmeldedaten nur für Clientanwendungen freigeben. |
| |
| ===== Passwörter speichern ===== |
| Verwenden Sie die [[https://www.postgresql.org/docs/current/auth-password.html|empfohlene Methode zum Speichern von Passwörtern]] ''scram-sha-256''. Das folgende Manifest beschreibt beispielsweise eine benutzerdefinierte Ressource, die die ''scram-sha-256''-Verschlüsselung mit dem Feld ''postgresql.parameters.password_encryption'' angibt: |
| <code yaml> |
| apiVersion: "acid.zalan.do/v1" |
| kind: postgresql |
| metadata: |
| name: my-cluster |
| spec: |
| ... |
| postgresql: |
| parameters: |
| password_encryption: scram-sha-256 |
| </code> |
| |
| ===== Nutzeranmeldedaten rotieren ===== |
| Sie können [[https://postgres-operator.readthedocs.io/en/latest/administrator/#password-rotation-in-k8s-secrets|Nutzeranmeldedaten rotieren]], die in Kubernetes-Secrets mit Zalando gespeichert sind. Das folgende Manifest beschreibt beispielsweise eine benutzerdefinierte Ressource, die die Rotation von Nutzeranmeldedaten mithilfe des Felds ''usersWithSecretRotation'' definiert: |
| <code yaml> |
| apiVersion: "acid.zalan.do/v1" |
| kind: postgresql |
| metadata: |
| name: my-cluster |
| spec: |
| ... |
| usersWithSecretRotation: |
| - myuser |
| - myanotheruser |
| - ... |
| </code> |
| |
| ===== Authentifizierungsbeispiel: Verbindung zu Postgres herstellen ===== |
| In diesem Abschnitt erfahren Sie, wie Sie einen Postgres-Beispielclient bereitstellen und mit dem Passwort aus einem Kubernetes-Secret eine Verbindung zur Datenbank herstellen. |
| - Führen Sie den Client-Pod aus, um mit Ihrem Postgres-Cluster zu interagieren: <code bash>kubectl apply -n postgres -f manifests/02-auth/client-pod.yaml</code> Die Anmeldedaten der Nutzer myuser und mydatabaseowner werden aus den zugehörigen Secrets übernommen und als Umgebungsvariablen im Pod bereitgestellt. |
| - Stellen Sie eine Verbindung zum Pod her, wenn er bereit ist: <code bash>kubectl wait pod postgres-client --for=condition=Ready --timeout=300s -n postgres |
| kubectl exec -it postgres-client -n postgres -- /bin/bash</code> |
| - Stellen Sie eine Verbindung zu Postgres her und versuchen Sie, eine neue Tabelle mit den myuser-Anmeldedaten zu erstellen: <code bash>PGPASSWORD=$CLIENTPASSWORD psql \ |
| -h my-cluster \ |
| -U $CLIENTUSERNAME \ |
| -d mydatabase \ |
| -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);"</code> Der Befehl sollte mit einem Fehler wie diesem fehlschlagen: <code bash>ERROR: permission denied for schema public |
| LINE 1: CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR...</code> Der Befehl schlägt fehl, da sich Nutzer ohne zugewiesene Berechtigungen standardmäßig nur bei Postgres anmelden und Datenbanken auflisten können. |
| - Erstellen Sie eine Tabelle mit ''mydatabaseowner''-Anmeldedaten und gewähren Sie ''myuser'' **alle** Berechtigungen für die Tabelle: <code bash>PGPASSWORD=$OWNERPASSWORD psql \ |
| -h my-cluster \ |
| -U $OWNERUSERNAME \ |
| -d mydatabase \ |
| -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);GRANT ALL ON test TO myuser;GRANT ALL ON SEQUENCE test_id_seq TO myuser;" </code> Die Ausgabe sieht in etwa so aus: <code bash>CREATE TABLE |
| GRANT |
| GRANT </code> |
| - Fügen Sie zufällige Daten mit den Anmeldedaten myuser in die Tabelle ein: <code bash>for i in {1..10}; do |
| DATA=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13) |
| PGPASSWORD=$CLIENTPASSWORD psql \ |
| -h my-cluster \ |
| -U $CLIENTUSERNAME \ |
| -d mydatabase \ |
| -c "INSERT INTO test(randomdata) VALUES ('$DATA');" |
| done</code> Die Ausgabe sieht in etwa so aus: <code bash>INSERT 0 1 |
| INSERT 0 1 |
| INSERT 0 1 |
| INSERT 0 1 |
| INSERT 0 1 |
| INSERT 0 1 |
| INSERT 0 1 |
| INSERT 0 1 |
| INSERT 0 1 |
| INSERT 0 1</code> |
| - Rufen Sie die eingefügten Werte ab: <code bash>PGPASSWORD=$CLIENTPASSWORD psql \ |
| -h my-cluster \ |
| -U $CLIENTUSERNAME \ |
| -d mydatabase \ |
| -c "SELECT * FROM test;"</code> Die Ausgabe sieht in etwa so aus: <code bash>id | randomdata |
| ----+--------------- |
| 1 | jup9HYsAjwtW4 |
| 2 | 9rLAyBlcpLgNT |
| 3 | wcXSqxb5Yz75g |
| 4 | KoDRSrx3muD6T |
| 5 | b9atC7RPai7En |
| 6 | 20d7kC8E6Vt1V |
| 7 | GmgNxaWbkevGq |
| 8 | BkTwFWH6hWC7r |
| 9 | nkLXHclkaqkqy |
| 10 | HEebZ9Lp71Nm3 |
| (10 rows)</code> |
| - Beenden Sie die Pod-Shell: <code bash>exit</code> |