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.

Prerrequisitos:
- Un clúster de Kubernetes
- Un equipo ejecutando Linux para crear un servidor NFS
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=#