Nous visons toujours à construire des services aussi économes en ressources que possible. Cela est vrai pour les services que nous proposons à l’externe et, tout aussi important, pour notre propre infrastructure interne. Beaucoup de nos outils internes, comme les systèmes de facturation et de supervision, reposent sur des bases de données PostgreSQL. Bien qu’essentielles, ces bases de données restent souvent inactives pendant de longues périodes, consommant CPU et mémoire RAM sans raison.
Alors, nous nous sommes demandé : peut-on appliquer notre philosophie scale-to-zero à nos propres bases de données ? La réponse est oui. Nous avons développé un système pour provisionner des instances PostgreSQL qui ne tournent que lorsqu’elles sont activement utilisées. Cette conception est incroyablement économe en ressources, mais elle présente quelques compromis que nous explorerons.
Voici un schéma qui présente ce que nous avons construit et comment nous sommes parvenus à cette montée en charge dynamique.
flowchart LR
subgraph Machine
A[systemd.socket]
B[systemd-socket-proxyd]
D[(disque local)]
A -- port 15432 --> B
subgraph Docker-Compose
C[conteneur postgres]
end
B -- port 5432 --> C
C -- dossier-données --> D
end
X[Internet] -- port 25432 --> A
Le cœur de cette configuration est l’activation par socket systemd. Plutôt que de faire tourner un conteneur PostgreSQL 24h/24 et 7j/7, nous laissons le système d’initialisation systemd écouter sur le port de la base de données. Lorsqu’une application tente de se connecter, systemd intercepte la requête, démarre le conteneur de base de données à la demande, puis transmet la connexion. Une fois que la base n’est plus utilisée, elle est arrêtée automatiquement.
Cette approche combine la puissance d’outils Linux standards et éprouvés : systemd pour la gestion des services et l’activation par socket, et Docker Compose pour définir notre environnement conteneurisé de base de données. C’est simple, robuste, et ne nécessite aucun logiciel personnalisé.
Nous avons fait deux choix technologiques précis pour cette configuration : faire tourner PostgreSQL dans un conteneur et le gérer avec Docker Compose.
Découplage de l’OS hôte : En faisant tourner PostgreSQL dans un conteneur Docker, nous découplons la version de la base de la version de l’OS hôte. Cela nous donne la flexibilité d’exécuter différentes versions de PostgreSQL pour différents services internes sur un même hôte sans conflits ni problèmes de dépendances. Nous pouvons upgrader une base pour un service sans impacter les autres.
Compatibilité avec systemd : Nous avons choisi Docker Compose car ses commandes de cycle de vie correspondent parfaitement à la manière dont systemd gère les services. La directive ExecStart de systemd attend une commande qui s’exécute au premier plan jusqu’à l’arrêt du service. docker-compose up fait exactement cela. La sémantique plus classique docker create suivie de docker start est plus difficile à gérer, car systemd aurait besoin d’un script plus complexe pour gérer le cycle de vie. docker-compose down fournit une commande unique et propre pour la directive ExecStop, garantissant que tout l’environnement est arrêté proprement.
Décomposons les fichiers de configuration qui rendent cela possible.
Nous utilisons une combinaison d’un fichier docker-compose.yml pour définir la base de données et de trois unités systemd pour gérer le cycle de vie scale-to-zero.
C’est un fichier docker-compose.yml standard. Il définit un conteneur PostgreSQL 18, mappe un port interne vers l’hôte, et monte un volume pour persister les données de la base sur le disque local. Cela garantit que même si le conteneur s’arrête, les données restent en sécurité. Tous les paramètres documentés dans l’image officielle PostgreSQL sur Docker Hub peuvent être utilisés ici, permettant des personnalisations supplémentaires comme la création d’utilisateurs ou de bases spécifiques au démarrage.
/root/pg/pg1/docker-compose.yml
1version: "3"
2services:
3 database:
4 image: 'postgres:18'
5 ports:
6 - 127.0.0.1:14532:5432
7 volumes:
8 - /root/pg/pg1/data:/var/lib/postgresql
9 environment:
10 POSTGRES_PASSWORD: SuperSecretAdminPassword
Cette unité .socket demande à systemd d’écouter le port 24532 sur toutes les interfaces réseau. Lorsqu’une connexion TCP arrive, systemd active le service pg1-proxy.service. C’est le point d’entrée de toutes les connexions à la base.
/etc/systemd/system/pg1-proxy.socket
1[Unit]
2Description=Socket pour le proxy pg1 pg (24532->127.0.0.1:14532)
3
4[Socket]
5ListenStream=0.0.0.0:24532
6ReusePort=true
7NoDelay=true
8Backlog=128
9
10[Install]
11WantedBy=sockets.target
C’est ici que réside la logique à la demande. Quand activé par le socket, ce service démarre d’abord le service réel de la base (Requires=pg1-postgres.service). La commande ExecStartPre est une petite boucle shell critique qui vérifie à plusieurs reprises si le port PostgreSQL interne est ouvert. Sans cette vérification, une condition de compétition pourrait survenir où le proxy démarre et redirige la connexion client avant que le conteneur PostgreSQL n’ait fini de s’initialiser. Cela entraînerait une erreur immédiate de “Connection Refused” pour le client. Ce script de pré-démarrage garantit une transition fluide et que le client ne se connecte qu’une fois la base complètement prête.
Le processus principal est systemd-socket-proxyd, un outil intégré qui redirige la connexion entrante vers le port interne sur lequel écoute le conteneur PostgreSQL (127.0.0.1:14532). L’élément crucial est --exit-idle-time=3min. Cela indique au proxy de quitter automatiquement s’il est inactif pendant trois minutes.
/etc/systemd/system/pg1-proxy.service
1[Unit]
2Description=Proxy TCP activé par socket vers Postgres local sur 14532
3
4Requires=pg1-postgres.service
5After=pg1-postgres.service
6
7[Service]
8Type=simple
9Sockets=pg1-proxy.socket
10ExecStartPre=/bin/bash -c 'for i in {1..10}; do nc -z 127.0.0.1 14532 && exit 0; sleep 1; done; exit 0'
11ExecStart=/usr/lib/systemd/systemd-socket-proxyd --exit-idle-time=3min 127.0.0.1:14532
Ce service gère le cycle de vie Docker Compose. Il est démarré par le service proxy. La directive clé est StopWhenUnneeded=true. Cela lie son cycle de vie au service proxy. Quand pg1-proxy.service s’arrête (car son timer d’inactivité a expiré), systemd considère que ce service n’est plus nécessaire et l’arrête automatiquement en lançant docker-compose down. Le conteneur est arrêté, libérant toutes ses ressources.
/etc/systemd/system/pg1-postgres.service
1[Unit]
2Description=conteneur postgres
3PartOf=pg1-proxy.service
4StopWhenUnneeded=true
5
6[Service]
7WorkingDirectory=/root/pg/pg1
8
9Type=simple
10ExecStart=/usr/bin/docker-compose up
11ExecStop=/usr/bin/docker-compose down
12
13Restart=on-failure
14RestartSec=2s
15TimeoutStopSec=30s
Cette configuration est incroyablement efficace, mais présente une considération majeure : la latence du “démarrage à froid”. La toute première connexion à la base après une période d’inactivité sera retardée. Le client doit attendre que systemd lance docker-compose up et que le conteneur PostgreSQL s’initialise. D’après notre expérience, cela prend environ une seconde pour une petite base, mais augmente avec la taille du stockage.
Pour beaucoup de systèmes internes — CI/CD, traitements par lot, ou tableaux de bord d’administration utilisés peu fréquemment — ce délai est un compromis parfaitement acceptable en échange d’économies substantielles de ressources. Pour des applications de production à fort trafic et sensibles à la latence, une base traditionnelle toujours active reste le bon choix.
Pour mettre une nouvelle base en ligne, il suffit d’activer les unités systemd.
1systemctl daemon-reload
2systemctl enable pg1-proxy.service
3systemctl enable pg1-postgres.service
4systemctl enable --now pg1-proxy.socket
Une fois activée, la base est prête à accepter des connexions, mais ne consommera aucune ressource jusqu’à la première. C’est une petite étape supplémentaire dans notre mission d’éliminer le gaspillage, prouvant que même une infrastructure essentielle comme une base relationnelle peut être pilotée de manière légère et à la demande.