Desplegar PostgreSQL en GKE con CloudNativePG

En esta guía se explica cómo desplegar clústeres de PostgreSQL en Google Kubernetes Engine (GKE) mediante el operador CloudNativePG.

PostgreSQL es una base de datos relacional de objetos de código abierto con varias décadas de desarrollo activo, lo que garantiza un rendimiento estable del cliente. Ofrece una serie de funciones, como la replicación, la recuperación a un momento dado, funciones de seguridad y extensibilidad. PostgreSQL es compatible con los principales sistemas operativos y cumple totalmente los estándares ACID (atomicidad, coherencia, aislamiento y durabilidad).

Esta guía está dirigida a administradores de plataformas, arquitectos de la nube y profesionales de operaciones que quieran implementar clústeres de PostgreSQL en GKE. Ejecutar Postgres en GKE en lugar de usar Cloud SQL puede ofrecer más flexibilidad y control de la configuración a los administradores de bases de datos con experiencia.

Ventajas

CloudNativePG es un operador de código abierto desarrollado por EDB con una licencia Apache 2. Ofrece las siguientes funciones para la implementación de PostgreSQL:

  • Una forma declarativa y nativa de Kubernetes de gestionar y configurar clústeres de PostgreSQL
  • Gestión de copias de seguridad con capturas de volumen o Cloud Storage
  • Conexión TLS cifrada en tránsito, posibilidad de usar tu propia autoridad de certificación e integración con Certificate Manager para la emisión y rotación automáticas de certificados TLS
  • Actualizaciones continuas de versiones secundarias de PostgreSQL
  • Uso del servidor de la API de Kubernetes para mantener el estado de un clúster de PostgreSQL y las conmutaciones por error para lograr una alta disponibilidad sin necesidad de herramientas adicionales
  • Una configuración de exportador de Prometheus integrada a través de métricas definidas por el usuario escritas en SQL

Configurar un entorno

Para configurar tu entorno, sigue estos pasos:

  1. Define las variables de entorno:

    export PROJECT_ID=PROJECT_ID
    export KUBERNETES_CLUSTER_PREFIX=postgres
    export REGION=us-central1
    

    Sustituye PROJECT_ID por el Google Cloud ID de tu proyecto.

  2. Clona el repositorio de GitHub:

    git clone https://github.com/GoogleCloudPlatform/kubernetes-engine-samples
    
  3. Cambia al directorio de trabajo:

    cd kubernetes-engine-samples/databases/postgresql-cloudnativepg
    

Crear la infraestructura del clúster

En esta sección, ejecutarás una secuencia de comandos de Terraform para crear un clúster de GKE privado, regional y de alta disponibilidad.

Puedes instalar el operador con un clúster estándar o de piloto automático.

Estándar

En el siguiente diagrama se muestra un clúster de GKE estándar regional privado desplegado en tres zonas diferentes:

Para desplegar esta infraestructura, ejecuta los siguientes comandos:

export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
terraform -chdir=terraform/gke-standard init
terraform -chdir=terraform/gke-standard apply \
-var project_id=${PROJECT_ID}   \
-var region=${REGION}  \
-var cluster_prefix=${KUBERNETES_CLUSTER_PREFIX}

Cuando se te solicite, escribe yes. Este comando puede tardar varios minutos en completarse y el clúster en mostrar el estado "Listo".

Terraform crea los siguientes recursos:

  • Una red de VPC y una subred privada para los nodos de Kubernetes
  • Un router para acceder a Internet a través de NAT
  • Un clúster de GKE privado en la región us-central1
  • Un grupo de nodos con el escalado automático habilitado (de uno a dos nodos por zona, con un mínimo de un nodo por zona)

El resultado debería ser similar al siguiente:

...
Apply complete! Resources: 14 added, 0 changed, 0 destroyed.
...

Autopilot

En el siguiente diagrama se muestra un clúster de Autopilot de GKE regional privado:

Para desplegar la infraestructura, ejecuta los siguientes comandos:

export GOOGLE_OAUTH_ACCESS_TOKEN=$(gcloud auth print-access-token)
terraform -chdir=terraform/gke-autopilot init
terraform -chdir=terraform/gke-autopilot apply \
-var project_id=${PROJECT_ID} \
-var region=${REGION} \
-var cluster_prefix=${KUBERNETES_CLUSTER_PREFIX}

Cuando se te solicite, escribe yes. Este comando puede tardar varios minutos en completarse y el clúster en mostrar el estado "Listo".

