restart-condition in container-services
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -8,6 +8,9 @@
|
||||
.env.*
|
||||
!.env.example
|
||||
|
||||
# Entwicklungsmodus - gehoert nicht auf den Produktivserver
|
||||
docker-compose.override.yml
|
||||
|
||||
# Docker-Secrets-Dateien
|
||||
secrets/
|
||||
*.key
|
||||
|
||||
25
README.md
25
README.md
@@ -151,9 +151,28 @@ Dummy-Hash, damit die Antwortzeit konstant bleibt.
|
||||
hilft nicht gegen verteilte Angriffe auf ein einzelnes Konto; nur pro Konto zu
|
||||
begrenzen macht das Aussperren fremder Nutzer trivial.
|
||||
|
||||
**`restart: "no"` beim API-Container.** Während der Entwicklung soll ein
|
||||
Startfehler zu einem stehenden Container führen, nicht zu einer Endlosschleife,
|
||||
die das Log flutet. Für den Produktivbetrieb auf `unless-stopped` ändern.
|
||||
**Produktivbetrieb ist die Voreinstellung.** Der `api`-Container läuft mit
|
||||
`restart: unless-stopped`, ohne Bind-Mount und ohne `--reload` – es läuft der
|
||||
Code aus dem gebauten Abbild.
|
||||
|
||||
Zum Entwickeln:
|
||||
|
||||
```bash
|
||||
cp docker-compose.override.yml.example docker-compose.override.yml
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Compose zieht eine vorhandene `docker-compose.override.yml` automatisch mit
|
||||
heran. Sie bindet `./backend` ein, schaltet `--reload` an und setzt
|
||||
`restart: "no"`, damit ein Startfehler zu einem stehenden Container führt statt
|
||||
zu einer Endlosschleife im Log. Die Datei steht in `.gitignore` und gehört
|
||||
nicht auf den Produktivserver.
|
||||
|
||||
**Ohne Override wirken Änderungen am Quelltext erst nach einem Neubau:**
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
```
|
||||
|
||||
**Selbstregistrierung: drei Zustände statt zwei.** `ALLOW_SELF_REGISTRATION`
|
||||
kennt `true`, `false` und `admin`. Bei `true`/`false` ist der Wert fest
|
||||
|
||||
@@ -7,7 +7,18 @@ python -m app.wait_for_db
|
||||
echo "[entrypoint] Migrationen einspielen ..."
|
||||
alembic upgrade head
|
||||
|
||||
echo "[entrypoint] Starte API."
|
||||
# --reload nur beim Entwickeln. Im Dauerbetrieb bringt es einen
|
||||
# zusaetzlichen Ueberwachungsprozess, dauerndes Absuchen des
|
||||
# Dateisystems - und es setzt voraus, dass der Quelltext ueber ein
|
||||
# Bind-Mount hereinkommt statt aus dem gebauten Abbild.
|
||||
if [ "${DEV_RELOAD:-false}" = "true" ]; then
|
||||
echo "[entrypoint] Starte API im Entwicklungsmodus (--reload)."
|
||||
RELOAD_ARG="--reload"
|
||||
else
|
||||
echo "[entrypoint] Starte API."
|
||||
RELOAD_ARG=""
|
||||
fi
|
||||
|
||||
# --proxy-headers: X-Forwarded-For auswerten, damit das Rate Limiting
|
||||
# die echte Client-IP sieht und nicht die des nginx-Containers.
|
||||
# --forwarded-allow-ips: nur diesen Absendern glauben. Der Wert ist
|
||||
@@ -15,4 +26,4 @@ echo "[entrypoint] Starte API."
|
||||
exec uvicorn app.main:app \
|
||||
--host 0.0.0.0 --port 8000 \
|
||||
--proxy-headers --forwarded-allow-ips "${FORWARDED_ALLOW_IPS:-*}" \
|
||||
--reload
|
||||
${RELOAD_ARG}
|
||||
|
||||
34
docker-compose.override.yml.example
Normal file
34
docker-compose.override.yml.example
Normal file
@@ -0,0 +1,34 @@
|
||||
# Entwicklungsmodus.
|
||||
#
|
||||
# cp docker-compose.override.yml.example docker-compose.override.yml
|
||||
# docker compose up -d
|
||||
#
|
||||
# Docker Compose zieht eine vorhandene docker-compose.override.yml
|
||||
# automatisch mit heran - ohne zusaetzliche Schalter.
|
||||
#
|
||||
# Diese Datei gehoert NICHT auf den Produktivserver: Mit dem Bind-Mount
|
||||
# laeuft dort Code vom Host statt aus dem gebauten, geprueften Abbild.
|
||||
|
||||
services:
|
||||
api:
|
||||
# Quelltext live einbinden und uvicorn bei Aenderungen neu starten
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
environment:
|
||||
DEV_RELOAD: "true"
|
||||
# Startfehler sollen zu einem stehenden Container fuehren, nicht zu
|
||||
# einer Endlosschleife, die das Log flutet
|
||||
restart: "no"
|
||||
# Der Healthcheck stoert beim Entwickeln nur - waehrend eines
|
||||
# Neustarts durch --reload waere der Container kurz "unhealthy"
|
||||
healthcheck:
|
||||
disable: true
|
||||
|
||||
# API direkt erreichbar fuer curl und die interaktive Dokumentation.
|
||||
# An 127.0.0.1 gebunden - sonst haengt eine ungeschuetzte API am
|
||||
# oeffentlichen Interface.
|
||||
# ports:
|
||||
# - "127.0.0.1:8000:8000"
|
||||
|
||||
web:
|
||||
restart: "no"
|
||||
@@ -36,9 +36,18 @@ services:
|
||||
depends_on:
|
||||
db:
|
||||
condition: service_healthy
|
||||
volumes:
|
||||
- ./backend:/app
|
||||
restart: "no"
|
||||
# Kein Bind-Mount und kein --reload: Es laeuft der Code aus dem
|
||||
# gebauten Abbild. Zum Entwickeln docker-compose.override.yml
|
||||
# anlegen (Vorlage: docker-compose.override.yml.example).
|
||||
healthcheck:
|
||||
# Kein curl im python:slim-Abbild - deshalb ueber Python.
|
||||
test: ["CMD", "python3", "-c",
|
||||
"import urllib.request,sys; sys.exit(0 if urllib.request.urlopen('http://127.0.0.1:8000/healthz', timeout=3).status==200 else 1)"]
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 60s
|
||||
restart: unless-stopped
|
||||
networks: [einkaufsapp]
|
||||
|
||||
web:
|
||||
|
||||
@@ -152,6 +152,64 @@ Clients die alte Fassung.
|
||||
geänderten Werte aus der `.env`. Das hat in diesem Projekt schon mehrfach für
|
||||
Verwirrung gesorgt.
|
||||
|
||||
## Wenn ein Container verschwindet
|
||||
|
||||
```bash
|
||||
docker compose ps -a
|
||||
docker inspect einkaufsapp_api --format \
|
||||
'Status={{.State.Status}} Exit={{.State.ExitCode}} OOM={{.State.OOMKilled}} Ende={{.State.FinishedAt}}'
|
||||
docker compose logs --tail=50 api
|
||||
```
|
||||
|
||||
Die letzten Zeilen im Log verraten den Grund:
|
||||
|
||||
| Im Log | Bedeutung |
|
||||
|---|---|
|
||||
| `Shutting down` → `Application shutdown complete` → `Stopping reloader process` | Geordnetes Beenden. Jemand hat den Container gestoppt – SIGTERM an PID 1 |
|
||||
| Nichts, `OOMKilled=true` | Vom Kernel wegen Speichermangels abgeschossen |
|
||||
| Traceback, `Exit=1` | Startfehler, meist Konfiguration oder Migration |
|
||||
|
||||
### Geordnetes Beenden aufspüren
|
||||
|
||||
```bash
|
||||
journalctl --since "yesterday 22:00" --until "today 07:00" | grep -iE "docker|compose|borg"
|
||||
grep -rniE "docker (compose )?(stop|down)" /etc/cron* /root /opt 2>/dev/null
|
||||
```
|
||||
|
||||
Häufigster Verursacher ist ein Sicherungsskript, das Container anhält. **Für
|
||||
die Datenbanksicherung ist das nicht nötig:** `mariadb-dump
|
||||
--single-transaction` liefert einen konsistenten Stand im laufenden Betrieb.
|
||||
|
||||
### Was `restart: unless-stopped` leistet – und was nicht
|
||||
|
||||
Der `api`-Container startet jetzt nach Abstürzen und nach einem Neustart des
|
||||
Hosts von selbst wieder.
|
||||
|
||||
**Nach einem ausdrücklichen `docker compose stop` aber nicht.** Das ist die
|
||||
Bedeutung von „unless-stopped": Docker merkt sich, dass der Stopp gewollt war.
|
||||
Wenn dein Sicherungsskript Container anhält, muss es sie danach selbst wieder
|
||||
starten:
|
||||
|
||||
```bash
|
||||
docker compose stop
|
||||
# ... sichern ...
|
||||
docker compose start
|
||||
```
|
||||
|
||||
Ein Skript, das nur stoppt und sich auf die Neustartregel verlässt, hinterlässt
|
||||
eine tote Anwendung – genau das ist hier passiert.
|
||||
|
||||
### Kehrseite der Neustartregel
|
||||
|
||||
Bricht der Container beim Start ab, etwa wegen einer fehlgeschlagenen
|
||||
Migration, versucht Docker es endlos erneut und das Log füllt sich mit
|
||||
Wiederholungen. Dann hilft:
|
||||
|
||||
```bash
|
||||
docker compose stop api
|
||||
docker compose logs --tail=60 api # in Ruhe lesen
|
||||
```
|
||||
|
||||
## Speicherplatz im Blick behalten
|
||||
|
||||
```bash
|
||||
|
||||
Reference in New Issue
Block a user