Conteneurs: Orchestration

Containers
Docker
I211
PO43
Lecture
Principes de base de l’Orchestration de conteneurs, application avec Docker Compose.
Auteur
Affiliations

Université de Toulon

LIS UMR CNRS 7020

Date de publication

2024-10-02

Source
Branch
  • develop (b23c847)
  • 2024-03-08 09:42:51
Java
  • OpenJDK Temurin-21.0.4+7
  • Apache Maven 3.9.9
Docker
  • Client: 27.2.0 - buildx v0.16.2 - compose v2.29.7
  • Server: 27.2.0

Une introduction au développement maintenable et réutilisable: Git, Le langage Java et Docker.

Une application utilisant des conteneurs demande d’en coordonner plusieurs c’est que l’on appelle l’orchestration. L’outil classique est kubernetes de Google. Il s’agit d’un outils complexe. Une alternative simple est docker compose.

Docker compose permet de manipuler une architecture logicielle basée sur des conteneurs en décrivant des services dans un fichier YAML (docker-compose.yml). Les volumes et réseaux sont aussi gérés. Docker compose reprend les options des commandes de docker client. Docker compose est un simple plugin de docker client à installer.

Un exemple simple

Dans l’exemple suivant une application est composée de deux services (1) db une base de données relationnelles et (2) app une application JPA/REST écrit en Java.

Le service app s’appuie sur l’exécution d’une image docker de PostgreSQL en lui fixant des variables d’environnements, des volumes et une configuration réseau. Il faut noter que les volumes et les réseaux doivent externes définis globalement (dans le cas d’une utilisation avancée il est possible de les contrôler finement).

services:
  db:
    image: 'postgres:15.2-alpine'
    environment:
      - POSTGRES_USER=dba
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=demo-db
    volumes:
      - restjpa-pg-data:/var/lib/postgresql/data
    networks:
      - backend

  app:
    image: 'restjpa:latest'
    build: .
    depends_on:
      - db
    environment:
      - DATASOURCE_URL=jdbc:postgresql://db:5432/demo-db
      - DATASOURCE_USERNAME=dba
      - DATASOURCE_PASSWORD=secret
    ports:
      - "8088:8080"
    networks:
      - frontend
      - backend

volumes:
  restjpa-pg-data:

networks:
  frontend:
  backend:
%printWithName false
String script="""
pwd
( cd sample-java/restjpa && docker compose down --remove-orphans )""";

IJava.getKernelInstance().getMagics().applyCellMagic("shell",List.of(""),script);   
/home/jovyan/work/local
0

Il est possible de lancer explicitement un service avec la commande docker compose up depuis le répertoire qui contient le l’option -d permet de le détacher.

%%shell
cd sample-java/restjpa
docker compose up --quiet-pull --detach db
 db Pulling 
 db Pulled 
 Network restjpa_backend  Creating
 Network restjpa_backend  Created
 Volume "restjpa_restjpa-pg-data"  Creating
 Volume "restjpa_restjpa-pg-data"  Created
 Container restjpa-db-1  Creating
 Container restjpa-db-1  Created
 Container restjpa-db-1  Starting
 Container restjpa-db-1  Started

la commande ls liste les projets en cours d’exécution.

%%shell
docker compose ls -a
NAME                STATUS              CONFIG FILES
restjpa             running(1)          /home/jovyan/work/local/sample-java/restjpa/docker-compose.yml

la commande ps liste les conteneurs du projet courant, noter que compose nomme automatiquement le conteneur (<nom_repertoire_parent>-<service>-<id>). Il est donc simple de lancer plusieurs fois le même projet depuis des répertoires différents ou plusieurs fois le même service sans risque de conflit à condition de ne pas utiliser de bind volumes ni de mapper des ports de l’hôte.

%%shell
cd sample-java/restjpa
docker compose ps -a
NAME           IMAGE                  COMMAND                  SERVICE   CREATED        STATUS                  PORTS
restjpa-db-1   postgres:15.2-alpine   "docker-entrypoint.s…"   db        1 second ago   Up Less than a second   5432/tcp

On remarque que docker compose prend en charge la création et le nommage des réseaux et des volumes en les préfixant du nom du répertoire parent (le projet).

%%shell
docker network ls
NETWORK ID     NAME              DRIVER    SCOPE
16cb1a2ff3ee   bridge            bridge    local
ae33eca811d2   host              host      local
1b7d57c60e4f   none              null      local
ddda7471e743   restjpa_backend   bridge    local
%%shell
docker volume ls
DRIVER    VOLUME NAME
local     ae5e7e841549fa3fa2216ee68a9c7be5e1ac3607ecfb11e71350cbb8b7812c54
local     restjpa_restjpa-pg-data

L’affichage de log d’un service ou de tous les services se fait la commande logs (ici limité à 5 lignes).

%%shell
cd sample-java/restjpa
docker compose logs --tail 5
db-1  | 2024-10-02 04:50:17.985 UTC [1] LOG:  listening on IPv4 address "0.0.0.0", port 5432
db-1  | 2024-10-02 04:50:17.985 UTC [1] LOG:  listening on IPv6 address "::", port 5432
db-1  | 2024-10-02 04:50:17.987 UTC [1] LOG:  listening on Unix socket "/var/run/postgresql/.s.PGSQL.5432"
db-1  | 2024-10-02 04:50:17.989 UTC [52] LOG:  database system was shut down at 2024-10-02 04:50:17 UTC
db-1  | 2024-10-02 04:50:17.992 UTC [1] LOG:  database system is ready to accept connections

