Implementa PostgreSQL en GKE con CloudNativePG


En la guía, se muestra cómo implementar 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 que garantiza un rendimiento estable del cliente. Ofrece una variedad de funciones, como la replicación, la recuperación de un momento determinado, las funciones de seguridad y la extensibles. PostgreSQL es compatible con los sistemas operativos más importantes y cumple por completo con los estándares ACID (atomicidad, coherencia, aislamiento y durabilidad).

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

Ventajas

CloudNativePG es un operador de código abierto desarrollado por EBD con una licencia Apache 2. Agrega las siguientes características a la implementación de PostgreSQL:

  • Una forma declarativa y nativa de Kubernetes de administrar y configurar clústeres de PostgreSQL y PostgreSQL
  • Administración de copias de seguridad mediante instantáneas de volumen o Cloud Storage
  • Conexión TLS encriptada en tránsito, la capacidad de usar tu propia autoridad certificadora y su integración con el Administrador de certificados para la emisión y rotación automatizada de certificados TLS
  • Actualizaciones progresivas para actualizaciones secundarias de PostgreSQL
  • Uso del servidor de la API de Kubernetes para mantener el estado del clúster de PostgreSQL y conmutaciones por error para lograr una alta disponibilidad sin necesidad de herramientas adicionales
  • Una configuración integrada del exportador de Prometheus a través de métricas definidas por el usuario escritas en SQL

Objetivos

  • Planificar y, además, implementar la infraestructura de GKE para Postgres
  • Implementa y configura el operador de CloudNativePG Postgres con Helm
  • Implementa un clúster de PostgreSQL
  • Configura la autenticación y observabilidad de PostgreSQL

Arquitectura de implementación

PostgreSQL tiene varias opciones de implementación desde un servidor de base de datos independiente hasta un clúster con alta disponibilidad replicado. En este instructivo, nos enfocamos en la implementación de clúster con alta disponibilidad en GKE.

En esta implementación, las cargas de trabajo del clúster de PostgreSQL se distribuyen en varias zonas de disponibilidad dentro del clúster de GKE regional, lo que garantiza una alta disponibilidad y redundancia. Para obtener más información, consulta Clústeres regionales.

En el siguiente diagrama, se muestra un clúster de Postgres que se ejecuta en varios nodos y zonas en un clúster de GKE:

Clúster de Postgres en GKE

  • La configuración predeterminada incluye un servidor principal de PostgreSQL y dos servidores de copia de seguridad listos para actuar en caso de que el servidor principal falle, lo que garantiza una disponibilidad continua de la base de datos.

  • Los recursos del operador de CloudNativePG usan un espacio de nombres separado del clúster de GKE para mejorar el aislamiento de recursos y el enfoque de microservicios recomendado de una base de datos por clúster de PostgreSQL. La base de datos y su usuario correspondiente (usuario de la app) se definen en el recurso personalizado de Kubernetes que representa el clúster.

  • El almacenamiento es un componente fundamental cuando se analiza las bases de datos. El almacenamiento debe tener un rendimiento eficiente, garantizar la disponibilidad continua y garantizar la coherencia de los datos. Por estos motivos, recomendamos la clase de almacenamiento premium-rwo, que se basa en discos SSD. El operador CloudNativePG crea PersistentVolumeClaims de forma automática según sea necesario cuando configura Pods para el clúster de PostgreSQL.

Costos

En este documento, usarás los siguientes componentes facturables de Google Cloud:

Para generar una estimación de costos en función del uso previsto, usa la calculadora de precios. Es posible que los usuarios nuevos de Google Cloud califiquen para obtener una prueba gratuita.

Cuando finalices las tareas que se describen en este documento, puedes borrar los recursos que creaste para evitar que continúe la facturación. Para obtener más información, consulta Cómo realizar una limpieza.

Antes de comenzar