Terraform crea los siguientes recursos:

  • Una red de VPC y una subred privada para los nodos de Kubernetes
  • Un router para acceder a Internet a través de NAT
  • Un clúster de GKE privado en la región us-central1
  • Un ServiceAccount con permiso de registro y monitorización
  • Google Cloud Managed Service para Prometheus para la monitorización de clústeres

El resultado debería ser similar al siguiente:

...
Apply complete! Resources: 12 added, 0 changed, 0 destroyed.
...

Conéctate al clúster

Configura kubectl para que se comunique con el clúster:

gcloud container clusters get-credentials ${KUBERNETES_CLUSTER_PREFIX}-cluster --location ${REGION}

Desplegar el operador CloudNativePG

Despliega CloudNativePG en tu clúster de Kubernetes mediante un gráfico de Helm:

  1. Añade el repositorio del gráfico de Helm del operador CloudNativePG:

    helm repo add cnpg https://cloudnative-pg.github.io/charts
    
  2. Despliega el operador CloudNativePG con la herramienta de línea de comandos Helm:

    helm upgrade --install cnpg \
        --namespace cnpg-system \
        --create-namespace \
        cnpg/cloudnative-pg
    

    El resultado debería ser similar al siguiente:

    Release "cnpg" does not exist. Installing it now.
    NAME: cnpg
    LAST DEPLOYED: Fri Oct 13 13:52:36 2023
    NAMESPACE: cnpg-system
    STATUS: deployed
    REVISION: 1
    TEST SUITE: None
    ...
    

Desplegar Postgres

El siguiente manifiesto describe un clúster de PostgreSQL tal como lo define el recurso personalizado del operador CloudNativePG:

apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: gke-pg-cluster
spec:
  description: "Standard GKE PostgreSQL cluster"
  imageName: ghcr.io/cloudnative-pg/postgresql:16.2
  enableSuperuserAccess: true
  instances: 3
  startDelay: 300
  primaryUpdateStrategy: unsupervised
  postgresql:
    pg_hba:
      - host all all 10.48.0.0/20 md5
  bootstrap:
    initdb:
      database: app
  storage:
    storageClass: premium-rwo
    size: 2Gi
  resources:
    requests:
      memory: "1Gi"
      cpu: "1000m"
    limits:
      memory: "1Gi"
      cpu: "1000m"
  affinity:
    enablePodAntiAffinity: true
    tolerations:
    - key: cnpg.io/cluster
      effect: NoSchedule
      value: gke-pg-cluster
      operator: Equal
    additionalPodAffinity:
      preferredDuringSchedulingIgnoredDuringExecution:
      - weight: 1
        podAffinityTerm:
          labelSelector:
            matchExpressions:
            - key: app.component
              operator: In
              values:
              - "pg-cluster"
          topologyKey: topology.kubernetes.io/zone
  monitoring:
    enablePodMonitor: true

Este manifiesto tiene los siguientes campos:

  • spec.instances: número de pods del clúster
  • spec.primaryUpdateStrategy: la estrategia de actualización gradual:
    • Unsupervised: actualiza de forma autónoma el nodo del clúster principal después de los nodos de réplica.
    • Supervised: es necesario realizar una conmutación por error manual en el nodo del clúster principal
  • spec.postgresql: anulaciones de parámetros de archivo postgres.conf, como las reglas de pg_hba, LDAP y los requisitos que deben cumplir las réplicas de sincronización.
  • spec.storage: ajustes relacionados con el almacenamiento, como la clase de almacenamiento, el tamaño del volumen y los ajustes del registro de escritura anticipada.
  • spec.bootstrap: parámetros de la base de datos inicial creada en el clúster, credenciales de usuario y opciones de restauración de la base de datos
  • spec.resources: solicitudes y límites de los pods de clúster
  • spec.affinity: reglas de afinidad y antiafinidad de las cargas de trabajo del clúster

Crear un clúster de Postgres básico

  1. Crea un espacio de nombres:

    kubectl create ns pg-ns
    
  2. Crea el clúster de PostgreSQL con el recurso personalizado:

    kubectl apply -n pg-ns -f manifests/01-basic-cluster/postgreSQL_cluster.yaml
    

    Este comando puede tardar varios minutos en completarse.

  3. Comprueba el estado del clúster:

    kubectl get cluster -n pg-ns --watch
    

    Espera a que el resultado muestre el estado Cluster in healthy state antes de pasar al siguiente paso.

    NAME             AGE     INSTANCES   READY   STATUS                     PRIMARY
    gke-pg-cluster   2m53s   3           3       Cluster in healthy state   gke-pg-cluster-1
    

