5

Containeriser PostgreSQL avec Docker !

twitterlinkedinmail

Bonjour,

Depuis quelques années maintenant, nous avons vu arriver dans le milieu de l’informatique, le phénomène de “containerisation” !

C’est quoi au juste ! Et qu’est ce qui change par rapport à la virtualisation ?

On va dire que c’est un peu, comparativement, des concepts d’architecture qui se différencient comme peuvent l’être le Paas et le Iaas.  Toute la question est qu’est ce qui est inclut dans chaque couche !

 

Principes

 

Une VM englobe un OS, ses librairies (DLL windows, rpm/pkg linux), avec son schéma d’architecture stockage et les applications installées et embarquées.
Docker, avec la containerisation, ne prend en charge que l’environnement applicatif. En quelque sorte, avec Docker, vous n’avez besoin que d’une solution logiciel avec toutes les bibliothèques nécessaires, utiles à son bon fonctionnement, qui seront inclus dans le container que vous déploierez à ce moment précis.

Rien de mieux que des schémas pour visualiser concrètement le concept. Nous prendrons comme exemple, ces 2 croquis issus du site http://www.docker.com.

Voici le schéma d’architecture d’une virtualisation classique comme nous la connaissons sur une infra HyperV, par exemple :

L’hyperviseur prend en charge 1 ou plusieurs VM qui elle(s) même(s) comporte(nt) des couches OS et applicatives. Ainsi “n” OS seront déployés en fonction des “n” VM gérées par l’hyperviseur.

 

Avec Docker, nous avons le schéma suivant :

Nous voyons que nous n’avons qu’un seul OS sur l’hôte. Celui ci comporte un processus système appelé “Docker Engine” qui se chargera de communiquer avec les containers. Chaque container docker contiendra son application embarquée avec les dépendances nécessaires (compilateurs C, librairies, packages auxiliaires …..).
Mais l’OS de la machine hôte, de son coté, pourra ne contenir qu’un ensemble de packages et bibliothèques natives et donc, ne pas embarquer différents packages supplémentaires.

Les avantages :

Les principaux avantages que l’on pourra retenir vis à vis de Docker et de la containerisation :

  • 1 seul OS, celui ci est capable de gérer l’ensemble des containers fonctionnant sur la machine.
  • la simplicité, l’utilisateur peut déployer un ensemble de containers déjà pré-packagés ou en créer un via un “dockerfile”
  • Réduire le temps d’installation, pas d’OS dans l’image d’un container.
  • La portabilité. Des backup/restauration d’images de containers très rapides avec seulement l’applicatif embarquée dans une image.
  • Scalabilité avec possibilité de créer de multiples environnements identiques avec 1 seule image (très pratique pour créer un environnement UAT à partir d’un environnement de production)
  • Docker garantit maintenant un fonctionnement sur une grande partie des OS (Linux, Windows, Mac).
  • Linux gère cet ensemble de containers via le système LXC (LinuX Container) qui permet une parfaite isolation de chaque container au sein du même OS.

 

Quelques inconvénients :

  • A l’origine, Docker ne fonctionnait que sur Linux. Il fallait monter un environnement virtualisé type “boot2docker” pour fonctionner sur Mac ou Windows. A présent, Windows sait gérer Docker avec les dernières version de Windows.
  • Il est nécessaire d’installer la couche Docker Engine sur la machine hôte, ce qui peut être un frein pour certains clients ne maitrisant pas ce type de produit.
  • Toutes les applications ne sont pas “container compliance”, en outre, qu’en est-il d’une base de données Oracle avec 200To de données ? Et pour le patching de type one-off ?
  • Puisque les containers partagent le même OS, ils partagent donc les mêmes composants physiques. Qu’en est-il d’un phénomène de “out of memory” ou d’un “stack overflow” ? Dans un environnement virtualisé, chaque VM comporte son OS qui gère sa mémoire propre, avec Docker les applications se “partagent” également le même noyau.
  • En cas de virus sur la machine hôte, c’est toute l’architecture Docker et des containers qui est impactée, surtout si le virus s’attaque à une partie vitale de l’OS/!\

 

Installation sous Linux

Nous partons d’une VM de type EC2 AWS. C’est un Red Hat Linux RHEL version 8.2 qui est installé sur celle ci

 