Cloud Shell tiene preinstalado el software que necesitas para este instructivo, incluido kubectl, gcloud CLI, Helm y Terraform. Si no usas Cloud Shell, debes instalar gcloud CLI.

  1. Accede a tu cuenta de Google Cloud. Si eres nuevo en Google Cloud, crea una cuenta para evaluar el rendimiento de nuestros productos en situaciones reales. Los clientes nuevos también obtienen $300 en créditos gratuitos para ejecutar, probar y, además, implementar cargas de trabajo.
  2. Instala Google Cloud CLI.
  3. Para inicializar la CLI de gcloud, ejecuta el siguiente comando:

    gcloud init
  4. Crea o selecciona un proyecto de Google Cloud.

    • Crea un proyecto de Google Cloud:

      gcloud projects create PROJECT_ID

      Reemplaza PROJECT_ID por un nombre para el proyecto de Google Cloud que estás creando.

    • Selecciona el proyecto de Google Cloud que creaste:

      gcloud config set project PROJECT_ID

      Reemplaza PROJECT_ID por el nombre del proyecto de Google Cloud.

  5. Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud.

  6. Habilita las APIs de Compute Engine, IAM, GKE, Resource Manager:

    gcloud services enable compute.googleapis.com iam.googleapis.com container.googleapis.com cloudresourcemanager.googleapis.com
  7. Instala Google Cloud CLI.
  8. Para inicializar la CLI de gcloud, ejecuta el siguiente comando:

    gcloud init
  9. Crea o selecciona un proyecto de Google Cloud.

    • Crea un proyecto de Google Cloud:

      gcloud projects create PROJECT_ID

      Reemplaza PROJECT_ID por un nombre para el proyecto de Google Cloud que estás creando.

    • Selecciona el proyecto de Google Cloud que creaste:

      gcloud config set project PROJECT_ID

      Reemplaza PROJECT_ID por el nombre del proyecto de Google Cloud.

  10. Asegúrate de que la facturación esté habilitada para tu proyecto de Google Cloud.

  11. Habilita las APIs de Compute Engine, IAM, GKE, Resource Manager:

    gcloud services enable compute.googleapis.com iam.googleapis.com container.googleapis.com cloudresourcemanager.googleapis.com
  12. Otorga roles a tu Cuenta de Google. Ejecuta el siguiente comando una vez para cada uno de los siguientes roles de IAM: roles/compute.securityAdmin, roles/compute.viewer, roles/container.clusterAdmin, roles/container.admin, roles/iam.serviceAccountAdmin, roles/iam.serviceAccountUser

    gcloud projects add-iam-policy-binding PROJECT_ID --member="user:EMAIL_ADDRESS" --role=ROLE
    • Reemplaza PROJECT_ID con el ID del proyecto.
    • Reemplaza EMAIL_ADDRESS por tu dirección de correo electrónico.
    • Reemplaza ROLE por cada rol individual.

Configura tu entorno

Para configurar tu entorno, sigue estos pasos:

  1. Establece las variables de entorno:

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

    Reemplaza PROJECT_ID por el ID del proyecto de Google Cloud.

  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
    

Crea la infraestructura del clúster

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

Puedes instalar el operador mediante un clúster de Standard o Autopilot.

Estándar

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

Para implementar 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. Es posible que este comando tarde varios minutos en completarse y que el clúster muestre un estado de preparación.

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 ajuste de escala automático habilitado (de uno a dos nodos por zona y un nodo por zona como mínimo)

El resultado es similar al siguiente:

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

Autopilot

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

Para implementar 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. Es posible que este comando tarde varios minutos en completarse y que el clúster muestre un estado de preparación.

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 supervisión
  • Google Cloud Managed Service para Prometheus para la supervisión de clústeres

El resultado es similar al siguiente:

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

Conéctate al clúster

Configura kubectl para comunicarse con el clúster:

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

Implementa el operador CloudNativePG

Implementa CloudNativePG en tu clúster de Kubernetes con un gráfico de Helm:

  1. Agrega el repositorio de gráficos de Helm para operadores de CloudNativePG:

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

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

    El resultado es 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
    ...
    

Implementa Postgres

En el siguiente manifiesto, se describe un clúster de PostgreSQL según 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: la cantidad de Pods del clúster
  • spec.primaryUpdateStrategy: la estrategia de actualización progresiva:
    • Unsupervised: actualiza de forma autónoma el nodo del clúster principal después de los nodos de réplica
    • Supervised: se requiere un cambio manual para el nodo del clúster principal
  • spec.postgresql: anulaciones de parámetros de archivo postgres.conf, como las reglas de pg-hba, LDAP y requisitos para que se cumplan las réplicas de sincronización.
  • spec.storage: la configuración relacionada con el almacenamiento, como la clase de almacenamiento, el tamaño del volumen y la configuración de registro de escritura por adelantado.
  • spec.bootstrap: parámetros de la base de datos inicial creada en el clúster, credenciales de usuario y opciones de restablecimiento de la base de datos
  • spec.resources: solicitudes y límites para Pods del clúster
  • spec.affinity: reglas de afinidad y antiafinidad de las cargas de trabajo del clúster