Inspeccionar los recursos

Confirma que GKE ha creado los recursos del clúster:

kubectl get cluster,pod,svc,pvc,pdb,secret,cm -n pg-ns

El resultado debería ser similar al siguiente:

NAME                                        AGE   INSTANCES   READY   STATUS                     PRIMARY
cluster.postgresql.cnpg.io/gke-pg-cluster   32m   3           3       Cluster in healthy state   gke-pg-cluster-1

NAME                   READY   STATUS    RESTARTS   AGE
pod/gke-pg-cluster-1   1/1     Running   0          31m
pod/gke-pg-cluster-2   1/1     Running   0          30m
pod/gke-pg-cluster-3   1/1     Running   0          29m

NAME                        TYPE        CLUSTER-IP    EXTERNAL-IP   PORT(S)    AGE
service/gke-pg-cluster-r    ClusterIP   10.52.11.24   <none>        5432/TCP   32m
service/gke-pg-cluster-ro   ClusterIP   10.52.9.233   <none>        5432/TCP   32m
service/gke-pg-cluster-rw   ClusterIP   10.52.1.135   <none>        5432/TCP   32m

NAME                                     STATUS   VOLUME                                     CAPACITY   ACCESS MODES   STORAGECLASS   AGE
persistentvolumeclaim/gke-pg-cluster-1   Bound    pvc-bbdd1cdd-bdd9-4e7c-8f8c-1a14a87e5329   2Gi        RWO            standard       32m
persistentvolumeclaim/gke-pg-cluster-2   Bound    pvc-e7a8b4df-6a3e-43ce-beb0-b54ec1d24011   2Gi        RWO            standard       31m
persistentvolumeclaim/gke-pg-cluster-3   Bound    pvc-dac7f931-6ac5-425f-ac61-0cfc55aae72f   2Gi        RWO            standard       30m

NAME                                                MIN AVAILABLE   MAX UNAVAILABLE   ALLOWED DISRUPTIONS   AGE
poddisruptionbudget.policy/gke-pg-cluster           1               N/A               1                     32m
poddisruptionbudget.policy/gke-pg-cluster-primary   1               N/A               0                     32m

NAME                                TYPE                       DATA   AGE
secret/gke-pg-cluster-app           kubernetes.io/basic-auth   3      32m
secret/gke-pg-cluster-ca            Opaque                     2      32m
secret/gke-pg-cluster-replication   kubernetes.io/tls          2      32m
secret/gke-pg-cluster-server        kubernetes.io/tls          2      32m
secret/gke-pg-cluster-superuser     kubernetes.io/basic-auth   3      32m

NAME                                DATA   AGE
configmap/cnpg-default-monitoring   1      32m
configmap/kube-root-ca.crt          1      135m

El operador crea los siguientes recursos:

  • Un recurso personalizado de clúster que representa el clúster de PostgreSQL controlado por el operador
  • Recursos PersistentVolumeClaim con los PersistentVolumes correspondientes
  • Secretos con credenciales de usuario para acceder a la base de datos y a la replicación entre nodos de Postgres.
  • Tres servicios de endpoint de base de datos: <name>-rw, <name>-ro y <name>-r para conectarse al clúster. Para obtener más información, consulta la arquitectura de PostgreSQL.

Autenticarse en PostgreSQL

