Cross-arch : container-test accepte une platform cible (incident « exec format error »)
Le premier déploiement NAS a échoué avec « exec /docker-entrypoint.sh: exec format error » : image arm64 (buildée sur Mac Apple Silicon) exécutée sur un hôte amd64. Le test réel l'a épiné : container-test.sh accepte désormais un paramètre PLATFORM (build cross-arch SANS émulation — le Dockerfile n'a aucun RUN — puis run émulé + E2E complet) : bash scripts/container-test.sh <port> linux/amd64 → validé vert sur linux/amd64 (healthcheck durci, E2E complet). Docs §10 : leçon cross-arch (les 2 procédures de build — sur le serveur cible ou multi-arch push via buildx), statut : les DEUX archs testées ; note : l'« io_setup() failed » sous émulation Rosetta est inoffensif.
This commit is contained in:
parent
96e0627741
commit
e078650045
23
README.md
23
README.md
@ -62,23 +62,32 @@ server {
|
|||||||
### Avec Docker (self-host NAS/VPS)
|
### Avec Docker (self-host NAS/VPS)
|
||||||
|
|
||||||
L'image ne fait **que servir les fichiers** : aucune donnée dedans, la vie
|
L'image ne fait **que servir les fichiers** : aucune donnée dedans, la vie
|
||||||
privée est identique (les données vivent dans les navigateurs). Build et
|
privée est identique (les données vivent dans les navigateurs). Deux
|
||||||
exécution :
|
procédures équivalentes — **l'image doit être pour l'arch CPU de l'hôte**
|
||||||
|
(« exec format error » = arch incompatible, cf
|
||||||
|
[docs/DEVELOPPEMENT.md §10](docs/DEVELOPPEMENT.md)) :
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
docker compose up -d # build + run → http://<hôte>:8080/
|
# ── Option 1 (recommandée) : build SUR le serveur cible
|
||||||
docker compose up -d --build # rebuild après mise à jour du dépôt
|
git clone https://gitea.cloudyfy.fr/Siphonight/HormoneTrack-web && cd HormoneTrack-web
|
||||||
|
docker compose up -d --build # arch native, toujours correcte
|
||||||
|
|
||||||
|
# ── Option 2 : multi-arch depuis un Mac (push vers ton registry)
|
||||||
|
docker buildx build --platform linux/amd64,linux/arm64 \
|
||||||
|
-t tonuser/hormonetrack-web:latest --push .
|
||||||
```
|
```
|
||||||
|
|
||||||
- Image **non privilégiée** (`nginx-unprivileged`, utilisateur 101, port
|
- Image **non privilégiée** (`nginx-unprivileged`, utilisateur 101, port
|
||||||
8080), filesystem en **lecture seule** (composé prêt pour le NAS),
|
8080), filesystem en **lecture seule** + tmpfs (composé durci), vérif
|
||||||
healthcheck intégré, gzip sur l'asset PK (550 Ko → ~150 Ko).
|
healthcheck intégrée, gzip sur l'asset PK (550 Ko → ~150 Ko).
|
||||||
- Headers `no-cache` : une mise à jour d'image est prise en compte au
|
- Headers `no-cache` : une mise à jour d'image est prise en compte au
|
||||||
rechargement suivant, sans code périmé chez les clients.
|
rechargement suivant, sans code périmé chez les clients.
|
||||||
- Validation en 3 commandes sur l'hôte qui a Docker (cf
|
- Validation en 3 commandes sur l'hôte qui a Docker (cf
|
||||||
[docs/DEVELOPPEMENT.md §10](docs/DEVELOPPEMENT.md)) : build, run, puis
|
[docs/DEVELOPPEMENT.md §10](docs/DEVELOPPEMENT.md)) : build, run, puis
|
||||||
`HRT_E2E_BASE=http://127.0.0.1:8080 node scripts/e2e.mjs` — le même test
|
`HRT_E2E_BASE=http://127.0.0.1:8080 node scripts/e2e.mjs` — le même test
|
||||||
E2E que le CI pilote le site SERVI PAR LE CONTENEUR.
|
E2E que le CI pilote le site SERVI PAR LE CONTENEUR. Pour tester l'arch
|
||||||
|
cible AVANT déploiement :
|
||||||
|
`bash scripts/container-test.sh 8979 linux/amd64`.
|
||||||
|
|
||||||
Aucun build, aucune variable d'environnement, aucun composant serveur : les
|
Aucun build, aucune variable d'environnement, aucun composant serveur : les
|
||||||
données de chaque personne vivent **dans SON navigateur**, jamais sur la
|
données de chaque personne vivent **dans SON navigateur**, jamais sur la
|
||||||
|
|||||||
@ -571,14 +571,54 @@ HRT_E2E_BASE=http://127.0.0.1:8080 node scripts/e2e.mjs
|
|||||||
|
|
||||||
**Statut (8 sept. 2026)** : build et run RÉELS validés localement via
|
**Statut (8 sept. 2026)** : build et run RÉELS validés localement via
|
||||||
colima (daemon Docker léger, 2 CPU / 2 Go) — `scripts/container-test.sh`
|
colima (daemon Docker léger, 2 CPU / 2 Go) — `scripts/container-test.sh`
|
||||||
vert en configuration DURCIE (healthcheck healthy, endpoints, headers,
|
vert en configuration DURCIE pour **les DEUX architectures** (arm64 native
|
||||||
gzip, MIME, E2E complet contre le conteneur). Deux problèmes réels trouvés
|
et linux/amd64 cross-compilée : healthcheck healthy, endpoints, headers,
|
||||||
et corrigés au premier test : (a) tmpfs `/tmp` REQUIS en read-only (les
|
gzip, MIME, E2E complet contre le conteneur). Problèmes réels trouvés aux
|
||||||
temporaires de nginx-unprivileged — « mkdir /tmp/proxy_temp failed »
|
premiers tests, corrigés et documentés : (a) tmpfs `/tmp` REQUIS en
|
||||||
sinon), (b) curl `%header_json` minuscule les clés (bug du TEST, pas du
|
read-only (« mkdir /tmp/proxy_temp failed » sinon), (b) curl `%header_json`
|
||||||
conteneur). Si le durcissement pose problème sur ton NAS : retirer
|
minuscule les clés (bug du TEST), (c) **exec format error sur l'hôte
|
||||||
`read_only`/`tmpfs`/`security_opt` du compose (aucune perte de sécurité
|
amd64** (cf §10 bis). Si le durcissement pose problème sur ton NAS :
|
||||||
cruciale — le conteneur ne détient rien).
|
retirer `read_only`/`tmpfs`/`security_opt` du compose (aucune perte de
|
||||||
|
sécurité cruciale — le conteneur ne détient rien).
|
||||||
|
|
||||||
|
### Cross-arch (ⓘ leçon du 8 sept. 2026 — « exec /docker-entrypoint.sh:
|
||||||
|
exec format error »)
|
||||||
|
|
||||||
|
`exec format error` au `docker run` = l'image est pour une AUTRE
|
||||||
|
architecture CPU que l'hôte (image arm64 buildée sur Mac → serveur amd64).
|
||||||
|
Le Dockerfile ne contient **AUCUN `RUN`** (labels + COPY + HEALTHCHECK) :
|
||||||
|
la cross-compilation est FAIBLE — buildx produit l'image pour n'importe
|
||||||
|
quelle platform SANS émulation de build (constaté : build amd64 instantané
|
||||||
|
sur l'arm64). Seul le RUN du conteneur émule (Rosetta via colima VZ / qemu
|
||||||
|
sur l'hôte de build — sous émulation nginx journalise un
|
||||||
|
`io_setup() failed (ENOSYS)` inoffensif : il retombe et répond 200 ; ce
|
||||||
|
syscall existe sur un vrai Linux amd64).
|
||||||
|
|
||||||
|
**Règle** : le conteneur de PROD doit être buildé pour L'ARCH de l'hôte —
|
||||||
|
deux procédures équivalentes :
|
||||||
|
|
||||||
|
1. **Sur le serveur cible** (recommandé — zéro registre, zéro émulation) :
|
||||||
|
```bash
|
||||||
|
git clone https://gitea.cloudyfy.fr/Siphonight/HormoneTrack-web && cd HormoneTrack-web
|
||||||
|
docker compose up -d --build # arch native du serveur, toujours bon
|
||||||
|
```
|
||||||
|
2. **Multi-arch depuis le Mac** (push vers un registry — Docker Hub, GHCR,
|
||||||
|
ou le registry interne du NAS) :
|
||||||
|
```bash
|
||||||
|
docker buildx build --platform linux/amd64,linux/arm64 -t <user>/hormonetrack-web:latest --push .
|
||||||
|
```
|
||||||
|
(build natif des deux archs — cf container-test.sh [platform] pour
|
||||||
|
tester CHAQUE arch localement avant push : l'E2E complet tourne contre
|
||||||
|
l'image cross-archémulée.)
|
||||||
|
|
||||||
|
**Test de l'arch cible AVANT push/déploiement** :
|
||||||
|
`bash scripts/container-test.sh <port> linux/amd64` — c'est le test qui
|
||||||
|
aurait attrapé l'incident « exec format error » du premier déploiement
|
||||||
|
NAS (image arm64 seule déployée sur hôte amd64).
|
||||||
|
|
||||||
|
Composition de l'utilisatrice (8 sept.) : service `image:` depuis un
|
||||||
|
registry, `env_file`/limites de ressources/réseaux externes — tout est
|
||||||
|
inoffensif pour ce conteneur statique (aucune variable lue, no volumes).
|
||||||
|
|
||||||
## 11. Bugs potentiels évités pendant le portage
|
## 11. Bugs potentiels évités pendant le portage
|
||||||
|
|
||||||
|
|||||||
@ -12,7 +12,18 @@
|
|||||||
# Prérequis : docker (daemon) ; node + playwright pour l'E2E final (skippé
|
# Prérequis : docker (daemon) ; node + playwright pour l'E2E final (skippé
|
||||||
# sinon — cf check.sh pour l'installation).
|
# sinon — cf check.sh pour l'installation).
|
||||||
#
|
#
|
||||||
# Usage : bash scripts/container-test.sh [port] (défaut 8975)
|
# Usage : bash scripts/container-test.sh [port] [platform]
|
||||||
|
# [port] défaut 8975
|
||||||
|
# [platform] platform Docker optionnelle (ex. linux/amd64) — permet de
|
||||||
|
# tester l'image pour L'HÔTE CIBLE depuis un Mac arm64 : sans
|
||||||
|
# elle, on build+run l'arch native (arm64) ; avec platform,
|
||||||
|
# buildx produit l'image pour CETTE platform (build natif —
|
||||||
|
# le Dockerfile ne contient AUCUN RUN, cross-compile sans
|
||||||
|
# émulation de build) et le run passe par l'émulation de
|
||||||
|
# l'hôte (Rosetta/qemu). ⚠️ Le bug réel du 8 sept. —
|
||||||
|
# « exec /docker-entrypoint.sh: exec format error » sur le
|
||||||
|
# NAS amd64 — venait d'une image arm64 uniquement : C'EST CE
|
||||||
|
# TEST cross-arch qui l'eût attrapé.
|
||||||
#
|
#
|
||||||
# ⚠️ Un SEUL invocation fait tout (leçon Android #43 : les invocations
|
# ⚠️ Un SEUL invocation fait tout (leçon Android #43 : les invocations
|
||||||
# rapprochées se remplacent mutuellement) et l'échec est BRUYANT (exit 1
|
# rapprochées se remplacent mutuellement) et l'échec est BRUYANT (exit 1
|
||||||
@ -22,9 +33,17 @@ set -euo pipefail
|
|||||||
cd "$(dirname "$0")/.." # racine du dépôt
|
cd "$(dirname "$0")/.." # racine du dépôt
|
||||||
|
|
||||||
PORT="${1:-8975}"
|
PORT="${1:-8975}"
|
||||||
|
PLATFORM="${2:-}"
|
||||||
HOST_PORT="127.0.0.1:${PORT}"
|
HOST_PORT="127.0.0.1:${PORT}"
|
||||||
IMAGE="hormonetrack-web:smoke"
|
# Suffixe de tag par platevorme pour ne jamais mélanger les images
|
||||||
CONTAINER="hrt-web-smoke"
|
TAG_SUFFIX="smoke"
|
||||||
|
PLATFORM_ARG=()
|
||||||
|
if [[ -n "${PLATFORM:-}" ]]; then
|
||||||
|
TAG_SUFFIX="smoke-${PLATFORM##*/}" # linux/amd64 → smoke-amd64
|
||||||
|
PLATFORM_ARG=(--platform "$PLATFORM")
|
||||||
|
fi
|
||||||
|
IMAGE="hormonetrack-web:${TAG_SUFFIX}"
|
||||||
|
CONTAINER="hrt-web-${TAG_SUFFIX}"
|
||||||
ROOM=0 # compteur d'échecs
|
ROOM=0 # compteur d'échecs
|
||||||
|
|
||||||
fail() { echo " ✗ $1"; ROOM=$((ROOM + 1)); }
|
fail() { echo " ✗ $1"; ROOM=$((ROOM + 1)); }
|
||||||
@ -35,8 +54,8 @@ cleanup() {
|
|||||||
}
|
}
|
||||||
trap cleanup EXIT
|
trap cleanup EXIT
|
||||||
|
|
||||||
echo "── 1. Build de l'image ──"
|
echo "── 1. Build de l'image${PLATFORM:+ [${PLATFORM}]} ──"
|
||||||
docker build -t "$IMAGE" . 2>&1 | tail -2
|
docker build "${PLATFORM_ARG[@]}" -t "$IMAGE" . 2>&1 | tail -2
|
||||||
|
|
||||||
echo "── 2. Run (durcissement compose : read_only + no-new-privileges) ──"
|
echo "── 2. Run (durcissement compose : read_only + no-new-privileges) ──"
|
||||||
# On teste l'image dans la configuration DURCIE de docker-compose.yml —
|
# On teste l'image dans la configuration DURCIE de docker-compose.yml —
|
||||||
@ -46,6 +65,7 @@ echo "── 2. Run (durcissement compose : read_only + no-new-privileges) ─
|
|||||||
# « mkdir /tmp/proxy_temp failed » (constaté au premier container-test).
|
# « mkdir /tmp/proxy_temp failed » (constaté au premier container-test).
|
||||||
docker run -d --rm --name "$CONTAINER" \
|
docker run -d --rm --name "$CONTAINER" \
|
||||||
-p "${HOST_PORT}:8080" \
|
-p "${HOST_PORT}:8080" \
|
||||||
|
"${PLATFORM_ARG[@]}" \
|
||||||
--read-only \
|
--read-only \
|
||||||
--tmpfs /var/cache/nginx --tmpfs /run --tmpfs /tmp \
|
--tmpfs /var/cache/nginx --tmpfs /run --tmpfs /tmp \
|
||||||
--security-opt no-new-privileges \
|
--security-opt no-new-privileges \
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user