Crea un clúster básico de Postgres

  1. Crea un espacio de nombres:

    kubectl create ns pg-ns
    
  2. Crea el clúster de PostgreSQL mediante 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. Verifica el estado del clúster:

    kubectl get cluster -n pg-ns --watch
    

    Espera a que el resultado muestre un estado de Cluster in healthy state antes de continuar con el siguiente paso.

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

Inspecciona los recursos

Confirma que GKE creó los recursos para el clúster:

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

El resultado es 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 que controla el operador
  • Recursos PersistentVolumeClaim con los volúmenes persistentes correspondientes
  • Secrets con credenciales de usuario para acceder a la base de datos y replicación entre los nodos de Postgres.
  • Tres servicios de extremo de base de datos: <name>-rw, <name>-ro y <name>-r para conectarse al clúster. Para obtener más información, consulta Arquitectura de PostgreSQL.

Autentica en Postgres

Puedes conectarte a la base de datos de PostgreSQL y verificar el acceso a través de diferentes extremos de servicio creados por el operador. Para hacerlo, usa un Pod adicional con un cliente PostgreSQL y credenciales de usuario de la aplicación sincronizadas activadas 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 y accede al Service 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. Accede a la base de datos con el servicio gke-pg-cluster-rw para establecer una conexión con privilegios de lectura y escritura:

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

    La terminal comienza con el nombre de la 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. Visualiza los datos que creaste:

    SELECT * FROM travel_agency_clients ;
    

    El resultado es similar al siguiente:

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

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

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

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

    El resultado es similar al siguiente:

    ERROR:  cannot execute INSERT in a read-only transaction
    
  10. Intenta leer los datos:

    SELECT * FROM travel_agency_clients ;
    

    El resultado es similar al siguiente:

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

    exit
    
  12. Sal de la shell del Pod:

    exit
    

Comprende cómo Prometheus recopila métricas para tu clúster de Postgres

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

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

  • Un Pod de Postgres que recopila métricas en la ruta de acceso / y el puerto 9187
  • Recopiladores basados en Prometheus que procesan las métricas del Pod de Postgres
  • Un recurso PodMonitoring que envía métricas a Cloud Monitoring

Para permitir que las métricas se recopilen de tus Pods, realiza los siguientes pasos:

  1. Crea el recurso PodMonitoring:

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

    Dirígete al Explorador de métricas

    En el panel, se muestra una tasa de transferencia de métricas distinta de cero.

  3. En Selecciona una métrica, ingresa Prometheus Target.

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

Crea un panel de métricas

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

  1. Implementa un panel:

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

    Ir a Paneles

  3. Selecciona el panel PostgresQL Prometheus Overview.

    Para revisar cómo los paneles supervisan las funciones, puedes volver a usar las acciones de la sección Autenticación de base de datos y aplicar solicitudes de lectura y escritura en la base de datos y, luego, revisar la visualización de métricas recopiladas en un panel.

  4. Conéctate al Pod cliente:

    kubectl exec -n pg-ns -i -t pg-client -- /bin/sh
    
  5. Inserta 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. Los grafos se actualizan con métricas actualizadas.

  7. Sal de la shell del Pod:

    exit
    

Limpia

Borra el proyecto

    Borra un proyecto de Google Cloud:

    gcloud projects delete PROJECT_ID

Borra los recursos individuales

  1. Configurar variables de entorno

    export PROJECT_ID=${PROJECT_ID}
    export KUBERNETES_CLUSTER_PREFIX=postgres
    export REGION=us-central1
    
  2. Ejecuta el comando terraform destroy:

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

    Reemplaza FOLDER por gke-autopilot o gke-standard.

    Cuando se te solicite, escribe yes.

  3. Busca todos los discos no conectados:

    export disk_list=$(gcloud compute disks list --filter="-users:* AND labels.name=${KUBERNETES_CLUSTER_PREFIX}-cluster" --format "value[separator=|](name,zone)")
    
  4. Borra los discos:

    for i in $disk_list; do
      disk_name=$(echo $i| cut -d'|' -f1)
      disk_zone=$(echo $i| cut -d'|' -f2|sed 's|.*/||')
      echo "Deleting $disk_name"
      gcloud compute disks delete $disk_name --zone $disk_zone --quiet
    done
    

¿Qué sigue?

  • Explora arquitecturas de referencia, diagramas y prácticas recomendadas sobre Google Cloud. Consulta nuestro Cloud Architecture Center.