%printWithName false
String script="""
pwd
( cd sample-java/restjpa && docker compose down --remove-orphans )""";
.getKernelInstance().getMagics().applyCellMagic("shell",List.of(""),script); IJava
/home/jovyan/work/local
0
2024-10-02
Source | |
Branch |
|
Java |
|
Docker |
|
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.
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.
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.
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.
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).
NETWORK ID NAME DRIVER SCOPE
16cb1a2ff3ee bridge bridge local
ae33eca811d2 host host local
1b7d57c60e4f none null local
ddda7471e743 restjpa_backend bridge local
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).
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.
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"}]
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_on
pour ne pas simple lancer un autre service mais attendre qu’il soit fonctionnel.