Postgres en Kubernetes con un volumen NFS persistente

Uso de un volumen NFS persistente para la base de datos Postgres desplegada en Kubernetes

En este tutorial aprenderá a desplegar una instancia de base de datos Postgres en un clúster de Kubernetes y almacenar de forma segura sus datos en una ubicación persistente.

La instancia de Postgres tendrá sus datos almacenados en un servidor NFS remoto para preservar los datos almacenados aunque se destruya el clúster de Kubernetes o falle o se destruya el Pod que ejecuta Postgres.

Esta configuración es apropiada exclusivamente para desarrollo y pruebas de aplicaciones en un clúster de desarrollo local de Kubernetes y forma parte del tutorial Creación de un Clúster de Kubernetes usando Vagrant y Ansible.

External NSF Server providing storage for a PostgreSQL server inside a Kubernetes Cluster
Volumen NFS persistente para una base de datos Postgres para desarrollo local en un clúster de Kubernetes

Prerrequisitos:

Volúmenes persistentes en Kubernetes

La mayoría de los contenedores desplegados en Kubernetes se utilizan para aplicaciones sin estado, donde cada instancia es desechable, no almacena datos que sea necesario persistir tras reinicios en el contenedor o que se necesites para las sesiones de sus clientes ya que su almacenamiento es efímero.

Por el contrario, las aplicaciones con estado requieren almacenar datos y que estos sigan estando disponibles tras los reinicios y las diferentes sesiones. Las bases de datos tales como Postgres o MySQL son aplicaciones con estado.

Kubernetes proporciona soporte para muchos tipos de volúmenes dependiendo del proveedor Cloud. Para nuestro clúster de desarrollo local la forma más sencilla y apropiada es configurar un volumen NFS.

Instalación de un servidor NFS

Si no dispone de un servidor NFS, puede crear uno local.

Instale los paquetes necesarios y cree un directorio local para compartir por NFS /mnt/vagrant-kubernetes.

Ejemplo para Ubuntu:

sudo apt install nfs-kernel-server
sudo mkdir -p /mnt/vagrant-kubernetes
sudo mkdir -p /mnt/vagrant-kubernetes/data
sudo chown nobody:nogroup /mnt/vagrant-kubernetes
sudo chmod 777 /mnt/vagrant-kubernetes

Edite el fichero /etc/exports para añadir el directorio local exportado y limite el acceso al rango de IPs que utiliza el clúster de Kubernetes para acceder a recursos externos.

Si está utilizando el ejemplo creación de un Clúster de Kubernetes usando Vagrant y Ansible, utilice el CIDR 192.168.50.0/24 que se corresponde a la red utilizada por las máquinas de VirtualBox para acceder a redes externas.

/mnt/vagrant-kubernetes 192.168.50.0/24(rw,sync,no_subtree_check,insecure,no_root_squash)

Inicie el servidor NFS:

# sudo exportfs -a
# sudo systemctl restart nfs-kernel-server    
# sudo exportfs -v
/mnt/vagrant-kubernetes
	192.168.50.0/24(rw,wdelay,insecure,no_root_squash,no_subtree_check,sec=sys,rw,insecure,no_root_squash,no_all_squash)

Fichero de despliegue en Kubernetes

El despliegue de la base de datos Postgres está compuesto por los siguientes objetos de Kubernetes:

  • ConfigMap: almacena configuración común para el servidor de base de datos Postgres
  • PersistentVolume: crea un cliente NFS que conecta al servidor NFS y hace disponible la carpeta NFS compartida dentro de Kubernetes para resolver solicitudes de volúmenes (volume claims)
  • PersistentVolumeClaim: define las características del volumen que se necesita, Kubernetes intentará resolver la necesidad usando alguno de los volúmenes persistentes cuya configuración es adecuada a la solicitud.
  • Deployment: despliegue de un contendedor docker de Postgres utilizando el ConfigMap que redefine la configuración de Postgres vía variables de entorno y monta el volumen persistente dentro del contenedor, reemplazando el directorio Data de PostgreSQL.
  • Service (NodePort): publica el servidor Postgres fuera del clúster de Kubernetes usando un NodePort.

Al final del tutorial se muestra el contenido completo del fichero de despliegue.

ConfigMap para Postgres sobre Kubernetes

El config map almacena pares clave-valor y los hace disponibles para su uso en todos los nodos del clúster de Kubernetes.

