Conteneuriser

une application

Pierre Gambarotto

Objectifs

À partir d’une application existante :

  • étudier l’architecture de l’application
  • isoler les différents composants dans des conteneurs séparés
  • étudier la communication entre les différents composants
  • traduire en «docker compose»
  • configurer les liens entre conteneurs

Prérequis

  • connaître docker
  • outils : docker dans une version > 2020
  • git, jq

Quant à moi

Responsable informatique à l’IMT (Toulouse)

  • développeur backend initialement
  • sysadmin unix

Membre de la PLM Team

Centres d’intérêts : programmation fonctionnelle, déploiement reproductible, architecture logicielle, self-hosting

Cas d’étude : GRR

Grr par Devome

Source

https://github.com/JeromeDevome/GRR version cible : 4.3.X

PHP : >= 7.2.5 && <= 8.3

php : modules php-fileinfo, php-gd, php-mbstring, php-mysqli, php-mysqlnd, php-xml, php-intl, mysql : >= 5.4 && <= 5.7

https://devome.com/GRR/DOC/installation-et-mise-a-jour/installation

Ce que cela fait

Permets à des utilisateurs de réserver des ressources d’un domaine dans un calendrier.

  • ressources gérées par domaine
  • calendrier
  • utilisateurs : authentication interne ou sso/ldap
  • permissions des utilisateurs sur les actions possibles sur les ressources

Présentation et test de grr

git clone --recurse-submodules \
  https://plmlab.math.cnrs.fr/anf2024/grr-docker.git grr
cd grr
docker compose up # C-c pour arrêter

http://localhost:8888

=> connexion : ADMINISTRATEUR/admin

Remarques :

  • les sources de grr en version 4.3.6 sont liées par un sous-module git dans app/src
  • fichier .env

Créer quelques ressourecs

  1. Connectez-vous en tant qu’admin
  2. Passer dans la partie administration
  3. Éditer le domaine
    • ajouter ou personnaliser une ressource existante

Observer le répertoire où est sauvegardée l’image => personnalisation/images/ressources/1-bb14175d9eff562ebc3fe42d20614aec/img_1.gif

observez les conteneurs docker mis en jeu

docker compose ps -a
NAME         IMAGE            COMMAND                  SERVICE
grr-app-1    grr-app          "/usr/local/bin/db_c…"   app
grr-db-1     mariadb:11.5.2   "docker-entrypoint.s…"   db
grr-init-1   mariadb:11.5.2   "docker-entrypoint.s…"   init
  • nommage dépendant du projet, par défaut le répertoire
  • notion de service : conteneur docker

Persistence : volumes

docker volume ls | grep grr_
local     grr_db_data
local     grr_uploads
  • même principes pour le nommage

Communication : network

docker network ls | grep grr_default
8a06c789788f   grr_default          bridge    local

  • nommage consistant

Rappel : lancer un processus dans un conteneur existant

À la mode docker

docker exec -ti  grr-app-1 /bin/bash

À la mode docker compose

docker compose exec -ti app /bin/bash

Se généralise bien à toutes les commandes que vous connaissez sur docker :

docker compose [exec/run/kill/build/images/ps/pull/push]

docker compose agit comme un wrapper pour docker pour les conteneurs définis dans compose.yaml

Conteneuriser une application

  1. étudier l’architecture de l’application
  2. scindez en plusieurs conteneurs
  3. dénichez des images standards comme base
  4. créer ou adaptez les images au cas
  5. gestion de la persistence
  6. les détails

Architecture, étude du cas de grr

Pour au final arriver à un découpage en conteneur, on s’intéresse aux différents processus nécessaires à l’application.

Pour l’utilisateur, le fonctionnement est le suivant :

navigateur -> [ http -> php -> mariadb ]
navigateur -> [ http -> php -> ressources statiques ]
navigateur -> [ http -> ressources statiques ]

ressources statique = du stockage

Application web, 3 parties

  • app : application php, exécutée par php-fpm/module apache ou autre
  • http : nginx, apache, caddy, …
  • base de données : mariadb, mysql

Si vous le souhaitez, en particulier pour des raison de performance, la partie http elle-même peut être scindée en plusieurs parties, pour assurer les fonctionnalités suivantes :

  1. terminaison ssl
  2. répartition de charge (haproxy, nginx, caddy …)
  3. cache

Interactions entre les différents parties

Par réseau ou par stockage

Ici :

http <-> app partagent des fichiers, typiquement les ressources css/js/images qui doivent être servies par la couche http mais peuvent être gérées par l’application php.

la partie base de données ne communique qu’avec la partie application

app <-> mariadb sur tcp -> mysql

Transformation en docker-compose

Principes :

  • un container par groupes de processus, idéalement 1 processus principal par conteneur.
  • spécifier les moyens de communication entre 2 conteneurs: par réseau, par stockage
  • persistence : définir des volumes

explication des principes

modularité/SoC Separation of Concerns : gérer séparément des choses différentes permet:

  • séparer les évolutions
  • sécurité par conteneur
  • intégration continue : permet de gérer des caches, meilleure réactivité
  • utilisation d’image déjà faite