$ cat /etc/*relea*
NAME="Red Hat Enterprise Linux"
VERSION="8.2 (Ootpa)"
ID="rhel"
ID_LIKE="fedora"
VERSION_ID="8.2"
PLATFORM_ID="platform:el8"
PRETTY_NAME="Red Hat Enterprise Linux 8.2 (Ootpa)"
ANSI_COLOR="0;31"
CPE_NAME="cpe:/o:redhat:enterprise_linux:8.2:GA"
HOME_URL="https://www.redhat.com/"
BUG_REPORT_URL="https://bugzilla.redhat.com/"

 

La première étape consiste à installer la couche Docker. Cette opération installera la partie serveur avec le processus “Docker Engine”, c’est un “runtime environnement” qui fera tourner l’ensemble des containers. Puis la partie gestion des différents containers. Ceux ci communiqueront avec l’OS via le “Docker Engine”.

Afin de simplifier au mieux cette installation, nous partirons des dépôts Docker que nous enregistrerons sur la machine via “yum-config”.

# yum-config-manager --add-repo https://download.docker.com/linux/centos/docker-ce.repo
Adding repo from: https://download.docker.com/linux/centos/docker-ce.repo

Une fois chargé dans les sources, nous pourrons lancer l’installation des packages suivants :

# yum install docker-ce docker-ce-cli containerd.io
=================================================================================================================================================================
Package Architecture Version Repository Size
=================================================================================================================================================================
Installing:
containerd.io x86_64 1.4.9-3.1.el8 docker-ce-stable 30 M
docker-ce x86_64 3:20.10.8-3.el8 docker-ce-stable 22 M
docker-ce-cli x86_64 1:20.10.8-3.el8 docker-ce-stable 29 M
Installing dependencies:
container-selinux noarch 2:2.164.1-1.module+el8.4.0+11870+8b6f7018 rhel-8-appstream-rhui-rpms 52 k
docker-ce-rootless-extras x86_64 20.10.8-3.el8 docker-ce-stable 4.6 M
docker-scan-plugin x86_64 0.8.0-3.el8 docker-ce-stable 4.2 M
fuse-common x86_64 3.2.1-12.el8 rhel-8-baseos-rhui-rpms 21 k
fuse-overlayfs x86_64 1.6-1.module+el8.4.0+11822+6cc1e7d7 rhel-8-appstream-rhui-rpms 73 k
fuse3 x86_64 3.2.1-12.el8 rhel-8-baseos-rhui-rpms 50 k
fuse3-libs x86_64 3.2.1-12.el8 rhel-8-baseos-rhui-rpms 94 k
iptables x86_64 1.8.4-10.el8_2.1 rhel-8-baseos-rhui-rpms 581 k
libcgroup x86_64 0.41-19.el8 rhel-8-baseos-rhui-rpms 70 k
libnetfilter_conntrack x86_64 1.0.6-5.el8 rhel-8-baseos-rhui-rpms 65 k
libnfnetlink x86_64 1.0.1-13.el8 rhel-8-baseos-rhui-rpms 33 k
libnftnl x86_64 1.1.5-4.el8 rhel-8-baseos-rhui-rpms 83 k
libslirp x86_64 4.3.1-1.module+el8.4.0+11822+6cc1e7d7 rhel-8-appstream-rhui-rpms 69 k
policycoreutils-python-utils noarch 2.9-9.el8 rhel-8-baseos-rhui-rpms 251 k
slirp4netns x86_64 1.1.8-1.module+el8.4.0+11822+6cc1e7d7 rhel-8-appstream-rhui-rpms 51 k
Enabling module streams:
container-tools rhel8

 

Vérifier la bonne installation de Docker sur la machine :

# docker version
Client: Docker Engine - Community
Version: 20.10.8
API version: 1.41
Go version: go1.16.6
Git commit: 3967b7d
Built: Fri Jul 30 19:53:39 2021
OS/Arch: linux/amd64
Context: default
Experimental: true

 

Puis démarrer le process via systemctl

 

# systemctl start docker
# systemctl status docker
● docker.service - Docker Application Container Engine
Loaded: loaded (/usr/lib/systemd/system/docker.service; disabled; vendor preset: disabled)
Active: active (running) since Wed 2021-09-15 14:05:28 UTC; 3s ago
Docs: https://docs.docker.com
Main PID: 21769 (dockerd)
Tasks: 7
Memory: 78.7M
CGroup: /system.slice/docker.service
└─21769 /usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock

 

Votre système est maintenant prêt à accepter la containerisation

 

Et sur les autres OS

Comme évoqué plus haut, à ses débuts, Docker n’était compatible que sur Linux.
Heureusement, avec le temps, Microsoft en a permis l’utilisation de Docker sur sa plateforme Windows.

Il vous suffira d’installer l’un des outils compatible avec Windows en fonction de votre utilisation.

  • Docker desktop , outil complet permettant de monter des containers Windows ou Linux.
  • VS Code, utilisé pour un environnement de développement au sein d’un container
  • Visual Studio prend également en charge l’environnement Docker avec compatibilité Azure Container Registry.

Les dernières version de Windows 10 et Windows 2016 server embarquent une image Linux (ubuntu par exemple), le déploiement Docker se fera alors comme sur un serveur Linux classique, à peu de chose près.

Pour MacOS :

  • utiliser également Docker Desktop qui permet de monter des containers sur un environnement Mac.
  • boot2docker a été le premier outil à pouvoir gérer Docker sous MacOS, mais celui ci nécessitait de configurer une VM qui comportait un OS Linux + les applications packagés.

 

PostgreSQL sous Docker

Une fois notre installation Docker effectuée, nous pouvons voir que, pour le moment, notre Docker Engine ne fait pas grand chose.
Par d’image container à gérer

# docker images
REPOSITORY TAG IMAGE ID CREATED SIZE

pas de processus container non plus

# docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES

Nous pourrons alors débuter la création de containers.

 

Méthodes

Nous avons plusieurs méthodes nous permettant de construire des containers PostgreSQL.

  • En allant chercher directement l’image la plus récente présente sur les dépôts Docker. Une simple commande “docker pull postgres” suffira à aller chercher cette image de PostgreSQL. En revanche, ce sera la toute dernière version qui sera télécharger. En septembre 2021, il s’agit de PostgreSQL 13.4.

 

  • Il est possible de choisir l’image d’une version antérieure bien évidement.  Pour cela, il faudra aller sur le site du Hub Docker et télécharger la version voulue. En fait il suffit juste de choisir la version PG dans notre commande “docker pull“. Par exemple, pour une version 9.6, c’est “docker pull postgres:9.6“.

 

  • Il sera également possible de configurer soi même son image, grâce à un fichier “dockerfile“. Ce fichier texte nous laissera le choix entre de nombreuses options de configurations pour notre instance PostgreSQL, et nous permettra même de lancer des commandes de création de bases et/ou rôles sur l’instance. Le hub docker propose également des exemples de dockerfile, mais vous pourrez en trouver sur github ou autres sites communautaires.
    Vous pourrez également le “construire” vous même en respectant certaines syntaxes précises.

Installation simple

 

Dans ce premier exemple, nous partirons d’une image pré-packagée directement extraite des dépôts linux Docker.

Rien de plus simple :

# docker run --name postgresql-container_test1 -p 5442:5432 -e POSTGRES_PASSWORD=test2021 -d postgres
Unable to find image 'postgres:latest' locally
latest: Pulling from library/postgres
a330b6cecb98: Pull complete
3b0b899b4747: Pull complete
cc0b2671a552: Pull complete
1a7c7505993a: Pull complete
02cdead79556: Pull complete
0d8fbe9259d6: Pull complete
974e6d476aa7: Pull complete
e9abf0d5d0bc: Pull complete................
..............

 

Et ce que l’on peut voir, avec le warning “Unable to find image ‘postgres:latest’ locally“, c’est que le processus de lancement est capable d’aller directement chercher sur les dépôts Docker une image PostgreSQL qu’il pourra alors utiliser pour démarrer notre container.

L’option -p est utilisé afin de changer le port par défaut. Ici nous utiliserons cette instance sur le 5442. L’option -e permet d’ajouter des variables d’environnement en paramètres, comme ici le password du user ‘postgres”. L’option -d démarre le container en mode ‘background’.

Si le message suivant s’affiche, c’est que l’on a réussi le lancement de notre container

Digest: sha256:97e5e91582e89514277912d4b7c95bceabdede3482e32395bcb40099abd9c506
Status: Downloaded newer image for postgres:latest
c0214f610a796c34b526c54217f36e0e02c1e43753c7eb0363ffd8dc07abceba

La dernière ligne nous donne l’ID crypté de notre processus container. Un “docker ps” permet de voir ce qui tourne

# docker ps -a
CONTAINER ID  IMAGE     COMMAND                 CREATED             STATUS         PORTS                                       NAMES
c0214f610a79  postgres  "docker-entrypoint.s…"  5 minutes ago       Up 5 minutes   0.0.0.0:5442->5432/tcp, :::5442->5432/tcp   postgresql-container_test1

 

Au prochain lancement d’un container PostgreSQL, avec cette même image, il ne sera pas nécessaire d’aller chercher les sources dans les dépôts. Cette image est maintenant enregistrée dans le docker local.

 

# docker images -a
REPOSITORY    TAG      IMAGE ID       CREATED       SIZE
postgres      latest   346c7820a8fb   12 days ago   315MB

Nous pourrons tester une connexion, via psql, sous le user “postgres”.

# su - postgres
$ psql -p 5442 -h 127.0.0.1
Password for user postgres: *******
psql (13.0, server 13.4 (Debian 13.4-1.pgdg100+1))
Type "help" for help.

postgres=# \l
List of databases
Name       | Owner    | Encoding | Collate    | Ctype      | Access privileges
-----------+----------+----------+------------+------------+-----------------------
postgres   | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
template0  | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres +
           |          |          |            |            | postgres=CTc/postgres
template1  | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres +
           |          |          |            |            | postgres=CTc/postgres
(3 rows)

 

On peut voir que le “data_directory” est celui par défaut pour une instance PostgreSQL. Et que notre version est la toute dernière, à savoir la 13.4 compilée sur Debian !

postgres=# show data_directory;
data_directory
--------------------------
/var/lib/postgresql/data
(1 row)

postgres=# select version ();
version
------------------------------------------------------------------------------------------------------------------
PostgreSQL 13.4 (Debian 13.4-1.pgdg100+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 8.3.0-6) 8.3.0, 64-bit
(1 row)

 

Mais sur notre machine, bien entendu, rien dans “/var/lib/postgresql/data” ! Les fichiers PostgreSQL sont dans le container directement.

[postgres@ip-172-44-2-96 ~]$ ls -l /var/lib/postgresql
ls: cannot access '/var/lib/postgresql': No such file or directory

On note la présence d’un nouveau montage de type “overlay”. c’est le driver de stockage générique fonctionnant avec Docker (remplacé par OverlayFS par la suite).

$ df -hT
Filesystem Type Size Used Avail Use% Mounted on
.....
overlay overlay 10G 4.2G 5.8G 42% /var/lib/docker/overlay2/50846bbd5d32162e138f91df136d7877fc33c7f2f17ef3e9c2d2de1704e7a190/merged

 

C’est dans ce montage nommé “/var/lib/docker/overlay2/50846bbd5d32162e138f91df136d7877fc33c7f2f17ef3e9c2d2de1704e7a190/merged’ que sont tous les packages système nécessaires uniquement à notre instance PostgreSQL ainsi que ses fichiers bases de données. Ce montage est présent tant que notre container est démarré.

Créer une image

Cette première étape nous a permis de créer un container PostgreSQL  dans un temps très court.
Il est bien sur possible de gérer soi même ce que l’on souhaite installer dans son container, sa version, les options …..

Pour cela, nous allons créer un “dockerfile“. C’est un fichier texte que l’on utilisera pour la configuration de notre container.
Des exemples existent sur le site Github que nous pourrons utiliser et modifier selon notre convenance.

Nous naviguerons sur le site et irons copier le “dockerfile” que l’on souhaite en fonction de notre version. Par exemple, si nous souhaitons la version 11.13.

 

Nous chargerons les 2 fichiers. Tout d’abord “dockerfile” qui est le fichier appeler pour construire l’image, puis le “docker-entrypoint.sh” qui est le script lancé pour la partie configuration de l’application.

Ces 2 fichiers sont au format txt et donc utilisable sur un éditeur de texte classique.

# mkdir PG11 
# cd PG11 
# vi Dockerfile 
# vi docker-entrypoint.sh

Ne pas oublier de passer un chmod755 sur le fichier “docker-entrypoint.sh“, en effet, ce script est exécuté à chaque lancement de notre container Docker /!\

Dans un “dockerfile” chaque instruction va commencer par un ordre à interpréter. Les principaux ordres étant les suivants :

  • FROM : les sources que l’on prend pour construire notre image (ce peut être une distribution linux, un package applicatif….)
  • RUN : l’action qui devra être exécutée (installer des packages, télécharger des sources ….)
  • ENV : définir des variables d’environnement utilisées durant le cycle de vie de construction de cette image.
  • CMD : il s’agit de l’exécution par défaut prise par le dockerfile, en quelque sorte la dernière instruction à effectuer.

D’autres instructions facultatives peuvent apparaitre afin d’effectuer des actions bien précises :

  • COPY : faire une copie d’un fichier local vers l’image
  • VOLUME : définir un FS d’installation pour notre container
  • ENTRYPOINT : un argument à exécuter lorsque notre container est lancé (ici nous exécuterons le script “docker-entrypoint.sh“)

On pourra par exemple aller changer le répertoire du PGDATA, et le faire pointer vers “/var/lib/postgresql/11” au lieu de “/var/lib/postgresql/data“.

# vi Dockerfile
.....
ENV PGDATA /var/lib/postgresql/data
# this 777 will be replaced by 700 at runtime (allows semi-arbitrary "--user" values)
RUN mkdir -p "$PGDATA" && chown -R postgres:postgres "$PGDATA" && chmod 777 "$PGDATA"
VOLUME /var/lib/postgresql/data

par

 

ENV PGDATA /var/lib/postgresql/11
# this 777 will be replaced by 700 at runtime (allows semi-arbitrary "--user" values)
RUN mkdir -p "$PGDATA" && chown -R postgres:postgres "$PGDATA" && chmod 777 "$PGDATA"
VOLUME /var/lib/postgresql/11

 

Dans le fichier “docker-entrypoint.sh”, nous pourrons définir l’option “data-checksums” à la création de l’instance.
Nous allons aussi créer un rôle nommé “manu”, et une base de test pour notre instance dont le nom sera donné en paramètre.

# vi docker-entrypoint.sh
....
 eval 'initdb --username="$POSTGRES_USER" --pwfile=<(echo "$POSTGRES_PASSWORD") '"$POSTGRES_INITDB_ARGS"' "$@"'

par

 eval 'initdb --username="$POSTGRES_USER" --pwfile=<(echo "$POSTGRES_PASSWORD") --data-checksums '"$POSTGRES_INITDB_ARGS"' "$@"'

 

et

docker_setup_db_manu() {
psql -U ${POSTGRES_USER} -c "CREATE ROLE \"${POSTGRES_USER_MANU}\" with LOGIN CREATEDB PASSWORD '${USER_MANU_PASS}';" >/dev/null
psql -U ${POSTGRES_USER} -c "CREATE DATABASE \"${DB_MANU}\" with owner '${POSTGRES_USER_MANU}';" >/dev/null
}
...
 file_env 'POSTGRES_USER_MANU' 'manu'
 file_env 'DB_MANU'

Sur cette base, nous y installerons l’extension “pg_cron”.
La modification va se dérouler en 3 étapes, un peu comme cela peut être fait sur une installation on-prem.

  • Tout d’abord, télécharger et installer le package présent sur les dépôts système PGDG.
  • configurer dans le fichier “postgresql.conf” les données propres à cette extension et à son fonctionnement.
  • créer celle ci directement sur la base souhaitée.

Les modifications devront être effectuées sur nos 2 fichiers “Dockerfile” et “docker-entrypoint.sh”

Sur le “Dockerfile”, installer le package pour cette extension” :

# vi Dockerfile
...
FROM debian:stretch-slim
...
RUN set -ex; \
....
 apt-get install -y postgresql-11-cron;\
....

 

Cela va ajouter le package “postgresql-11-cron” dans notre image packagée.

C’est dans le fichier “docker-entrypoint.sh” que nous configurerons l’extension en base.
Tout d’abord avec une procédure qui chargera dans le fichier “postgresql.conf” la configuration de notre extension. Le nom de la base ou sera installée cette extension sera celui que l’on passera en paramètre.

# vi docker-entrypoint.sh ....
docker_setup_pg_cron() {
   {
   echo "shared_preload_libraries = 'pg_cron'"
   } >> "$PGDATA/postgresql.conf"
   {
   echo "cron.database_name='${DB_MANU}'"
   } >> "$PGDATA/postgresql.conf"
}

Puis en la créant directement dans cette base passée en paramètre.

docker_setup_db_manu() {
....
psql -U ${POSTGRES_USER} -d ${DB_MANU} -c "CREATE EXTENSION pg_cron;" >/dev/null
}

Les procédures “docker_setup_pg_cron()”  et “docker_setup_db_manu()” seront appelées dans la partie “main” de notre script “docker-entrypoint.sh“.

 

Construire l’image

 

La construction se fait à partir du “dockerfile

# docker build . -t image_pg_11_manu 

Tout un environnement Linux debian est chargé, puis mis à jour avec les derniers packages. Le -t permet de tagger notre image. A la fin de ce processus, nous devrions voir :

Successfully built f5ebf2fe8083
Successfully tagged image_pg_11_manu:latest

Cela veut dire que notre image est créée. Nous allons le vérifier

# docker image ls
REPOSITORY        TAG          IMAGE ID       CREATED          SIZE
image_pg_11_manu  latest       f5ebf2fe8083   29 seconds ago   283MB
postgres          latest       346c7820a8fb   2 weeks ago      315MB
debian            stretch-slim 27a36d9cbe0f   2 weeks ago      55.3MB

Nous avons donc notre image nommée “image_pg_11_manu” qui est prête. Notons que nous avons également conserver une partie des libraires propres à Debian pour construire cette image.

Démarrer le container

Nous passons au démarrage du container à partir de cette nouvelle image :

# docker run --name postgresql11manu -itd --restart always --publish 5438:5432 -e "POSTGRES_PASSWORD=test2021" -e "DB_MANU=manudb" -e "USER_MANU_PASS=test2021" image_pg_11_manu
62a270bc27a71585208fd2223ee92fc6a4e49eceb11ea4cd6ad2cf383bd3103d

Un id d’opération nous est alors renvoyé par la commande. Pour confirmer la création de ce container :

# docker container ls
CONTAINER ID   IMAGE              COMMAND                 CREATED          STATUS         PORTS                                      NAMES
62a270bc27a7   image_pg_11_manu   "docker-entrypoint.s…"  19 seconds ago   Up 18 seconds  0.0.0.0:5438->5432/tcp, :::5438->5432/tcp  postgresql11manu

Voir le log du container :

# docker logs --tail 100 --details postgresql11manu
...
Success. You can now start the database server using:

pg_ctl -D /var/lib/postgresql/11 -l logfile start

waiting for server to start....2021-09-22 12:36:06.779 UTC [45] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2021-09-22 12:36:06.794 UTC [46] LOG: database system was shut down at 2021-09-22 12:36:06 UTC
2021-09-22 12:36:06.798 UTC [45] LOG: database system is ready to accept connections
2021-09-22 12:36:06.801 UTC [52] FATAL: database "manudb" does not exist
2021-09-22 12:36:06.803 UTC [45] LOG: background worker "pg_cron launcher" (PID 52) exited with exit code 1
done
server started

PostgreSQL init process complete; ready for start up.

2021-09-22 12:36:07.456 UTC [1] LOG: listening on IPv4 address "0.0.0.0", port 5432
2021-09-22 12:36:07.456 UTC [1] LOG: listening on IPv6 address "::", port 5432
2021-09-22 12:36:07.459 UTC [1] LOG: listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
2021-09-22 12:36:07.473 UTC [92] LOG: database system was shut down at 2021-09-22 12:36:07 UTC
2021-09-22 12:36:07.477 UTC [1] LOG: database system is ready to accept connections
2021-09-22 12:36:07.486 UTC [98] LOG: pg_cron scheduler started

L’instance est démarrée sur le port 5432 dans le container, mais bien accessible via le port 5438 depuis notre serveur local, comme nous le voyons sur la commande “docker container ls“.
A première vue, nous voyons que PostgreSQL a du redémarrer car il ne trouvait pas la base “manudb”, celle ci a été créé par la suite.

Test de connexion avec psql :

 

[postgres]$ psql -p 5438 -h localhost
Password for user postgres:
psql (13.0, server 11.13 (Debian 11.13-1.pgdg90+1))
Type "help" for help.

postgres=# \l+

                                                                   List of databases
   Name    |  Owner   | Encoding |  Collate   |   Ctype    |   Access privileges   |  Size   | Tablespace |                Description
-----------+----------+----------+------------+------------+-----------------------+---------+------------+--------------------------------------------
 manudb    | manu     | UTF8     | en_US.utf8 | en_US.utf8 |                       | 7529 kB | pg_default |
 postgres  | postgres | UTF8     | en_US.utf8 | en_US.utf8 |                       | 7669 kB | pg_default | default administrative connection database
 template0 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +| 7529 kB | pg_default | unmodifiable empty database
           |          |          |            |            | postgres=CTc/postgres |         |            |
 template1 | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres          +| 7529 kB | pg_default | default template for new databases
           |          |          |            |            | postgres=CTc/postgres |         |            |

(4 rows)

postgres=# \du+

                                          List of roles
 Role name |                         Attributes                         | Member of | Description
-----------+------------------------------------------------------------+-----------+-------------
 manu      | Create DB                                                  | {}        |
 postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}        |


postgres=# select version();

                                                              version
------------------------------------------------------------------------------------------------------------------------------------
 PostgreSQL 11.13 (Debian 11.13-1.pgdg90+1) on x86_64-pc-linux-gnu, compiled by gcc (Debian 6.3.0-18+deb9u1) 6.3.0 20170516, 64-bit

(1 row)

 

L’extension “pg_cron” a bien été inscrite dans les librairies préchargées avec un fonctionnement sur notre base “manudb” passée en paramètre à l’appel du “docker run” :

# docker exec -it postgresql11manu psql -U postgres -c "select name, setting from pg_settings where name like '%shared%'"
name                        | setting
----------------------------+---------
dynamic_shared_memory_type  | posix
shared_buffers              | 16384
shared_preload_libraries    | pg_cron


# docker exec -it postgresql11manu psql -U postgres -c "select name, setting from pg_settings where name like '%cron%'"
name | setting
-----------------------------+-----------
cron.database_name           | manudb
cron.host                    | localhost
cron.log_run                 | on
cron.log_statement           | on
cron.max_running_jobs        | 32
cron.use_background_workers  | off
# docker exec -it postgresql11manu psql -U postgres -d manudb -c "select * from pg_extension"
extname  | extowner | extnamespace | extrelocatable | extversion | extconfig                 | extcondition
---------+----------+--------------+----------------+------------+---------------------------+---------------
plpgsql  | 10       | 11           | f              | 1.0        |                           |
pg_cron  | 10       | 2200         | f              | 1.3        | {16390,16388,16411,16409} | {"","","",""}

 

Notre instance a été créée avec succès. Elle est accessible depuis le port 5438 de notre machine, et notre rôle “manu” est créé avec sa base “manudb“.
Sur cette base, l’extension “pg_cron” est bien active.
Cette instance est une version 11.13.

 

Gérer son container

Il est possible d’accéder directement à notre container et de lancer un “bash” dessus, accessible depuis le compte root de notre machine locale.

# docker exec -it postgresql11manu bash
root@62a270bc27a7:/# ls -l

A partir de la, nous pourrons accéder à notre instance PostgreSQL, accessible depuis le port par défaut, 5432.

postgres@62a270bc27a7:~$ psql
psql (11.13 (Debian 11.13-1.pgdg90+1))
Type "help" for help.

postgres=# \conninfo
You are connected to database "postgres" as user "postgres" via socket in "/var/run/postgresql" at port "5432".

Attention, nous sommes sur un container, donc une image Linux “light”. De nombreuses commandes ne sont pas accessibles sur le bash courant.

Il sera possible de vérifier la configuration complète du container, avec l’option “inspect”. Le retour nous est donnée via un affichage JSON

# docker container ls
CONTAINER ID   IMAGE              COMMAND                  CREATED          STATUS          PORTS                                       NAMES
62a270bc27a7   image_pg_11_manu   "docker-entrypoint.s…"   51 minutes ago   Up 51 minutes   0.0.0.0:5438->5432/tcp, :::5438->5432/tcp   postgresql11manu
# docker container inspect postgresql11manu
[
    {
        "Id": "62a270bc27a71585208fd2223ee92fc6a4e49eceb11ea4cd6ad2cf383bd3103d",
        "Created": "2021-09-17T15:24:39.528442352Z",
        "Path": "docker-entrypoint.sh",
        "Args": [
            "postgres"
        ],
        "State": {
            "Status": "running",
            "Running": true,
            "Paused": false,
            "Restarting": false,
            "OOMKilled": false,
            "Dead": false,
            "Pid": 30809,
            "ExitCode": 0,
            "Error": "",
            "StartedAt": "2021-09-17T15:24:40.13504382Z",
            "FinishedAt": "0001-01-01T00:00:00Z"
        }, .........

 

La persistance des données

Il est possible de rendre persistent les fichiers de son instance sur son système local. Pour cela, nous utiliserons l’option -v ou —volume afin de faire pointer les fichiers bases de données sur un FS de notre machine. Ceci nous permettra de conserver une copie en locale des fichiers, même une fois notre container supprimé !

Créer le FS localement :

# mkdir /data/postgres/11.13

La suite consistera à créer un nouveau container, à partir de notre image construite auparavant, qui porte le nom “image_pg_11_manu”.

# docker run --name pg11manu_local -itd --restart always --publish 5439:5432 -e "POSTGRES_PASSWORD=test2021" -e "DB_MANU=manudb2" -e "USER_MANU_PASS=test2021" -v "/data/postgres/11.13:/var/lib/postgresql/11" image_pg_11_manu
51a6a4c8e9e3cf7b80fb1870415446ef9f38ac2847df0791e8e9332ad84f95f6

Nous créons donc un nouveau container, avec une base nommée “manudb2“, avec des fichiers persistés vers le nouveau dossier “/data/postgres/11.13” sur notre machine locale.

Si l’on vérifie les bases en se connectant sur le container :

# docker exec -it pg11manu_local psql -U postgres -c "\l"
List of databases
Name       | Owner    | Encoding | Collate    | Ctype | Access privileges
-----------+----------+----------+------------+------------+-----------------------
manudb2    | manu     | UTF8     | en_US.utf8 | en_US.utf8 |
postgres   | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
template0  | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres +
           |          |          |            |            | postgres=CTc/postgres
template1  | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres +
           |          |          |            |            | postgres=CTc/postgres

 

Et sur le FS local :

# ls -lrt /data/postgres/11.13/
total 120
drwx------. 2 systemd-coredump input 4096 Sep 21 15:14 pg_dynshmem
drwx------. 2 systemd-coredump input 4096 Sep 21 15:14 pg_commit_ts
-rw-------. 1 systemd-coredump input 3 Sep 21 15:14 PG_VERSION
drwx------. 2 systemd-coredump input 4096 Sep 21 15:14 pg_twophase
drwx------. 2 systemd-coredump input 4096 Sep 21 15:14 pg_tblspc
drwx------. 2 systemd-coredump input 4096 Sep 21 15:14 pg_snapshots
drwx------. 2 systemd-coredump input 4096 Sep 21 15:14 pg_serial
drwx------. 2 systemd-coredump input 4096 Sep 21 15:14 pg_replslot
drwx------. 4 systemd-coredump input 4096 Sep 21 15:14 pg_multixact
-rw-------. 1 systemd-coredump input 88 Sep 21 15:14 postgresql.auto.conf
-rw-------. 1 systemd-coredump input 1636 Sep 21 15:14 pg_ident.conf
drwx------. 2 systemd-coredump input 4096 Sep 21 15:14 pg_xact
drwx------. 3 systemd-coredump input 4096 Sep 21 15:14 pg_wal
drwx------. 2 systemd-coredump input 4096 Sep 21 15:14 pg_subtrans
-rw-------. 1 systemd-coredump input 4535 Sep 21 15:14 pg_hba.conf
drwx------. 6 systemd-coredump input 4096 Sep 21 15:14 base
-rw-------. 1 systemd-coredump input 24084 Sep 21 15:16 postgresql.conf
drwx------. 4 systemd-coredump input 4096 Sep 21 15:17 pg_logical
drwx------. 2 systemd-coredump input 4096 Sep 21 15:18 pg_notify
-rw-------. 1 systemd-coredump input 36 Sep 21 15:18 postmaster.opts
drwx------. 2 systemd-coredump input 4096 Sep 21 15:18 global
-rw-------. 1 systemd-coredump input 92 Sep 21 15:18 postmaster.pid
drwx------. 2 systemd-coredump input 4096 Sep 21 15:18 pg_stat_tmp
drwx------. 2 systemd-coredump input 4096 Sep 21 15:18 pg_stat

Nos fichiers sont la !

Passons, par exemple, le “shared_buffer” de 128M par défaut à 256M dans le fichier “postgresql.conf”.

# vi /data/postgres/11.13//postgresql.conf
...
shared_buffers = 256MB

# docker container stop pg11manu_local 
# docker container start pg11manu_local 

# docker exec -it pg11manu_local psql -U postgres -c "show shared_buffers"
shared_buffers
----------------
256MB

 

Maintenant, supprimons complètement le container.

# docker stop pg11manu_local
pg11manu_local
# docker rm pg11manu_local
pg11manu_local

Nous remarquons que les fichiers sont toujours la dans le FS

# ls -lrt /data/postgres/11.13/ | wc -l
25

Et s’il l’on recrée un container vers ce même volume !

# docker run --name pg11manu_local_2 -itd --restart always --publish 5439:5432 -e "POSTGRES_PASSWORD=test2021" -e "DB_MANU=manudb2" -e "USER_MANU_PASS=test2021" -v "/data/postgres/11.13:/var/lib/postgresql/11" image_pg_11_manu
c4a7705e4d38709ae33e16a7f9791dc727bbca23dea32e8c11337575e255b40c

 

# docker exec -it pg11manu_local_2 psql -U postgres -c "\l"
List of databases
Name       | Owner    | Encoding | Collate    | Ctype | Access privileges
-----------+----------+----------+------------+------------+-----------------------
manudb2    | manu     | UTF8     | en_US.utf8 | en_US.utf8 |
postgres   | postgres | UTF8     | en_US.utf8 | en_US.utf8 |
template0  | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres +
           |          |          |            |            | postgres=CTc/postgres
template1  | postgres | UTF8     | en_US.utf8 | en_US.utf8 | =c/postgres +
           |          |          |            |            | postgres=CTc/postgres

 

# docker exec -it pg11manu_local_2 psql -U postgres -c "show shared_buffers"
shared_buffers
----------------
256MB
(1 row)

 

la valeur de notre “shared_buffer” a été conservée car c’est celle qui est configurée sur le “postgresql.conf” sur le FS local.

 

Sauvegarder un container

Pour effectuer la sauvegarde de notre container PostgreSQL, 2 solutions.

– via un export du container complet dans un fichier “tar”

# docker container export pg11manu_local_2 -o container_manu_2.tar
# ls -l container_manu_2.tar
-rw-------. 1 root root 282522624 Sep 21 15:44 container_manu_2.tar

Attention, dans cette méthode, toute l’arborescence du montage “overlay” sera prise en charge dans l’archive. un “tar tvf” sur le fichier permettra de voir le contenu.

– via la création d’un “snapshot” pour faire une image de notre container. L’option -p permet de faire une image dite consistente, le container est mis en “pause” durant le backup.

# docker container commit -a "Emmanuel RAMI" -p pg11manu_local_2 backup_pg11manu_local_2
sha256:310b8475239840be9de470e574a94e74122b481bdc23abc300d2f2451388c649

# docker image ls backup_pg11manu_local_2
REPOSITORY               TAG      IMAGE ID      CREATED        SIZE
backup_pg11manu_local_2  latest   310b84752398  2 minutes ago  283MB

Par la suite, nous pouvons envoyer vers un fichier “tar” le contenu de cette image

# docker save -o /data/postgres/backup/backup_pg11manu_local_2 backup_pg11manu_local_2

# ls -l /data/postgres/backup/backup_pg11manu_local_2
-rw-------. 1 root root 290162176 Sep 21 15:53 /data/postgres/backup/backup_pg11manu_local_2

 

A noter que les fichiers d’export du container et de l’image seront sensiblement aussi volumineux.
Il sera cependant possible d’externaliser son image via un “docker push” vers un compte sur DockerHub.

 

Supprimer le container et son image

 

Attention, avant de supprimer l’image, il faut tout d’abord supprimer les containers qui y sont rattachés. Sinon :

# docker image ls
REPOSITORY                TAG           IMAGE ID      CREATED          SIZE
backup_pg11manu_local_2   latest        310b84752398  16 hours ago     283MB
image_pg_11_manu          latest        ea64f1b1c6ff  4 days ago       283MB
postgres                  latest        346c7820a8fb  2 weeks ago      315MB
debian                    stretch-slim  27a36d9cbe0f  2 weeks ago      55.3MB
# docker image rm image_pg_11_manu
Error response from daemon: conflict: unable to remove repository reference "image_pg_11_manu" (must force) - container c4a7705e4d38 is using its referenced image ea64f1b1c6ff

 

Voyons le container portant l’ID c4a7705e4d38

# docker container ls
CONTAINER ID    IMAGE              COMMAND                  CREATED         STATUS            PORTS                                     NAMES
c4a7705e4d38    image_pg_11_manu   "docker-entrypoint.s…"   16 hours ago    Up About a minute 0.0.0.0:5439->5432/tcp, :::5439->5432/tcp pg11manu_local_2
1fcc85219a3e    postgres           "docker-entrypoint.s…"   40 hours ago    Up About a minute 0.0.0.0:5440->5432/tcp, :::5440->5432/tcp postgresql13
62a270bc27a7    image_pg_11_manu   "docker-entrypoint.s…"   4 days ago      Up About a minute 0.0.0.0:5438->5432/tcp, :::5438->5432/tcp postgresql11manu

 

C’est le second container portant les fichiers PostgreSQL sur un FS en local. Nous allons donc arrêter et supprimer celui ci

# docker container stop pg11manu_local_2
pg11manu_local_2

# docker container rm pg11manu_local_2
pg11manu_local_2

Puis

# docker image rm image_pg_11_manu
Untagged: image_pg_11_manu:latest

et

# docker image ls
REPOSITORY                TAG           IMAGE ID      CREATED        SIZE
backup_pg11manu_local_2   latest        310b84752398  16 hours ago   283MB
postgres                  latest        346c7820a8fb  2 weeks ago    315MB
debian                    stretch-slim  27a36d9cbe0f  2 weeks ago    55.3MB

 

N’hésitez pas si vous avez des commentaires 🙂

Merci à vous.

Emmanuel RAMI

Continuez votre lecture sur le blog :

twitterlinkedinmail

Emmanuel RAMI

5 commentaires

  1. Bonjour,
    Article intéressant merci !
    Sur les désavantages je rajouterai que certes Docker tourne sur Mac et Windows mais à quel prix ? celui de la lenteur…
    Pour travailler sur une machine local (environnement de développement) je conseille vivement d’utiliser un Linux comme OS hôte, si on souhaite utiliser Docker.

  2. Bonjour Adam
    merci pour votre retour.
    Je suis assez d’accord, j’ai essayé sur Windows, clairement le runtime Docker n’est pas performant et en plus trop compliqué pour la mise en place en terme de sécurité. Je recommande largement l’utilisation de la couche Ubuntu embarquée sur les dernières versions de Windows 10.
    Cordialement.

  3. Bonjour
    j’ai lu votre article avec beaucoup d’attention car je tente de créer un conteneur postgres sous centos8 mais avec des incompréhensions sur l’entrypoint car des erreurs pas trop clair apparaissent
    l’image se crée correctement mais lorsque j’essaye d’instancier mon image avec docker-compose j’obtiens une erreur OCI : Cannot start service test: OCI runtime create failed: container_linux.go:380: starting container process caused: exec: “/usr/local/bin/docker-entrypoint.sh”: permission denied: unknown

  4. Bonjour David
    j’ai eu cette erreur moi aussi.
    EN fait, avant d’importer votre fichier pour créer l’image, n’oubliez pas de faire un chmod 755 sur le fichier “docker-entreypoint.sh”.
    Par la suite, relancer la creation de votre image avec :
    $ docker build . -t

  5. Merci Mr Rami pour cet article fort intéressant.
    Je vais me lancer dans l’apprentissage de PostgreSql et de Docker sur un portable MacBook Pro donc votre article va m’aider dans ma démarche.

    F.Lam

Laisser un commentaire

Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.