On peut stopper, détruite ou relancer un service avec stop, rm et restart. La commande down détruit tous les containeurs et les réseaux. Attention, l’option -v supprime les volumes associés nommés ou non.

%%shell
cd sample-java/restjpa
docker compose down -v
 Container restjpa-db-1  Stopping
 Container restjpa-db-1  Stopped
 Container restjpa-db-1  Removing
 Container restjpa-db-1  Removed
 Volume restjpa_restjpa-pg-data  Removing
 Volume restjpa_restjpa-pg-data  Removed
 Network restjpa_backend  Removing
 Network restjpa_backend  Removed

L’autre service app est une application JPA/REST Java dont l’image docker est produite par sample-java/restjpa/Dockerfile. L’option build dans docker-compose.yml indique qu’il faut fabriquer l’image à partir du contexte courant (image sera alors son tag). La fabrication de l’image sera automatique au démarrage au besoin elle peut être faite manuellement avec docker compose build.

%%shell
cd sample-java/restjpa
docker compose --progress=quiet build
docker compose up --quiet-pull --detach
 Network restjpa_backend  Creating
 Network restjpa_backend  Created
 Network restjpa_frontend  Creating
 Network restjpa_frontend  Created
 Volume "restjpa_restjpa-pg-data"  Creating
 Volume "restjpa_restjpa-pg-data"  Created
 Container restjpa-db-1  Creating
 Container restjpa-db-1  Created
 Container restjpa-app-1  Creating
 Container restjpa-app-1  Created
 Container restjpa-db-1  Starting
 Container restjpa-db-1  Started
 Container restjpa-app-1  Starting
 Container restjpa-app-1  Started

Il est possible de contrôler l’ordre d’exécution des conteneurs avec la directive depends_on dans docker-compose.yml pour indiquer quels services doivent être démarrés avant un service. Dans l’exemple, la base de données doit être démarrée avant l’application.

%%shell

sleep 5
    
docker run --network restjpa_frontend \
           --rm --quiet curlimages/curl:7.88.1 \
               curl --verbose --silent \
                 -H "Accept: application/json" \
                 http://restjpa-app-1:8080/restjpa/persons
*   Trying 172.19.0.2:8080...
* Connected to restjpa-app-1 (172.19.0.2) port 8080 (#0)
> GET /restjpa/persons HTTP/1.1
> Host: restjpa-app-1:8080
> User-Agent: curl/7.88.1-DEV
> Accept: application/json
> 
< HTTP/1.1 200 OK
< Content-Type: application/json
< Content-Length: 187
< 
{ [187 bytes data]
* Connection #0 to host restjpa-app-1 left intact
[{"email":"a.b@ici.fr","firstname":"a","id":1,"lastname":"b"},{"email":"c.d@la.fr","firstname":"c","id":2,"lastname":"d"},{"email":"e.f@encore.com","firstname":"e","id":3,"lastname":"f"}]

Un exemple avancé

L’exemple ci-dessous présente un exemple un peu plus avancé en ajoutant un reverse proxy (https://traefik.io) pour gérer les points d’entrées de l’application (sécurité, répartition de charges, …).

services:
  app:
    labels:
      - traefik.enable=true
      - traefik.http.routers.app.rule=PathPrefix(`/restjpa{regex:$$|/.*}`)
      - traefik.http.services.app.loadbalancer.server.port=8080
    image: 'restjpa:latest'
    build:
      context: .
    depends_on:
      - db:
          condition: service_healthy
    environment:
      - DATASOURCE_URL=jdbc:postgresql://db:5432/demo-db
      - DATASOURCE_USERNAME=dba
      - DATASOURCE_PASSWORD=secret
    ports:
      - "8088:8080"
    networks:
      - frontend
      - backend

  db:
    image: 'postgres:15.2-alpine'
    ports:
      - "5432:5432"
    environment:
      - POSTGRES_USER=dba
      - POSTGRES_PASSWORD=secret
      - POSTGRES_DB=demo-db
    volumes:
      - restjpa-pg-data:/var/lib/postgresql/data
    networks:
      - backend
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5

  traefik:
    image: 'traefik:v2.9.8'
    restart: 'unless-stopped'
    depends_on:
      - db
      - app
    ports:
      - '10080:80'
      - '18080:8080'
    volumes:
      - '/var/run/docker.sock:/var/run/docker.sock:ro'
      - './traefik/traefik.toml:/traefik.toml'
    networks:
      - frontend

volumes:
  restjpa-pg-data:

networks:
  frontend:
    external: true
    name: restjpa_frontend
  backend:

La directive healthcheck de docker compose (qui existe dans le client) permet d’indique une commande à exécuter pour vérifier l’état de santé du conteneur (s’il fonctionne correctement et pas seulement s’il a démarré). Cela peut être utilisé comme condition pour depends_onpour ne pas simple lancer un autre service mais attendre qu’il soit fonctionnel.

%%shell
cd sample-java/restjpa
docker compose --file Docker-compose.yml up --quiet-pull --detach
 Container restjpa-db-1  Running
 Container restjpa-app-1  Running

Réutilisation