Puede conectarse a la base de datos PostgreSQL y comprobar el acceso a través de diferentes endpoints de servicio creados por el operador. Para ello, se usa un pod adicional con un cliente PostgreSQL y credenciales de usuario de aplicación sincronizadas montadas como variables de entorno.

  1. Ejecuta el pod del cliente para interactuar con tu clúster de Postgres:

    kubectl apply -n pg-ns -f manifests/02-auth/pg-client.yaml
    
  2. Ejecuta un comando exec en el pod pg-client e inicia sesión en el servicio gke-pg-cluster-rw:

    kubectl wait --for=condition=Ready -n pg-ns pod/pg-client --timeout=300s
    kubectl exec -n pg-ns -i -t pg-client -- /bin/sh
    
  3. Inicia sesión en la base de datos con el gke-pg-cluster-rw Service para establecer una conexión con privilegios de lectura y escritura:

    psql postgresql://$CLIENTUSERNAME:$CLIENTPASSWORD@gke-pg-cluster-rw.pg-ns/app
    

    El terminal empieza con el nombre de tu base de datos:

    app=>
    
  4. Crear una tabla:

    CREATE TABLE travel_agency_clients (
    client VARCHAR ( 50 ) UNIQUE NOT NULL,
    address VARCHAR ( 50 ) UNIQUE NOT NULL,
    phone VARCHAR ( 50 ) UNIQUE NOT NULL);
    
  5. Inserta datos en la tabla:

    INSERT INTO travel_agency_clients(client, address, phone)
    VALUES ('Tom', 'Warsaw', '+55555')
    RETURNING *;
    
  6. Consulta los datos que has creado:

    SELECT * FROM travel_agency_clients ;
    

    El resultado debería ser similar al siguiente:

    client | address |  phone
    --------+---------+---------
    Tom    | Warsaw  | +55555
    (1 row)
    
  7. Cerrar la sesión de la base de datos actual:

    exit
    
  8. Inicia sesión en la base de datos mediante el servicio gke-pg-cluster-ro para verificar el acceso de solo lectura. Este servicio permite consultar datos, pero restringe cualquier operación de escritura:

    psql postgresql://$CLIENTUSERNAME:$CLIENTPASSWORD@gke-pg-cluster-ro.pg-ns/app
    
  9. Intenta insertar datos nuevos:

    INSERT INTO travel_agency_clients(client, address, phone)
    VALUES ('John', 'Paris', '+55555')
    RETURNING *;
    

    El resultado debería ser similar al siguiente:

    ERROR:  cannot execute INSERT in a read-only transaction
    
  10. Intento de lectura de datos:

    SELECT * FROM travel_agency_clients ;
    

    El resultado debería ser similar al siguiente:

    client | address |  phone
    --------+---------+---------
    Tom    | Warsaw  | +55555
    (1 row)
    
  11. Cerrar la sesión de la base de datos actual:

    exit
    
  12. Sal del shell del pod:

    exit
    

Información sobre cómo recoge Prometheus las métricas de tu clúster de PostgreSQL

En el siguiente diagrama se muestra cómo funciona la recogida de métricas de Prometheus:

En el diagrama, un clúster privado de GKE contiene lo siguiente:

  • Un pod de Postgres que recoge métricas en la ruta / y el puerto 9187
  • Recogedores basados en Prometheus que procesan las métricas del pod de PostgreSQL
  • Un recurso PodMonitoring que envía métricas a Cloud Monitoring

Para habilitar la recogida de métricas de tus pods, sigue estos pasos:

  1. Crea el recurso PodMonitoring:

    kubectl apply -f manifests/03-observability/pod-monitoring.yaml -n pg-ns
    
  2. En la Google Cloud consola, ve a la página Explorador de métricas:

    Ir a Explorador de métricas

    El panel de control muestra una tasa de ingestión de métricas distinta de cero.

  3. En Seleccionar una métrica, introduzca Objetivo de Prometheus.

  4. En la sección Categorías de métricas activas, seleccione Cnpg.

Crear un panel de control de métricas

Para visualizar las métricas exportadas, crea un panel de control de métricas.

  1. Despliega un panel de control:

    gcloud --project "${PROJECT_ID}" monitoring dashboards create --config-from-file manifests/03-observability/gcp-pg.json
    
  2. En la Google Cloud consola, ve a la página Paneles de control.

    Ir a Paneles

  3. Selecciona el panel de control Resumen de Prometheus de PostgreSQL.

    Para revisar cómo monitorizan las funciones los paneles de control, puedes reutilizar las acciones de la sección Autenticación de base de datos y aplicar solicitudes de lectura y escritura en la base de datos. Después, consulta la visualización de las métricas recogidas en un panel de control.

  4. Conéctate al pod del cliente:

    kubectl exec -n pg-ns -i -t pg-client -- /bin/sh
    
  5. Insertar datos aleatorios:

    psql postgresql://$CLIENTUSERNAME:$CLIENTPASSWORD@gke-pg-cluster-rw.pg-ns/app -c "CREATE TABLE test (id serial PRIMARY KEY, randomdata VARCHAR ( 50 ) NOT NULL);INSERT INTO test (randomdata) VALUES (generate_series(1, 1000));"
    
  6. Actualiza el panel de control. Los gráficos se actualizan con las métricas actualizadas.

  7. Sal del shell del pod:

    exit