Usaremos este recurso para fijar los valores de algunas variables de entorno del contenedor que ejecuta Postgres:

  • POSTGRES_DB: db (la base de datos que será creada al iniciar Postgres si no existe)
  • POSTGRES_USER: user (el nombre del usuario administrador)
  • POSTGRES_PASSWORD: pass (contraseña del usuario administrador)
  • PGDATA: /var/lib/postgresql/data/k8s (la ubicación de los datos tras la inicialización de la base de datos. Usaremos el subdirectorio k8s para esta instancia)

Definición de ConfigMap en el fichero postgresql.yaml

apiVersion: v1
kind: ConfigMap
metadata:
  name: psql-itwl-dev-01-cm
data:
  POSTGRES_DB: db
  POSTGRES_USER: user
  POSTGRES_PASSWORD: pass
  PGDATA: /var/lib/postgresql/data/k8s

Postgres Kubernetes PersistentVolume

Define un NFS PersistentVolume ubicado en:

  • Servidor: 192.168.50.1 (La IP asignada por VirtualBox a nuestra máquina)
  • Ruta: /mnt/vagrant-kubernetes/data (el subdirectorio «data» dentro de la carpeta compartida)

El volumen tiene la siguiente configuración:

  • Etiquetas:
    • app: psql
    • ver: itwl-dev-01-pv
  • Capacidad: podrá almacenar hasta 1 Giga byte.
  • Modo de Acceso: permite múltiples clientes escribiendo y leyendo a la vez.
  • Política de retención: tras el uso, el volumen no es destruido

Definición de PersistentVolume en el fichero postgresql.yaml

apiVersion: v1
kind: PersistentVolume
metadata:
  name: psql-itwl-dev-01-pv
  labels: #Labels 
    app: psql
    ver: itwl-dev-01-pv 
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    server: 192.168.50.1
    path: "/mnt/vagrant-kubernetes/data"

Postgres Kubernetes PersistentVolumeClaim

El PersistentVolumeClaim es el objeto que se asigna al despliegue. El PersistentVolumeClaim define cómo debe ser el volumen, Kubernetes intentará encontrar un PersistentVolume que satisfaga los requisitos.

El PersistentVolumeClaim tiene las siguientes características:

  • Etiquetas:
    • app: psql
    • ver: itwl-dev-01-pv
  • Modo de acceso: ReadWriteMany
  • Capacidad: 1 Giga byte

Definición de PersistentVolumeClaim en el fichero postgresql.yaml

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: psql-itwl-dev-01-pvc
spec:
  selector:
    matchLabels:  #Select a volume with this labels
      app: psql
      ver: itwl-dev-01-pv
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi

Despliegue de Postgres es Kubernetes

Se trata de un despliegue habitual con las siguientes características:

Se despliega una sola réplica. No se requiere Istio, lo que se indica mediante al anotación sidecar.istio.io/inject false. Vea Installing Istio in Kubernetes para saber más sobre Istio.

