Données persistantes
- Leçon 9
- 50 min
- Module 3 : Réseau, configuration et persistance
Leçon 9 sur 15 · L'essentiel de Docker et du nuage pour les développeurs en IADonnées persistantes
1. Comprendre les conteneurs
2. Construire l'image de l'application
3. Réseau, configuration et persistance
4. Les services de base du nuage
5. Déployer et nettoyer
Ce que vous saurez faire
Conservez les données importantes quand les conteneurs sont remplacés, et perdez seulement ce qui ne compte pas. Vous ajoutez un historique des classifications dans PostgreSQL. Vous exécutez l’API et sa base de données ensemble avec une seule commande.
Avant de commencerCe qu’il faut savoir et avoir sous la main
Ce que vous devez savoir
- Les conteneurs d’un réseau défini par l’utilisateur se trouvent par leur nom. Voir Ports et accès au service : de conteneur à conteneur.
- Les fichiers secrets et
API_KEY_FILE, dans Configuration à l’exécution : fichiers secrets. - L’état de fonctionnement et l’état de préparation (
/healthet/ready), dans Publier en toute sécurité : vitalité et disponibilité. - Le contrat révisé et
python -m ticket_api.export_contract, dans Garder la documentation exacte : exemple détaillé : le contrat révisé. requirements-run.txt, créé dans un conteneur, dans L’essentiel du Dockerfile : créer requirements-run.txt dans un conteneur.- Facultatif : une instruction SQL
SELECT(sélectionner), dans Sélectionner et filtrer : Une requête sur six lignes. La leçon explique la seule requête qu’elle utilise.
Ce qu’il vous faut sur votre ordinateur
- Le projet avec les nouveaux
config.pyetmain.py,secrets/api_key.txtet l’imageticket-api:1.1.0-dev. Voir Configuration à l’exécution : exercice guidé : paramètres et fichier secret. - L’environnement virtuel
.venvdans le projet, pour exécuter les tests. Il vient de Créer et déployer votre première API (Environnement et premier point de terminaison : étape 1 : créer le projet). - Une connexion Internet : la leçon télécharge un fichier Python et l’image PostgreSQL (environ 167 Mo).
- Si votre projet manque ou ne fonctionne plus, copiez l’instantané
end-of-m02, comme l’indique Utiliser un instantané. Faites ensuite les étapes 1 à 6 de l’exercice guidé de Configuration à l’exécution.
Testé avec Docker Desktop 4.81.0 (Docker Engine 29.6.1, Compose v5.2.0) sur macOS, avec une puce Apple. Les étapes pour Windows et Linux ne sont pas testées.
Les fichiers d’un conteneur sont jetables
Section intitulée « Les fichiers d’un conteneur sont jetables »Amira démarre l’API, écrit un fichier dans le conteneur et l’examine :
docker run -d --name ticket-api ticket-api:1.1.0-devdocker exec ticket-api sh -c "echo hello > /tmp/note.txt"docker exec ticket-api cat /tmp/note.txtdocker diff ticket-apiLa sortie (après l’identifiant du conteneur) :
helloC /tmpA /tmp/note.txtdocker diff énumère les changements dans la couche accessible en écriture du conteneur. C’est la mince couche au-dessus de l’image (Images et conteneurs en cours d’exécution). C signifie modifié, et A signifie ajouté. Elle remplace maintenant le conteneur par un nouveau, créé à partir de la même image, comme à chaque mise à jour :
docker rm -f ticket-apidocker run -d --name ticket-api ticket-api:1.1.0-devdocker exec ticket-api cat /tmp/note.txtLa sortie (après le nom du conteneur et le nouvel identifiant) :
cat: /tmp/note.txt: No such file or directoryLe fichier se trouvait dans la couche accessible en écriture de l’ancien conteneur. Cette couche a été supprimée avec le conteneur. Un conteneur est jetable. Vous le remplacez à chaque nouvelle version, changement de paramètre ou redémarrage sur un nouveau serveur. Tout ce qui doit survivre doit être stocké à l’extérieur.
docker rm -f ticket-apiOù stocker les données
Section intitulée « Où stocker les données »| Stockage | Ce que c’est | Survit à un nouveau conteneur? | À utiliser pour |
|---|---|---|---|
| La couche accessible en écriture | Fichiers qu’un conteneur écrit dans son propre système de fichiers | Non | Fichiers temporaires |
| Volume nommé | Stockage nommé que Docker gère à l’extérieur de tous les conteneurs | Oui, jusqu’à la suppression du volume | Fichiers d’une base de données sur votre ordinateur |
| Montage lié | Fichier ou dossier de votre ordinateur, visible dans le conteneur | Oui : c’est votre fichier | Fichier secret, comme dans la leçon précédente |
| Base de données externe | Serveur de base de données hors du conteneur de l’application, souvent un service infonuagique géré | Oui | Enregistrements, comme l’historique, en production |
| Stockage d’objets | Service infonuagique pour les fichiers, comme Azure Blob Storage | Oui | Fichiers téléversés, rapports, fichiers de modèles entraînés |
Un volume est un espace de stockage pour les conteneurs qui se trouve à l’extérieur de ceux-ci. Un conteneur le monte à un chemin, et les fichiers à ce chemin vont dans le volume. Un volume nommé reste toutefois sur un seul ordinateur. Un service infonuagique qui démarre votre conteneur sur différents serveurs ne peut pas l’utiliser. Dans le nuage, les enregistrements vont dans une base de données gérée, et les fichiers dans le stockage d’objets (module 4).
Cette leçon ajoute un historique à l’API : une ligne dans une base de données pour chaque classification. La base de données est PostgreSQL, dans son propre conteneur, avec ses fichiers dans un volume nommé.
L’historique conserve le résultat (catégorie, priorité, confiance, version du modèle, heure et identifiant de requête), pas le texte du billet. Un billet peut contenir des noms, des adresses ou des numéros de commande. L’historique sert à compter et à faire le suivi d’un identifiant de requête. Ni l’un ni l’autre n’exige le texte. Les données que vous ne stockez pas ne peuvent pas être divulguées.
Ajouter l’historique
Section intitulée « Ajouter l’historique »Le code de l’historique est fourni : le cours porte sur les conteneurs, pas sur Python. Vous ajoutez un module et le raccordez dans quatre fichiers.
Étape 1 : obtenir history.py
Section intitulée « Étape 1 : obtenir history.py »Téléchargez le fichier dans ticket_api :
curl -fsSL -o ticket_api/history.py https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C18/files/history.pycurl.exe -fsSL -o ticket_api\history.py https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C18/files/history.pycurl -fsSL -o ticket_api/history.py https://raw.githubusercontent.com/Nextia-Intelligence/nextia-learning-labs/main/C18/files/history.py-f fait échouer curl lors d’une erreur HTTP au lieu d’enregistrer la page d’erreur. Si le téléchargement ne fonctionne pas, copiez le code ci-dessous dans ticket_api/history.py.
Afficher ticket_api/history.py
"""Keep a record of the classifications in PostgreSQL.
The record has the result of each classification, never the ticket's text.History is optional: without DATABASE_URL the API classifies tickets andkeeps no record."""
import psycopgfrom psycopg.rows import dict_row
CREATE_TABLE = """CREATE TABLE IF NOT EXISTS classifications ( id bigserial PRIMARY KEY, request_id text NOT NULL, category text NOT NULL, priority smallint NOT NULL, confidence real NOT NULL, model_version text NOT NULL, created_at timestamptz NOT NULL DEFAULT now())"""
class HistoryUnavailable(Exception): """History is off, or the database does not answer."""
class History: def __init__(self, url: str): self.url = url self.table_ready = False
def connect(self) -> psycopg.Connection: try: return psycopg.connect(self.url, connect_timeout=3, row_factory=dict_row) except psycopg.OperationalError as error: raise HistoryUnavailable("the database does not answer") from error
def setup(self) -> None: """Make the table if it does not exist.""" with self.connect() as conn: conn.execute(CREATE_TABLE) self.table_ready = True
def check(self) -> None: with self.connect() as conn: conn.execute("SELECT 1")
def add(self, request_id: str, result: dict) -> None: if not self.table_ready: self.setup() with self.connect() as conn: conn.execute( "INSERT INTO classifications" " (request_id, category, priority, confidence, model_version)" " VALUES (%s, %s, %s, %s, %s)", ( request_id, result["category"], result["priority"], result["confidence"], result["model_version"], ), )
def recent(self, limit: int) -> list[dict]: if not self.table_ready: self.setup() with self.connect() as conn: return conn.execute( "SELECT request_id, category, priority, confidence, model_version," " created_at FROM classifications ORDER BY id DESC LIMIT %s", (limit,), ).fetchall()Voici ce qu’il fait : il se connecte à PostgreSQL avec psycopg, le pilote PostgreSQL pour Python. Il attend au maximum 3 secondes. Il crée la table classifications si elle n’existe pas. add (ajouter) écrit une ligne, recent (récent) lit les lignes les plus récentes, et check (vérifier) sert à /ready. Tout problème de connexion devient HistoryUnavailable. Le reste de l’application n’a donc besoin de connaître qu’une seule erreur.
Étape 2 : le nouveau paquet et la version
Section intitulée « Étape 2 : le nouveau paquet et la version »Ajoutez psycopg à requirements.txt, sous les paquets nécessaires à l’exécution de l’API :
# Needed to run the APIfastapi[standard]==0.142.2psycopg[binary]==3.3.6Dans pyproject.toml, changez la version et ajoutez le même paquet :
version = "1.0.0"version = "1.1.0"description = "Classifies support tickets into a category and a priority."requires-python = ">=3.12"dependencies = ["fastapi[standard]==0.142.2"]dependencies = ["fastapi[standard]==0.142.2", "psycopg[binary]==3.3.6"][binary] installe une version prête à l’emploi de psycopg qui contient la bibliothèque cliente PostgreSQL. L’image allégée n’a donc besoin d’aucun paquet système supplémentaire.
Étape 3 : les modèles et l’erreur
Section intitulée « Étape 3 : les modèles et l’erreur »Dans ticket_api/models.py, ajoutez une importation au début, puis deux modèles après CategoryList :
from datetime import datetimefrom typing import Literalclass HistoryItem(BaseModel): """One classification that the API made. It has no ticket text."""
request_id: str = Field(description="The request that made the classification.") category: Category priority: int = Field(ge=1, le=3) confidence: float = Field(ge=0, le=1) model_version: str created_at: datetime = Field(description="When the API made it (UTC).")
class HistoryList(BaseModel): items: list[HistoryItem] = Field(description="The newest classification first.")Dans ticket_api/errors.py, importez l’erreur et ajoutez un gestionnaire après celui de ClassifierUnavailable :
from ticket_api.classifier import ClassifierUnavailablefrom ticket_api.history import HistoryUnavailablefrom ticket_api.models import ErrorDetail, ErrorResponse @app.exception_handler(HistoryUnavailable) async def history_unavailable(request: Request, exc: HistoryUnavailable): logger.warning("history unavailable: %s id=%s", exc, request.state.request_id) return error_response( request, 503, "history_unavailable", "The history is not available. Try again later.", )Étape 4 : enregistrer chaque résultat et les énumérer
Section intitulée « Étape 4 : enregistrer chaque résultat et les énumérer »Dans ticket_api/routes.py, faites trois changements : les importations, la fin de classify et un nouveau point de terminaison en fin de fichier :
import asynciofrom typing import Annotated, get_args
from fastapi import APIRouter, Depends, Requestimport logging
from fastapi import APIRouter, Depends, Query, Requestfrom fastapi.concurrency import run_in_threadpool
from ticket_api.classifier import ClassifierUnavailable, KeywordClassifierfrom ticket_api.history import HistoryUnavailablefrom ticket_api.models import ( Category, CategoryList, Classification, ErrorResponse, HistoryList, TicketIn,)from ticket_api.security import require_api_key
logger = logging.getLogger("ticket_api")
router = APIRouter(prefix="/v1", tags=["tickets"]) return Classification( result = Classification( category=prediction.category, priority=prediction.priority, confidence=prediction.confidence, model_version=classifier.version, ) history = request.app.state.history if history is not None: try: await run_in_threadpool( history.add, request.state.request_id, result.model_dump() ) except HistoryUnavailable: logger.warning("history not saved id=%s", request.state.request_id) return result
@router.get( "/history", tags=["history"], summary="List recent classifications", description=( "Returns the newest classifications first. History is off when the " "server has no DATABASE_URL." ), responses={ 401: { "model": ErrorResponse, "description": "The API key is missing or wrong.", }, 503: { "model": ErrorResponse, "description": "History is off, or the database does not answer.", }, }, dependencies=[Depends(require_api_key)],)async def list_history( request: Request, limit: Annotated[int, Query(ge=1, le=100, description="How many to return.")] = 10,) -> HistoryList: history = request.app.state.history if history is None: raise HistoryUnavailable("history is off: DATABASE_URL is not set") items = await run_in_threadpool(history.recent, limit) return HistoryList(items=items)Regardez le try (essayer) dans classify (classifier). Si la base de données ne répond pas, la classification renvoie quand même 200. Le journal reçoit alors un avertissement. C’est un choix de conception. La classification est la tâche principale de l’API. L’historique est un enregistrement secondaire : son échec ne doit donc pas interrompre la tâche principale. Mais cet échec doit être visible : dans le journal et dans /ready (prochaine étape). /v1/history exige la clé, comme /v1/classify.
Étape 5 : le raccorder dans main.py
Section intitulée « Étape 5 : le raccorder dans main.py »from ticket_api.config import Settings, SettingsError, load_settingsfrom ticket_api.errors import add_error_handlers, error_responsefrom ticket_api.history import History, HistoryUnavailablefrom ticket_api.models import ErrorResponse, Health, Readinessapp = FastAPI( title="Ticket Classifier API", version="1.0.0", version="1.1.0", description=DESCRIPTION, openapi_tags=[ {"name": "tickets", "description": "Classify support tickets."}, {"name": "history", "description": "See recent classifications."}, {"name": "health", "description": "Check that the service runs."}, ],app.state.classifier = make_classifier(settings.classifier_mode)app.state.history = Noneif settings.database_url: app.state.history = History(settings.database_url) try: app.state.history.setup() except HistoryUnavailable: logger.warning("the database does not answer yet: history is not ready")else: logger.info("DATABASE_URL is not set: history is off")def ready() -> Readiness: app.state.classifier.predict("readiness check") if app.state.history is not None: app.state.history.check() return Readiness(status="ready")Sans DATABASE_URL, l’API fonctionne comme avant, sans historique. Si la base de données n’est pas prête au démarrage de l’API, l’API démarre quand même. Elle crée la table plus tard, à la première requête.
Étape 6 : les tests
Section intitulée « Étape 6 : les tests »Ajoutez le fichier tests/test_history.py. Il contient 8 tests : 4 pour l’historique et 4 pour les paramètres de la leçon précédente. FakeHistory garde les lignes dans une liste Python. Les tests n’ont donc pas besoin de base de données.
Afficher tests/test_history.py
from datetime import datetime, timezone
import pytest
from ticket_api.config import SettingsError, load_settingsfrom ticket_api.history import HistoryUnavailable
class FakeHistory: """Keeps the history in a list, so the tests need no database."""
def __init__(self, down=False): self.rows = [] self.down = down
def check(self): if self.down: raise HistoryUnavailable("down")
def add(self, request_id, result): self.check() created_at = datetime.now(timezone.utc) self.rows.append({"request_id": request_id, **result, "created_at": created_at})
def recent(self, limit): self.check() return list(reversed(self.rows))[:limit]
@pytest.fixturedef history_client(make_client): def make(down=False): client = make_client() client.app.state.history = FakeHistory(down) return client
return make
def test_history_is_off_without_database_url(client): response = client.get("/v1/history") assert response.status_code == 503 assert response.json()["error"]["code"] == "history_unavailable"
def test_classification_is_recorded_without_the_text(history_client): client = history_client() response = client.post("/v1/classify", json={"subject": "Parcel lost"}) items = client.get("/v1/history").json()["items"] assert len(items) == 1 assert items[0]["category"] == "shipping" assert items[0]["request_id"] == response.headers["X-Request-ID"] assert "subject" not in items[0]
def test_history_limit(history_client): client = history_client() for _ in range(3): client.post("/v1/classify", json={"subject": "Refund"}) assert len(client.get("/v1/history?limit=2").json()["items"]) == 2 assert client.get("/v1/history?limit=0").status_code == 422
def test_database_down_still_classifies_but_is_not_ready(history_client): client = history_client(down=True) assert client.post("/v1/classify", json={"subject": "Refund"}).status_code == 200 assert client.get("/ready").status_code == 503 assert client.get("/v1/history").status_code == 503
def test_api_key_from_a_file(tmp_path, monkeypatch): key_file = tmp_path / "api_key" key_file.write_text("from-a-file\n") monkeypatch.setenv("API_KEY_FILE", str(key_file)) assert load_settings().api_key == "from-a-file"
def test_missing_secret_file_stops_the_app(monkeypatch): monkeypatch.setenv("API_KEY_FILE", "/no/such/file") with pytest.raises(SettingsError): load_settings()
def test_required_api_key(monkeypatch): monkeypatch.delenv("API_KEY", raising=False) monkeypatch.delenv("API_KEY_FILE", raising=False) monkeypatch.setenv("REQUIRE_API_KEY", "true") with pytest.raises(SettingsError): load_settings()
def test_secrets_are_not_in_the_settings_repr(monkeypatch): monkeypatch.setenv("API_KEY", "do-not-print") monkeypatch.setenv("DATABASE_URL", "postgresql://u:do-not-print@db/x") assert "do-not-print" not in repr(load_settings())Dans l’environnement virtuel, Amira installe le nouveau paquet, recrée le fichier de verrouillage et exécute les tests :
python -m pip install -r requirements.txtpython -m pip freeze > requirements-lock.txtpython -m pytestLa sortie (la fin) :
FAILED tests/test_contract.py::test_openapi_matches_the_reviewed_contract - A...1 failed, 24 passed in 0.24sPrédisez : pourquoi le test du contrat échoue-t-il? L’API a un nouveau point de terminaison et une nouvelle version. Le document OpenAPI a donc changé. Ce changement est voulu. Elle exporte donc à nouveau le contrat, comme dans le cours sur les API, puis relance les tests :
python -m ticket_api.export_contractpython -m pytestLa sortie (la fin) :
Wrote contract/openapi.json...25 passed in 0.30sÉtape 7 : les paquets de l’image
Section intitulée « Étape 7 : les paquets de l’image »L’image installe les paquets à partir de requirements-run.txt. Ce fichier doit donc aussi contenir psycopg. Recréez-le dans un conteneur, comme dans L’essentiel du Dockerfile : créer requirements-run.txt dans un conteneur. Cette fois, incluez psycopg dans la commande :
docker run --rm python:3.14.8-slim-trixie sh -c 'pip install -q --root-user-action=ignore --disable-pip-version-check "fastapi[standard-no-fastapi-cloud-cli]==0.142.2" "psycopg[binary]==3.3.6" && pip freeze' > requirements-run.txtVérifiez le résultat : wc -l requirements-run.txt et grep psycopg requirements-run.txt (PowerShell : (Get-Content requirements-run.txt).Count et Select-String psycopg requirements-run.txt). La sortie :
52 requirements-run.txtpsycopg==3.3.6psycopg-binary==3.3.652 lignes : les 50 du module 2 et les deux paquets psycopg.
Écrire compose.yaml
Section intitulée « Écrire compose.yaml »Le projet a maintenant besoin de deux conteneurs qui travaillent ensemble : l’API et PostgreSQL. Vous pourriez les démarrer avec deux commandes docker run, un réseau, un volume et plusieurs options. Docker Compose fait tout cela à partir d’un seul fichier. Compose est une partie de Docker qui démarre un groupe de conteneurs à partir d’un fichier nommé compose.yaml. Chaque conteneur dans ce fichier est un service.
D’abord, le mot de passe de la base de données. Il va dans .env, qui reste hors de Git. Un modèle sans véritable secret va dans Git pour que les autres sachent quelles valeurs définir. Créez .env.example :
# Copy this file to .env and change the value. Never commit .env.POSTGRES_PASSWORD=change-me-local-onlyCréez ensuite .env à partir de ce fichier. Il remplace le .env de la leçon précédente : les paramètres de l’API sont maintenant dans compose.yaml.
cp .env.example .envCopy-Item .env.example .envcp .env.example .envOuvrez .env et remplacez le mot de passe par un nouveau. Le mot de passe protège seulement la base de données sur votre ordinateur. Utilisez tout de même un mot de passe que vous n’utilisez nulle part ailleurs.
Créez ensuite compose.yaml dans le dossier du projet :
# The ticket API and its database, on your computer.# Start: docker compose up -d --build# Stop: docker compose down (keeps the data)# Reset: docker compose down --volumes (deletes the data)
services: api: build: . image: ticket-api:1.1.0 ports: - "127.0.0.1:8000:8000" environment: API_KEY_FILE: /run/secrets/api_key DATABASE_URL: postgresql://tickets:${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}@db:5432/tickets REQUIRE_API_KEY: "true" secrets: - api_key depends_on: db: condition: service_healthy
db: image: postgres:18.6-trixie environment: POSTGRES_USER: tickets POSTGRES_DB: tickets POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} volumes: - pgdata:/var/lib/postgresql healthcheck: test: ["CMD", "pg_isready", "-U", "tickets", "-d", "tickets"] interval: 2s timeout: 3s retries: 15
secrets: api_key: file: ./secrets/api_key.txt
volumes: pgdata:Ligne par ligne :
| Lignes | Signification |
|---|---|
build: . et image: ticket-api:1.1.0 |
Construire l’image à partir du Dockerfile de ce dossier et lui donner l’étiquette ticket-api:1.1.0. |
ports: "127.0.0.1:8000:8000" |
Comme -p 127.0.0.1:8000:8000 : seul cet ordinateur peut se connecter. Les guillemets empêchent YAML de lire la valeur comme un nombre. |
environment: |
Les paramètres de l’API, comme avec -e. REQUIRE_API_KEY: "true" : ici, la clé est obligatoire. |
DATABASE_URL: ...@db:5432/tickets |
L’adresse de la base de données. Le nom d’hôte est db, le nom du service. Compose place les deux services sur un même réseau. Chaque service y porte son nom, comme dans Ports et accès au service. |
${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env} |
Compose remplace ceci par la valeur de .env. :? signifie obligatoire : sans cette valeur, Compose s’arrête avec ce message (définir POSTGRES_PASSWORD dans .env). |
secrets: - api_key |
Monter le secret Compose api_key dans le conteneur, en lecture seule, à /run/secrets/api_key. API_KEY_FILE pointe vers ce chemin. |
depends_on: db: condition: service_healthy |
Démarrer l’API seulement quand la vérification de l’état de santé de la base de données réussit. |
image: postgres:18.6-trixie |
L’image officielle de PostgreSQL, PostgreSQL 18.6 sur Debian 13, fixée à une étiquette précise. |
POSTGRES_USER, POSTGRES_DB, POSTGRES_PASSWORD |
L’image crée cet utilisateur et cette base de données au premier démarrage avec un volume vide. |
volumes: - pgdata:/var/lib/postgresql |
Monter le volume nommé pgdata à /var/lib/postgresql, où PostgreSQL 18 garde ses fichiers. Voir Le chemin de montage de PostgreSQL 18. |
healthcheck: |
Une vérification de l’état de santé est une commande que Docker exécute régulièrement dans le conteneur pour vérifier le service. pg_isready demande à PostgreSQL s’il accepte les connexions : toutes les 2 secondes, au maximum 15 fois. |
secrets: api_key: file: |
Le secret Compose vient de ./secrets/api_key.txt, le fichier de la leçon précédente. |
volumes: pgdata: |
Déclarer le volume nommé. Compose le crée la première fois. |
Le fichier .env sert à Compose, pas aux conteneurs. Compose le lit pour remplir ${...} dans compose.yaml. Les conteneurs reçoivent seulement ce que environment: énumère. Le conteneur de l’API ne reçoit donc pas POSTGRES_PASSWORD, seulement l’URL qui le contient.
Prédisez : que se passe-t-il si .env manque? Amira a essayé avant de créer le fichier :
error while interpolating services.api.environment.DATABASE_URL: required variable POSTGRES_PASSWORD is missing a value: Set POSTGRES_PASSWORD in .envCompose s’est arrêté avant de créer quoi que ce soit.
Démarrer, vérifier et arrêter
Section intitulée « Démarrer, vérifier et arrêter »Description textuelle du diagramme
Votre ordinateur est l’hôte. curl ou un navigateur sur cet ordinateur appelle http://127.0.0.1:8000, le port publié. Docker transmet l’appel au port 8000 du conteneur api. Un téléphone sur le Wi-Fi ne peut pas se connecter, car le port est publié seulement sur 127.0.0.1. Le conteneur api (ticket-api:1.1.0, qui écoute sur 0.0.0.0:8000) et le conteneur db (postgres:18.6-trixie) partagent le réseau Compose ticket-api_default. L’API appelle la base de données à db:5432. Le port 5432 n’est pas publié. Seuls les conteneurs de ce réseau peuvent donc y accéder. Deux éléments sont à l’extérieur des conteneurs : le fichier de l’hôte secrets/api_key.txt, monté en lecture seule dans api à /run/secrets/api_key, et le volume nommé ticket-api_pgdata, monté dans db à /var/lib/postgresql. Les conteneurs sont remplacés. Le volume et le fichier sur l’hôte restent.
Amira démarre les deux services. --build construit d’abord l’image de l’API :
docker compose up -d --buildLa sortie (les lignes de construction sont abrégées) :
Image ticket-api:1.1.0 Building... Image ticket-api:1.1.0 Built Network ticket-api_default Creating Network ticket-api_default Created Volume ticket-api_pgdata Creating Volume ticket-api_pgdata Created Container ticket-api-db-1 Creating Container ticket-api-db-1 Created Container ticket-api-api-1 Creating Container ticket-api-api-1 Created Container ticket-api-db-1 Starting Container ticket-api-db-1 Started Container ticket-api-db-1 Waiting Container ticket-api-db-1 Healthy Container ticket-api-api-1 Starting Container ticket-api-api-1 StartedLa première fois, Compose télécharge aussi postgres:18.6-trixie. Les noms viennent du nom du projet, qui est le nom du dossier : ticket-api. Le réseau est donc ticket-api_default, le volume est ticket-api_pgdata, et les conteneurs sont ticket-api-db-1 et ticket-api-api-1. Regardez l’ordre : la base de données d’abord, puis Waiting (en attente) jusqu’à l’état Healthy (sain), puis l’API.
Description textuelle du diagramme
Vous exécutez docker compose up -d --build. Compose construit ticket-api:1.1.0 et crée le réseau et le volume. Il démarre db avec le volume à /var/lib/postgresql. Puis, toutes les 2 secondes et au plus 15 fois, il exécute la vérification de santé pg_isready dans db. Celle-ci répond « pas prêt », puis « prêt ». Quand db est sain, Compose démarre api, avec le secret à /run/secrets/api_key. L’API se connecte à db:5432 et crée la table. Compose affiche que ticket-api-api-1 a démarré. Votre appel curl 127.0.0.1:8000/ready reçoit {"status":"ready"}.
docker compose psLa sortie :
NAME IMAGE COMMAND SERVICE CREATED STATUS PORTSticket-api-api-1 ticket-api:1.1.0 "fastapi run --host …" api 3 seconds ago Up Less than a second 127.0.0.1:8000->8000/tcpticket-api-db-1 postgres:18.6-trixie "docker-entrypoint.s…" db 3 seconds ago Up 2 seconds (healthy) 5432/tcpLe port 5432 de la base de données n’est pas publié : seule l’API, sur le même réseau, peut y accéder. docker compose logs api affiche le journal de l’API, avec les mêmes lignes de démarrage qu’avant. Elle vérifie que l’API est prête, envoie deux billets, puis lit l’historique :
curl -s http://127.0.0.1:8000/readycurl -s -X POST http://127.0.0.1:8000/v1/classify -H "Content-Type: application/json" -H "X-API-Key: local-dev-key" -d @requests/billing.jsoncurl -s -X POST http://127.0.0.1:8000/v1/classify -H "Content-Type: application/json" -H "X-API-Key: local-dev-key" -d '{"subject":"Parcel lost"}'curl -s http://127.0.0.1:8000/v1/history -H "X-API-Key: local-dev-key"La sortie (une ligne par commande; la dernière est longue) :
{"status":"ready"}{"category":"billing","priority":1,"confidence":0.9,"model_version":"keywords-1.0"}{"category":"shipping","priority":1,"confidence":0.7,"model_version":"keywords-1.0"}{"items":[{"request_id":"1fe5dfeafae4","category":"shipping","priority":1,"confidence":0.7,"model_version":"keywords-1.0","created_at":"2026-10-07T16:58:28.691060Z"},{"request_id":"d2ef963e6b97","category":"billing","priority":1,"confidence":0.9,"model_version":"keywords-1.0","created_at":"2026-10-07T16:58:28.663288Z"}]}Les plus récents d’abord, avec l’heure en UTC, sans le texte des billets. PowerShell peut modifier les guillemets dans -d '{"subject":"Parcel lost"}' avant que curl.exe les reçoive. Si vous obtenez 422, enregistrez le JSON dans un fichier à l’extérieur du projet, par exemple $HOME\parcel.json. Utilisez ensuite -d "@$HOME\parcel.json". (Non testé sur Windows.)
Pour consulter la base de données, elle exécute psql, le client en ligne de commande de PostgreSQL, dans le conteneur db. docker compose exec est l’équivalent de docker exec avec le nom du service :
docker compose exec db psql -U tickets -d tickets -c "SELECT id, request_id, category, priority, created_at FROM classifications;"La sortie :
id | request_id | category | priority | created_at----+--------------+----------+----------+------------------------------- 1 | d2ef963e6b97 | billing | 1 | 2026-10-07 16:58:28.663288+00 2 | 1fe5dfeafae4 | shipping | 1 | 2026-10-07 16:58:28.69106+00(2 rows)Les deux mêmes lignes, dans l’ordre de leur écriture. Le SELECT demande cinq colonnes de chaque ligne de la table classifications.
Enfin, voici ce que Docker conserve sur le conteneur de l’API :
docker inspect ticket-api-api-1 --format "{{json .Config.Env}}"La sortie (abrégée) :
["REQUIRE_API_KEY=true","API_KEY_FILE=/run/secrets/api_key","DATABASE_URL=postgresql://tickets:change-me-local-only@db:5432/tickets",...]La clé est un fichier, comme prévu. Mais le mot de passe de la base de données est dans DATABASE_URL, en texte clair. Ici, c’est un mot de passe local qui protège seulement une base de données sans port publié. Dans le nuage, le mot de passe serait un secret de la plateforme. L’application pourrait aussi utiliser une identité plutôt qu’un mot de passe (module 4). L’API peut également lire l’URL dans un fichier avec DATABASE_URL_FILE, de la même façon qu’avec API_KEY_FILE.
Pour tout arrêter :
docker compose downdocker volume ls --filter name=ticket-apiLa sortie :
Container ticket-api-api-1 Stopping Container ticket-api-api-1 Stopped Container ticket-api-api-1 Removing Container ticket-api-api-1 Removed Container ticket-api-db-1 Stopping Container ticket-api-db-1 Stopped Container ticket-api-db-1 Removing Container ticket-api-db-1 Removed Network ticket-api_default Removing Network ticket-api_default RemovedDRIVER VOLUME NAMElocal ticket-api_pgdatadown supprime les conteneurs et le réseau, mais pas le volume. Le prochain docker compose up -d crée de nouveaux conteneurs, et l’historique est toujours là.
Le chemin de montage de PostgreSQL 18
Section intitulée « Le chemin de montage de PostgreSQL 18 »Beaucoup d’anciens tutoriels montent le volume de PostgreSQL à /var/lib/postgresql/data. Depuis PostgreSQL 18, l’image officielle conserve ses fichiers dans /var/lib/postgresql/18/docker, un dossier pour chaque version majeure. Une mise à niveau ultérieure peut ainsi conserver les anciens et les nouveaux fichiers côte à côte. L’image déclare le dossier parent, /var/lib/postgresql, comme son volume :
docker image inspect postgres:18.6-trixie --format "{{json .Config.Volumes}}"docker compose exec db ls /var/lib/postgresql/18La sortie :
{"/var/lib/postgresql":{}}dockerPrédisez : que se passe-t-il avec l’ancien chemin, pgdata:/var/lib/postgresql/data? Amira l’a essayé dans une copie de compose.yaml, avec un nouveau volume vide :
Container ticket-api-db-1 Waiting Container ticket-api-db-1 Error dependency db failed to startdependency failed to start: container ticket-api-db-1 exited (1)Le conteneur de la base de données s’est arrêté, et l’API n’a jamais démarré. docker compose ps -a affiche son état : Created (créé). Le journal de la base de données :
db-1 | Error: in 18+, these Docker images are configured to store database data in adb-1 | format which is compatible with "pg_ctlcluster" (specifically, usingdb-1 | major-version-specific directory names). This better reflects howdb-1 | PostgreSQL itself works, and how upgrades are to be performed....db-1 | Counter to that, there appears to be PostgreSQL data in:db-1 | /var/lib/postgresql/data (unused mount/volume)...db-1 | The suggested container configuration for 18+ is to place a single mountdb-1 | at /var/lib/postgresql which will then place PostgreSQL data in adb-1 | subdirectory, allowing usage of "pg_upgrade --link" without mount pointdb-1 | boundary issues.Le message parle d’anciennes données, mais le volume était vide : l’image refuse tout montage à l’ancien chemin. Montez le volume à /var/lib/postgresql, comme dans le compose.yaml de ce cours. L’image échoue avec un message clair, ce qui est bien. Une base de données qui démarrerait en écrivant ses fichiers hors du volume les perdrait sans message.
Exercice guidé : l’API et sa base de données
Section intitulée « Exercice guidé : l’API et sa base de données »- Faites les étapes 1 à 7 de Ajouter l’historique. Vous devriez obtenir
25 passed(25 tests réussis) et 52 lignes dansrequirements-run.txt. - Créez
.env.example,.envetcompose.yamlcomme dans Écrire compose.yaml. Changez le mot de passe dans.env. - Exécutez
git status. Vérifiez que.envetsecrets/ne sont pas dans la liste, mais que.env.exampleetcompose.yamly sont. - Démarrez les services :
docker compose up -d --build. - Exécutez
docker compose ps. Attendez quedbaffiche(healthy)(sain) et queapiafficheUp(en cours d’exécution). - Appelez
/ready. Vous devriez obtenir{"status":"ready"}. Si curl affiche(52) Empty reply from server(réponse vide du serveur), l’API démarre encore : attendez quelques secondes. - Envoyez deux billets à
/v1/classifyavec la clé, puis appelez/v1/historyavec la clé. Vous devriez obtenir deux éléments, le plus récent d’abord. - Exécutez la requête
psqldans le servicedb. Vous devriez obtenir les deux mêmes lignes. - Remplacez le
README.mddu projet par la version ci-dessous. - Laissez les services fonctionner pour l’exercice du module.
Afficher README.md
# ticket-api
A small web API that classifies support tickets. It runs in a container, alone or with a PostgreSQL database that keeps a history of the classifications.
## Run it in a container
You need Docker. The API listens on port 8000 in the container.
```shdocker build -t ticket-api:1.1.0 .docker run --rm -p 127.0.0.1:8000:8000 -e API_KEY=local-dev-key ticket-api:1.1.0```
Check it: `curl http://127.0.0.1:8000/health` gives `{"status":"ok"}`.
## Run it with the database
```shcp .env.example .env # then change the password in .envmkdir -p secretsprintf 'local-dev-key' > secrets/api_key.txtdocker compose up -d --build```
- `docker compose down` stops and removes the containers. The data stays in the volume `ticket-api_pgdata`.- `docker compose down --volumes` also deletes the data.
## Settings
| Setting | Required | Default | What it does ||---|---|---|---|| `API_KEY` or `API_KEY_FILE` | When `REQUIRE_API_KEY` is `true` | none | The key that clients send in `X-API-Key`. `API_KEY_FILE` is the path of a file that holds it. || `REQUIRE_API_KEY` | No | `false` | `true`: the API does not start without a key. || `DATABASE_URL` or `DATABASE_URL_FILE` | No | none | A PostgreSQL URL. Without it, history is off. || `ALLOWED_ORIGINS` | No | none | Sites that may call the API from a browser, separated by commas. || `SHOW_DOCS` | No | `true` | `false` hides `/docs`, `/redoc` and `/openapi.json`. || `CLASSIFIER_MODE`, `CLASSIFIER_TIMEOUT`, `LOG_LEVEL` | No | `keywords`, `2.0`, `INFO` | As in the API course. |
Never put a key or a password in the Dockerfile, in `compose.yaml` or in Git. `.env` and `secrets/` are in `.gitignore` and `.dockerignore`.
## Work on the code
```shpython3 -m venv .venv # Windows: python -m venv .venvsource .venv/bin/activate # Windows: .venv\Scripts\Activate.ps1python -m pip install -r requirements-lock.txtpython -m pytest```
`requirements-run.txt` is the exact list of packages in the image. Make it again inside a container, so that it is the same on every computer:
```shdocker run --rm python:3.14.8-slim-trixie sh -c 'pip install -q --root-user-action=ignore --disable-pip-version-check "fastapi[standard-no-fastapi-cloud-cli]==0.142.2" "psycopg[binary]==3.3.6" && pip freeze' > requirements-run.txt```À vous de jouer : la base de données s’arrête
Section intitulée « À vous de jouer : la base de données s’arrête »Changez une condition : arrêtez seulement la base de données avec docker compose stop db. Avant de tester, prédisez le code d’état de /health, de /ready et de POST /v1/classify (avec la clé). Ensuite, testez et lisez les dernières lignes de docker compose logs api. Redémarrez la base de données avec docker compose start db.
Afficher la réponse
{"status":"ok"}200{"error":{"code":"history_unavailable","message":"The history is not available. Try again later.","request_id":"57da74011ff3","fields":[]}}503{"category":"billing","priority":1,"confidence":0.7,"model_version":"keywords-1.0"}200Il s’agit de /health, de /ready et d’une classification de {"subject":"Charged twice for March"}, chacun avec -w "\n%{http_code}\n". Le processus fonctionne (/health 200). Il n’est pas prêt, car l’historique ne répond pas (/ready 503). Il classe quand même (200). Le journal montre l’effet secondaire :
WARNING ticket_api: history unavailable: the database does not answer id=57da74011ff3INFO ticket_api: GET /ready 503 18ms id=57da74011ff3...WARNING ticket_api: history not saved id=5ecc59a5fbbfINFO ticket_api: POST /v1/classify 200 8ms id=5ecc59a5fbbfAprès docker compose start db, /ready renvoie de nouveau {"status":"ready"}. La classification faite pendant la panne n’est pas dans l’historique : l’API ne la conserve pas pour plus tard. C’est pourquoi /ready doit signaler le problème. Dans le module 5, la plateforme infonuagique lit /ready et n’envoie aucun trafic à une copie qui n’est pas prête. Elle ne la redémarre toutefois pas.
Erreur courante
L'historique est vide après docker compose down et up
Symptôme : l’historique fonctionnait et avait même survécu au redémarrage du conteneur de la base de données. Après docker compose down et docker compose up -d, /v1/history renvoie {"items":[]}. docker volume ls affiche un volume dont le long nom contient des chiffres et des lettres, mais aucun ticket-api_pgdata.
Cause : les données n’ont jamais été dans un volume nommé. Dans ce test, Amira a supprimé les deux sections volumes: de compose.yaml. L’image PostgreSQL déclare /var/lib/postgresql comme un volume. Docker a donc créé un volume anonyme : un volume sans nom, pour ce seul conteneur. docker compose up -d --force-recreate db le réutilise, donc les données semblaient en sécurité. docker compose down supprime le conteneur. Le prochain up crée un nouveau conteneur avec un nouveau volume anonyme. L’ancien reste sur votre ordinateur, inutilisé. Sans aucun volume, les données disparaissent encore plus tôt. Les fichiers dans la couche inscriptible d’un conteneur disparaissent avec lui.
Solution :
- Attribuez un volume nommé à toutes les données à conserver, aux deux endroits :
- pgdata:/var/lib/postgresqlsous le service, etpgdata:sousvolumes:à la fin du fichier. - Vérifiez après
docker compose up -d:docker volume ls --filter name=ticket-apidoit afficherticket-api_pgdata. - Trouvez l’ancien volume anonyme avec
docker inspect <container> --format "{{json .Mounts}}"avant de supprimer le conteneur. Supprimez-le avecdocker volume rm <its name>. Supprimez seulement les volumes que vous savez être les vôtres.
Pour un vrai service d’IA : la même erreur fait perdre plus qu’un historique. Pensez à un service qui écrit des commentaires, des étiquettes ou un index vectoriel dans les fichiers de son conteneur. Il fonctionne parfaitement dans une démonstration, puis perd tout à la première mise à jour. Décidez pour chaque fichier : jetable, dans un volume sur un ordinateur, ou dans une base de données ou un stockage d’objets qui survit à tous les conteneurs.
Vérifiez votre compréhension
Quelles commandes suppriment les conteneurs, et quelle commande supprime aussi l'historique?
Afficher la réponse
docker compose down arrête et supprime les conteneurs et le réseau. Le volume nommé ticket-api_pgdata reste, donc l’historique survit. docker compose down --volumes supprime aussi le volume, et avec lui l’historique. docker compose up -d --force-recreate api remplace seulement le conteneur de l’API. Ni le volume ni la base de données ne sont modifiés.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Pourquoi DATABASE_URL utilise-t-il le nom d'hôte db, et non localhost ou 127.0.0.1?
Afficher la réponse
L’URL est utilisée dans le conteneur de l’API. À cet endroit, localhost et 127.0.0.1 désignent le conteneur de l’API lui-même, qui n’a pas de base de données. Compose place les deux services sur le réseau ticket-api_default, où chaque service est accessible par son nom. Ainsi, db:5432 permet d’atteindre le conteneur de la base de données. Le port 5432 n’est pas publié, donc les programmes à l’extérieur du réseau ne peuvent pas accéder à la base de données.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Exercice du module
Section intitulée « Exercice du module »Exemple détaillé. L’API et la base de données d’Amira fonctionnent depuis l’exercice guidé. Elle prouve quelles données survivent au remplacement du conteneur de l’API :
docker compose exec api sh -c "echo hello > /tmp/note.txt"docker compose exec api cat /tmp/note.txtdocker compose up -d --force-recreate apidocker compose exec api cat /tmp/note.txtcurl -s http://127.0.0.1:8000/v1/history -H "X-API-Key: local-dev-key"La sortie (l’historique est abrégé) :
hello Container ticket-api-db-1 Running Container ticket-api-api-1 Recreate Container ticket-api-api-1 Recreated Container ticket-api-db-1 Waiting Container ticket-api-db-1 Healthy Container ticket-api-api-1 Starting Container ticket-api-api-1 Startedcat: /tmp/note.txt: No such file or directory{"items":[{"request_id":"1fe5dfeafae4","category":"shipping",...},{"request_id":"d2ef963e6b97","category":"billing",...}]}--force-recreate api crée un nouveau conteneur d’API même si rien n’a changé. Le fichier de l’ancien conteneur a disparu. Les deux classifications sont toujours là, car elles sont dans la base de données, dans le volume. Elle fait la même chose avec docker compose down, puis docker compose up -d : encore deux éléments. Ensuite, elle supprime volontairement les données :
docker compose down --volumesdocker compose up -dcurl -s http://127.0.0.1:8000/v1/history -H "X-API-Key: local-dev-key"La sortie (les dernières lignes de down, puis le nouvel historique) :
Volume ticket-api_pgdata Removing Network ticket-api_default Removing Volume ticket-api_pgdata Removed Network ticket-api_default Removed...{"items":[]}Votre tâche. Exécutez l’API avec sa dépendance et recréez les deux conteneurs. Montrez ensuite, preuves à l’appui, quelles données ont survécu :
- Démarrez les services et envoyez trois billets avec des sujets différents.
- Écrivez un fichier dans le conteneur de l’API.
- Recréez les deux conteneurs avec
docker compose up -d --force-recreate api db. - Montrez que le fichier a disparu, que l’historique contient encore trois éléments et que la base de données contient trois lignes (
SELECT count(*) FROM classifications;). - Écrivez deux phrases : quel état est jetable, quel état survit, et pourquoi.
Afficher une réponse modèle
docker compose up -dcurl -s -X POST http://127.0.0.1:8000/v1/classify -H "Content-Type: application/json" -H "X-API-Key: local-dev-key" -d '{"subject":"Refund for order 1182"}'curl -s -X POST http://127.0.0.1:8000/v1/classify -H "Content-Type: application/json" -H "X-API-Key: local-dev-key" -d '{"subject":"Cannot log in"}'curl -s -X POST http://127.0.0.1:8000/v1/classify -H "Content-Type: application/json" -H "X-API-Key: local-dev-key" -d '{"subject":"Parcel lost"}'docker compose exec api sh -c "echo hello > /tmp/note.txt"docker compose up -d --force-recreate api dbdocker compose exec api cat /tmp/note.txtcurl -s "http://127.0.0.1:8000/v1/history?limit=5" -H "X-API-Key: local-dev-key"docker compose exec db psql -U tickets -d tickets -c "SELECT count(*) FROM classifications;"Les résultats du test : les trois billets ont donné billing (facturation, priorité 2), login (connexion) et shipping (livraison). Les deux conteneurs ont été recréés (Recreate (recréer), Recreated (recréé), puis Healthy pour db). cat: /tmp/note.txt: No such file or directory (aucun fichier ou dossier de ce nom). L’historique contenait trois éléments, avec shipping en premier. Le nombre était :
count------- 3(1 row)Le fichier était dans la couche inscriptible du conteneur de l’API, qui est supprimée avec le conteneur : il est jetable. Les classifications étaient dans les fichiers de PostgreSQL, dans le volume nommé ticket-api_pgdata, à l’extérieur de tous les conteneurs. Elles survivent jusqu’à docker compose down --volumes.
Vérification du module
Section intitulée « Vérification du module »Vérifiez votre compréhension
Nommez l'option -p qui permet seulement à votre ordinateur d'accéder au port 8000 d'un conteneur, et le réglage qui empêche l'API de billets de démarrer sans clé.
Afficher la réponse
-p 127.0.0.1:8000:8000 (dans Compose : "127.0.0.1:8000:8000" sous ports:). REQUIRE_API_KEY=true : avec ce réglage, une clé manquante produit Settings error: REQUIRE_API_KEY is true, but API_KEY is not set (erreur de configuration : REQUIRE_API_KEY est vrai, mais API_KEY n’est pas défini) et le code de sortie 1.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Vous exécutez docker compose down, puis docker compose up -d. Prédisez ce qui reste : le fichier /tmp/note.txt dans le conteneur de l'API, l'historique, le réseau, les conteneurs.
Afficher la réponse
L’historique est toujours là : il est dans le volume nommé ticket-api_pgdata, que down ne supprime pas. Le fichier a disparu, car il était dans la couche inscriptible de l’ancien conteneur. Le réseau et les conteneurs sont nouveaux : down les a supprimés, et up les a recréés avec les mêmes noms. Seul down --volumes supprime l’historique.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Vérifiez votre compréhension
Vérification de fin : confirmez, avec des commandes et leurs résultats, que les données nécessaires survivent, contrairement à l'état jetable du conteneur.
Afficher la réponse
Les preuves à présenter à une personne qui révise votre travail, tirées de l’exercice du module :
- Avant :
/v1/history(avec la clé) énumère les classifications, etdocker compose exec api cat /tmp/note.txtaffichehello(bonjour). docker compose up -d --force-recreate api dbremplace les deux conteneurs.- Après :
cat /tmp/note.txtrenvoieNo such file or directory(aucun fichier ou dossier de ce nom; l’état jetable a disparu)./v1/historycontient les mêmes éléments, etSELECT count(*)donne le même nombre (les données nécessaires ont survécu). docker volume ls --filter name=ticket-apiafficheticket-api_pgdata: l’endroit où les données survivent.- L’explication : la couche inscriptible appartient à un conteneur, et le volume n’appartient à aucun conteneur.
Démontré : les cinq éléments, avec des sorties réelles. Partiellement démontré : les commandes sans les sorties avant et après, ou sans explication.
Votre réponse contient-elle les points principaux de la nôtre ?
Connectez-vous et inscrivez-vous au cours pour enregistrer vos réponses. Chaque vérification compte pour le certificat.
Récapitulatif
Dans ce module, vous avez :
- publié le port de l’API seulement sur
127.0.0.1, trouvé le problème de l’adresse d’écoute0.0.0.0et permis aux conteneurs de se trouver par leur nom sur un réseau; - donné différents réglages à une même image au démarrage, conservé la clé dans un fichier secret en lecture seule (
API_KEY_FILE) et fait arrêter l’application en cas de clé manquante (REQUIRE_API_KEY); - ajouté l’historique dans PostgreSQL (version 1.1.0, 25 tests) et exécuté l’API et sa base de données avec Compose. Les données sont dans le volume nommé
ticket-api_pgdata; - prouvé que l’historique survit aux nouveaux conteneurs, contrairement aux fichiers dans un conteneur.
Réinitialisation. Pour tout arrêter et supprimer les données de l’exercice :
docker compose down --volumesdocker image rm ticket-api:1.1.0-devdocker volume lsdocker system dfConservez ticket-api:1.1.0 : le module 5 l’utilise. Vérifiez dans docker volume ls que ticket-api_pgdata a disparu. Si votre projet ne fonctionne plus, copiez l’instantané end-of-m03. Recréez ensuite .env et secrets/api_key.txt, comme l’indique son README.md.
Ensuite, vous quittez votre ordinateur : découvrez les services de base de chaque nuage et leur correspondance avec les besoins de l’API sur Azure.
Leçon suivante : Régions et types de services