Persistence

Rappel : un conteneur docker est immutable, tout changement est écrit dans un overlay qui est perdu au redémarrage.

Les données persistentes sont gérées dans des volumes.

Découpage en conteneurs

Grr: forte adhérence entre la partie app et la partie http, puisqu’un stockage sous forme de fichiers est commun.

2 solutions possibles sans modifier l’application :

  • 1 seul conteneur avec les 2 processus et un volume de stockage
  • 2 conteneurs qui partagent le volume de stockage.

Notre choix : 1 seul conteneur

apache + mod php

-> un seul processus en écoute, apache -> un seul volume de données non partagé

image de base : php:8.3-apache

FROM php:8.3-apache

Résumé

Pour faire tourner GRR, on va utiliser 2 conteneurs

  • db: processus de la base de données
  • app : apache+php

Étape suivante : images de base

Une des forces de l’écosystème Docker

https://hub.docker.com/ : registry par défaut utiliser par pull

Pour GRR, on choisit :

  • mariadb version 11.5.2
  • php version 8.3 avec apache

Compose.yaml

Fichier unique permettant de définir tous les éléments d’une application

À voir plus tard en TP :

Étude du compose.yaml fourni

principes : issus des bonnes pratiques en développement DEVOPS

  • DRY
  • pas de constantes dans le code

Injection automatique des variables d’environnement définies dans .env

service db

image Mariadb 11.5 utilisée directement

À savoir :

  • données gérées dans /var/lib/mysql
  • crée une base en fonction des variables d’environnement MARIADB_*

compose.yaml L10-24, introduction du volume db_data

service app

compose.yaml L37=57

On construit nous-même une image On suit le processus d’installation de GRR, et on adapte.

Dans le Dockerfile : installation des dépendances php et binaires

ARG vs ENV

ARG est utilisé dans le Dockerfile pour positionner la version de l’image de base utilisée.

ARG : utilisé dans la phase build : Dockerfile -> image

ENV : utilisé quand un conteneur s’exécute : image -> conteneur

Pour la configuration initiale

  • configuration de connect.inc.php
  • injection dans la base de données du schéma initial : besoin du client mariadb
  • insérer au moins le mot de passe admin dans la base : idem

Conteneur d’initialisation

compose.yaml L25-36

service init, basé sur la même image image Mariadb 11.5

On change la commande lancée pour un script spécifique d’initialisation de la base de donnée initdb.sh

dependson : db service healthy, on utilise le healthcheck du conteneur db

Techniques utilisées pour la mise au point

  • scripts en bind mount : édition sur la machine hôte
  • tests par bash dans un conteneur
  • le script «sourcé» a un comportement différent du script exécuté -> expose les fonctions utilisées, mais ne les exécute pas
docker compose run -ti --rm init /bin/bash
# inside
source /init/init_db.sh
declare -f # list all functions
sql

Configuration : env et bind mounts

Pour configurer un conteneur, on combine :

MAIS !!!

docker inspect grr-docker-app-1| jq '.[0].Config.Env'

Conclusion docker-compose

  • outil local, docker swarm n’a jamais décollé : pas de driver pour le stockage en particulier
  • principe de localité : entrée par un seul fichier
  • DiD : utilisable en intégration continue pour des tests
  • idéal pour développer en local une application

Pour déployer : voir la suite !

kompose --provider openshift --file compose.yaml convert -o k8s/

Permet d’obtenir un mapping initial entre les objets gérés par docker-compose et les objets kubernetes.

TP

docker compose up -d # -d : detach
dockur compose logs app|db -f # see the logs

Se connecter à chaque conteneur pour investiguer/mettre au point

Exemple avec le conteneur app :

docker compose exec -ti --rm app /bin/bash

Inspectez !

Le réseau pour commencer.

Point de départ :

docker network inspect grr_default
# chercher l'id
ip l # trouver le bridge avec l'id correspondant
# regarder les veth associés

De manière plus globale : les fonctions inspect de docker sont excellentes pour comprendre la machinerie utilisée.

Utilisez maintenant docker volume inspect pour trouver l’endroit réel où sont stockés les fichiers correspondant aux différents volumes.

Gestion des secrets

Déplacer la définition du mot de passe admin de la variable d’environnement à un secret.

secrets

Le mot de passe est actuellement utilisé dans init/scripts/init_db.sh à partir de la variable GRR_ADMIN_PASSWORD définie dans le .env.

  1. définissez un secret dans le fichier compose.yaml
  2. rendez-le accessible au service init
  3. modifier le script lancé par le service init

service backup pour la base de données et les autres données

Ajouter un service backup dans votre application.

Ce service doit être appelé pour réaliser un backup de la base de données ET des ressources statiques.

indices :

  • utiliser le volume uploads existant
  • utiliser l’image mariadb pour la connexion
  • backup : définir un volume supplémentaire

ne pas démarrer automatiquement : utiliser un profil spécifiques