El contenedor utiliza la imagen Docker más reciente disponible en el repositorio público de Docker (https://hub.docker.com/) y fija:

  • Exportar puerto del contenedor: 5432
  • Fijar las variables de entorno del contenedor usando el config map de Kubernetes psql-itwl-dev-01-cm
  • Reemplazar el directorio /var/lib/postgresql/data del contenedor de Postgres con un volumen llamado pgdatavol
  • Define el volumen pgdatavol como una instancia del persistentVolumeClaim psql-itwl-dev-01-pvc que ha sido definido anteriormente

Definición de Deployment en el fichero postgresql.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: psql-itwl-dev-01
  labels: 
    app: psql 
    ver: itwl-dev-01
spec:
  replicas: 1
  selector:
    matchLabels: 
      app: psql
      ver: itwl-dev-01
  template: #For the creation of the pod      
    metadata:
      labels:
        app: psql
        ver: itwl-dev-01
      annotations:
        sidecar.istio.io/inject: "false"        
    spec:
      containers:
        - name: postgres
          image: postgres:latest
          imagePullPolicy: "IfNotPresent"
          ports:
            - containerPort: 5432 
          envFrom:
            - configMapRef:
                name: psql-itwl-dev-01-cm          
          volumeMounts:
            - mountPath: /var/lib/postgresql/data
              name: pgdatavol
      volumes:
        - name: pgdatavol
          persistentVolumeClaim:
            claimName: psql-itwl-dev-01-pvc

Postgres Kubernetes NodePort Service

Dado que nuestro clúster Kubernetes no está en un proveedor Cloud que disponga de un balanceador de carga, usaremos un NodePort para acceder a los puertos publicados por el contenedor (puerto de base de datos).

El NodePort publicará Postgres en el puerto 30100 de cada nodo y maestro de Kubernetes:

  • 192.168.50.11:30100
  • 192.168.50.12:30100
  • 192.168.50.13:30100

Definición de Service en el fichero postgresql.yaml

apiVersion: v1
kind: Service
metadata:
  name: postgres-service-np
spec:
  type: NodePort
  selector:
    app: psql
  ports:
    - name: psql
      port: 5432        # Cluster IP http://10.109.199.234:port (docker exposed port)
      nodePort: 30100   # (EXTERNAL-IP VirtualBox IPs) 192.168.50.11:nodePort 192.168.50.12:nodePort 192.168.50.13:nodePort
      protocol: TCP      

Despliegue completo de Postgres in Kubernetes

Fichero postgresql.yaml con todos los recursos que serán desplegados en Kubernetes:

apiVersion: v1
kind: ConfigMap
metadata:
  name: psql-itwl-dev-01-cm
data:
  POSTGRES_DB: db
  POSTGRES_USER: user
  POSTGRES_PASSWORD: pass
  PGDATA: /var/lib/postgresql/data/k8s

---
apiVersion: v1
kind: PersistentVolume
metadata:
  name: psql-itwl-dev-01-pv
  labels: #Labels 
    app: psql
    ver: itwl-dev-01-pv 
spec:
  capacity:
    storage: 1Gi
  accessModes:
    - ReadWriteMany
  persistentVolumeReclaimPolicy: Retain
  nfs:
    server: 192.168.50.1
    path: "/mnt/vagrant-kubernetes/data"
          
---

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: psql-itwl-dev-01-pvc
spec:
  selector:
    matchLabels:  #Select a volume with this labels
      app: psql
      ver: itwl-dev-01-pv
  accessModes:
    - ReadWriteMany
  resources:
    requests:
      storage: 1Gi
---

apiVersion: apps/v1
kind: Deployment
metadata:
  name: psql-itwl-dev-01
  labels: 
    app: psql 
    ver: itwl-dev-01
spec:
  replicas: 1
  selector:
    matchLabels:  
      app: psql
      ver: itwl-dev-01
  template: #For the creation of the pod      
    metadata:
      labels:
        app: psql
        ver: itwl-dev-01
      annotations:
        sidecar.istio.io/inject: "false"        
    spec:
      containers:
        - name: postgres
          image: postgres:latest
          imagePullPolicy: "IfNotPresent"
          ports:
            - containerPort: 5432 
          envFrom:
            - configMapRef:
                name: psql-itwl-dev-01-cm          
          volumeMounts:
            - mountPath: /var/lib/postgresql/data
              name: pgdatavol
      volumes:
        - name: pgdatavol
          persistentVolumeClaim:
            claimName: psql-itwl-dev-01-pvc

---

apiVersion: v1
kind: Service
metadata:
  name: postgres-service-np
spec:
  type: NodePort
  selector:
    app: psql
  ports:
    - name: psql
      port: 5432        # Cluster IP http://10.109.199.234:port (docker exposed port)
      nodePort: 30100   # (EXTERNAL-IP VirtualBox IPs) http://192.168.50.11:nodePort/ http://192.168.50.12:nodePort/ http://192.168.50.13:nodePort/
      protocol: TCP      

Creación de los recursos en Kubernetes

$ kubectl apply -f postgresql.yaml 
configmap/psql-itwl-dev-01-cm created
persistentvolume/psql-itwl-dev-01-pv created
persistentvolumeclaim/psql-itwl-dev-01-pvc created
deployment.apps/psql-itwl-dev-01 created
service/postgres-service-np created

Comprobación de Kubernetes

Compruebe que todos los recursos han sido creados:

$ kubectl get all
NAME                                    READY   STATUS    RESTARTS   AGE
pod/psql-itwl-dev-01-594c7468c7-p9k9l   1/1     Running   0          6s

NAME                          TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)          AGE
service/kubernetes            ClusterIP   10.96.0.1       <none>        443/TCP          4d1h
service/postgres-service-np   NodePort    10.105.135.29   <none>        5432:30100/TCP   6s

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/psql-itwl-dev-01   1/1     1            1           6s

NAME                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/psql-itwl-dev-01-594c7468c7   1         1         1       6s

Revise el log de Postgres en Kubernetes

Utilizando el comando kubectl logs se accede a los logs del Pod que ejecuta Postgres:

$ kubectl logs 
$ kubectl logs pod/psql-itwl-dev-01-594c7468c7-p9k9l -f
The files belonging to this database system will be owned by user "postgres".
This user must also own the server process.

The database cluster will be initialized with locale "en_US.utf8".
The default database encoding has accordingly been set to "UTF8".
The default text search configuration will be set to "english".

Data page checksums are disabled.

fixing permissions on existing directory /var/lib/postgresql/data/k8s ... ok
creating subdirectories ... ok
selecting default max_connections ... 100
selecting default shared_buffers ... 128MB
selecting dynamic shared memory implementation ... posix
creating configuration files ... ok
running bootstrap script ... ok
performing post-bootstrap initialization ... ok

WARNING: enabling "trust" authentication for local connections
You can change this by editing pg_hba.conf or using the option -A, or
--auth-local and --auth-host, the next time you run initdb.
syncing data to disk ... ok

Success. You can now start the database server using:

    pg_ctl -D /var/lib/postgresql/data/k8s -l logfile start

waiting for server to start....2019-06-10 17:59:41.009 UTC [42] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2019-06-10 17:59:41.104 UTC [43] LOG:  database system was shut down at 2019-06-10 17:59:40 UTC
2019-06-10 17:59:41.141 UTC [42] LOG:  database system is ready to accept connections
 done
server started
CREATE DATABASE


/usr/local/bin/docker-entrypoint.sh: ignoring /docker-entrypoint-initdb.d/*

2019-06-10 17:59:48.590 UTC [42] LOG:  received fast shutdown request
waiting for server to shut down...2019-06-10 17:59:48.597 UTC [42] LOG:  aborting any active transactions
.2019-06-10 17:59:48.603 UTC [42] LOG:  background worker "logical replication launcher" (PID 49) exited with exit code 1
2019-06-10 17:59:48.603 UTC [44] LOG:  shutting down
2019-06-10 17:59:48.715 UTC [42] LOG:  database system is shut down
 done
server stopped

PostgreSQL init process complete; ready for start up.

2019-06-10 17:59:48.854 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
2019-06-10 17:59:48.854 UTC [1] LOG:  listening on IPv6 address "::", port 5432
2019-06-10 17:59:48.865 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2019-06-10 17:59:48.954 UTC [60] LOG:  database system was shut down at 2019-06-10 17:59:48 UTC
2019-06-10 17:59:48.997 UTC [1] LOG:  database system is ready to accept connections

Pruebe la base de datos Postgres sobre Kubernetes

Utilizando el cliente Postgres psql (apt-get install -y postgresql-client) se accede a la base de datos, la contraseña es pass.

# psql db -h 192.168.50.11 -p 30100 -U user
Password for user user: 
psql (10.8 (Ubuntu 10.8-0ubuntu0.18.10.1), server 11.3 (Debian 11.3-1.pgdg90+1))
WARNING: psql major version 10, server major version 11.
         Some psql features might not work.
Type "help" for help.

db=#

Cree una Table e inserte datos

db=# CREATE TABLE COLOR(
db(#    ID SERIAL PRIMARY KEY   NOT NULL,
db(#    NAME           TEXT     NOT NULL UNIQUE,
db(#    RED            SMALLINT NOT NULL,
db(#    GREEN          SMALLINT NOT NULL,
db(#    BLUE           SMALLINT NOT NULL
db(# );
CREATE TABLE
db=# 
db=# INSERT INTO COLOR (NAME,RED,GREEN,BLUE) VALUES('GREEN',0,128,0);
INSERT 0 1
db=# INSERT INTO COLOR (NAME,RED,GREEN,BLUE) VALUES('RED',255,0,0);
INSERT 0 1
db=# INSERT INTO COLOR (NAME,RED,GREEN,BLUE) VALUES('BLUE',0,0,255);
INSERT 0 1
db=# INSERT INTO COLOR (NAME,RED,GREEN,BLUE) VALUES('WHITE',255,255,255);
INSERT 0 1
db=# INSERT INTO COLOR (NAME,RED,GREEN,BLUE) VALUES('YELLOW',255,255,0);
INSERT 0 1
db=# INSERT INTO COLOR (NAME,RED,GREEN,BLUE) VALUES('LIME',0,255,0);
INSERT 0 1
db=# INSERT INTO COLOR (NAME,RED,GREEN,BLUE) VALUES('BLACK',255,255,255);
INSERT 0 1
db=# INSERT INTO COLOR (NAME,RED,GREEN,BLUE) VALUES('GRAY',128,128,128);
INSERT 0 1
db=# 
db=# SELECT * FROM COLOR;
 id |  name  | red | green | blue 
----+--------+-----+-------+------
  1 | GREEN  |   0 |   128 |    0
  2 | RED    | 255 |     0 |    0
  3 | BLUE   |   0 |     0 |  255
  4 | WHITE  | 255 |   255 |  255
  5 | YELLOW | 255 |   255 |    0
  6 | LIME   |   0 |   255 |    0
  7 | BLACK  | 255 |   255 |  255
  8 | GRAY   | 128 |   128 |  128
(8 rows)

db=# 

Elimine y recree la instancia de Postgres

Ahora puede destruir la instancia de Postgres en Kubernetes y volverla a crear, comprobará que los datos almacenados se han mantenido intactos.

$ kubectl delete -f postgresql.yaml 
configmap "psql-itwl-dev-01-cm" deleted
persistentvolume "psql-itwl-dev-01-pv" deleted
persistentvolumeclaim "psql-itwl-dev-01-pvc" deleted
deployment.apps "psql-itwl-dev-01" deleted
service "postgres-service-np" deleted

$ kubectl apply -f postgresql.yaml 
configmap/psql-itwl-dev-01-cm created
persistentvolume/psql-itwl-dev-01-pv created
persistentvolumeclaim/psql-itwl-dev-01-pvc created
deployment.apps/psql-itwl-dev-01 created
service/postgres-service-np created

$ kubectl get all
NAME                                    READY   STATUS              RESTARTS   AGE
pod/psql-itwl-dev-01-594c7468c7-7hrzt   0/1     ContainerCreating   0          4s

NAME                          TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
service/kubernetes            ClusterIP   10.96.0.1      <none>        443/TCP          4d1h
service/postgres-service-np   NodePort    10.100.67.43   <none>        5432:30100/TCP   3s

NAME                               READY   UP-TO-DATE   AVAILABLE   AGE
deployment.apps/psql-itwl-dev-01   0/1     1            0           4s

NAME                                          DESIRED   CURRENT   READY   AGE
replicaset.apps/psql-itwl-dev-01-594c7468c7   1         1         0       4s

$ kubectl logs pod/psql-itwl-dev-01-594c7468c7-7hrzt -f
2019-06-10 18:05:51.456 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
2019-06-10 18:05:51.457 UTC [1] LOG:  listening on IPv6 address "::", port 5432
2019-06-10 18:05:51.466 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2019-06-10 18:05:51.547 UTC [22] LOG:  database system was interrupted; last known up at 2019-06-10 18:04:54 UTC
2019-06-10 18:05:53.043 UTC [22] LOG:  database system was not properly shut down; automatic recovery in progress
2019-06-10 18:05:53.055 UTC [22] LOG:  redo starts at 0/1676B10
2019-06-10 18:05:53.055 UTC [22] LOG:  invalid record length at 0/1676BB8: wanted 24, got 0
2019-06-10 18:05:53.055 UTC [22] LOG:  redo done at 0/1676B48
2019-06-10 18:05:53.087 UTC [1] LOG:  database system is ready to accept connections

Utilice el cliente psql para comprobar que los datos se han mantenido:

# psql db -h 192.168.50.11 -p 30100 -U user
Password for user user: 
psql (10.8 (Ubuntu 10.8-0ubuntu0.18.10.1), server 11.3 (Debian 11.3-1.pgdg90+1))
WARNING: psql major version 10, server major version 11.
         Some psql features might not work.
Type "help" for help.

db=# select * from COLOR;
 id |  name  | red | green | blue 
----+--------+-----+-------+------
  1 | GREEN  |   0 |   128 |    0
  2 | RED    | 255 |     0 |    0
  3 | BLUE   |   0 |     0 |  255
  4 | WHITE  | 255 |   255 |  255
  5 | YELLOW | 255 |   255 |    0
  6 | LIME   |   0 |   255 |    0
  7 | BLACK  | 255 |   255 |  255
  8 | GRAY   | 128 |   128 |  128
(8 rows)

db=#
Postgres en Kubernetes con un volumen NFS persistente

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *