feat: auto-recovery pyrealsense2 stuck via healthcheck + autoheal
Sostituisce il healthcheck banale (urlopen /api/health) con uno che fallisce quando camera_open != true, e aggiunge il container willfarrell/autoheal che restarta il backend quando Docker lo marca unhealthy. Recovery <1min da SDK stuck senza intervento SSH. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
ece2171678
commit
ede70a21b3
2 files changed
+414
-3
No files matched your search
@@ -0,0 +1,379 @@
|
|||||||
|
# TODO — Auto-recovery del pipeline pyrealsense2 stuck
|
||||||
|
|
||||||
|
Documento di handoff per implementare auto-recovery quando la D435 SDK si pianta. Stato attuale: dopo boot di PC2 (o dopo un client che termina sporcamente), la D435 risulta `camera_open=False` e il `POST /api/open` ritorna `OPEN_FAILED`. **L'unico fix oggi è SSH a PC2 + `docker compose restart`**, che blocca chiunque sta usando il client.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Eliminare il bisogno di intervento manuale: il container deve auto-recuperare in <1 minuto da uno stato "SDK stuck", anche se nessuno è loggato su PC2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Sintomi osservati
|
||||||
|
|
||||||
|
Dopo boot PC2 o dopo terminazione sporca di un client (es. zombie Python che ha tenuto aperto un handle USB):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ curl -s http://192.168.123.99:8878/api/health
|
||||||
|
{"success":true,"data":{"status":"ok","sdk_initialized":true,"camera_open":false,"nats_connected":false}}
|
||||||
|
# → 200 OK, "healthy" per Docker, ma camera_open=False
|
||||||
|
|
||||||
|
$ curl -s http://192.168.123.99:8878/api/status | jq .data
|
||||||
|
{"sdk_initialized":true, "camera_open":false, "color_available":false, "usb_type":null, ...}
|
||||||
|
|
||||||
|
$ curl -s -X POST http://192.168.123.99:8878/api/open
|
||||||
|
{"success":false,"data":null,"error":{"code":"OPEN_FAILED","message":"Failed to open camera"}}
|
||||||
|
|
||||||
|
$ curl -s http://192.168.123.99:8878/api/discover | jq .data.cameras
|
||||||
|
[{"index":0,"vid":"8086","pid":"0b3a","serial":"243722073572", ...}]
|
||||||
|
# USB device discoverable, ma pyrealsense2.pipeline().start() fallisce.
|
||||||
|
# Soft cycle close+open NON sblocca. Solo `docker compose restart` del container ripristina.
|
||||||
|
```
|
||||||
|
|
||||||
|
Frequenza: osservato il **2026-05-23** e il **2026-05-25** (oggi). Sospetto trigger: un client che termina senza chiudere pulito la `pipeline` (es. SIGKILL, oppure process Python che muore con un handle USB aperto). pyrealsense2 SDK lascia il device descriptor in uno stato inconsistente che solo un fresh process può risolvere.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Comportamento attuale del container
|
||||||
|
|
||||||
|
[`docker-compose.yml`](docker-compose.yml) del backend service:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
backend:
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8878/api/health')"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 3
|
||||||
|
```
|
||||||
|
|
||||||
|
E [`src/backend/main.py:53-66`](src/backend/main.py) ha l'auto-open al startup:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if sdk_ok and settings.camera_auto_open:
|
||||||
|
try:
|
||||||
|
devices = camera.discover()
|
||||||
|
if devices:
|
||||||
|
if camera.open(0):
|
||||||
|
logger.info(f"Auto-opened camera 0 (serial={devices[0].serial})")
|
||||||
|
else:
|
||||||
|
logger.warning("Auto-open requested but open(0) failed") # ← cade qui spesso
|
||||||
|
else:
|
||||||
|
logger.warning("Auto-open requested but no RealSense devices found")
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"Auto-open failed: {exc}")
|
||||||
|
```
|
||||||
|
|
||||||
|
`camera_auto_open: bool = True` di default (vedi [`src/backend/config.py:27`](src/backend/config.py)).
|
||||||
|
|
||||||
|
**Cosa NON funziona** del comportamento attuale:
|
||||||
|
|
||||||
|
1. **Healthcheck su `/api/health`**: l'endpoint ritorna 200 anche se `camera_open=False`. Docker considera il container "healthy" anche quando la SDK è stuck → `restart: unless-stopped` non si attiva.
|
||||||
|
2. **Auto-open senza retry**: `main.py` prova `camera.open(0)` UNA volta sola. Se fallisce, log warning e basta. Nessun retry, nessun re-tentativo periodico.
|
||||||
|
3. **Nessun watchdog runtime**: se la SDK si piantava dopo che il container era già up + camera_open=True (es. il client kill un handle), il container resta vivo con stato "stuck" indefinitamente.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Soluzione raccomandata (Opzione A): healthcheck "vero" + autoheal
|
||||||
|
|
||||||
|
Production-grade, copre BOTH startup-failure E runtime-stuck.
|
||||||
|
|
||||||
|
### 3.1 Modifica `docker-compose.yml`
|
||||||
|
|
||||||
|
Cambia il healthcheck del `backend` per probare `camera_open=True` invece di solo `/api/health`:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
backend:
|
||||||
|
build: ...
|
||||||
|
privileged: true
|
||||||
|
ports:
|
||||||
|
- "${API_PORT:-8878}:8878"
|
||||||
|
devices:
|
||||||
|
- "/dev/bus/usb:/dev/bus/usb"
|
||||||
|
volumes:
|
||||||
|
- ./images:/app/images
|
||||||
|
- ./models:/app/models:ro
|
||||||
|
env_file: .env
|
||||||
|
depends_on:
|
||||||
|
nats:
|
||||||
|
condition: service_healthy
|
||||||
|
required: false
|
||||||
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
# autoheal monitorerà SOLO i container con questa label = true
|
||||||
|
autoheal: "true"
|
||||||
|
healthcheck:
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- python3
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
import urllib.request, json, sys
|
||||||
|
try:
|
||||||
|
r = urllib.request.urlopen('http://localhost:8878/api/status', timeout=3)
|
||||||
|
d = json.loads(r.read())
|
||||||
|
sys.exit(0 if d.get('data', {}).get('camera_open') else 1)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
interval: 15s # check ogni 15s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 4 # 4 fallimenti consecutivi → unhealthy (4×15s = 1min di stato cattivo)
|
||||||
|
start_period: 30s # 30s di grace al boot per dare tempo al pipeline start
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Aggiungi servizio `autoheal` al compose
|
||||||
|
|
||||||
|
Sotto la sezione `services:`, aggiungi:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# -- Autoheal -------------------------------
|
||||||
|
# Monitora i container con label `autoheal=true` e li restarta automaticamente
|
||||||
|
# quando Docker li marca "unhealthy". Risolve il caso "SDK stuck a runtime".
|
||||||
|
autoheal:
|
||||||
|
image: willfarrell/autoheal:latest
|
||||||
|
container_name: autoheal-d435
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
AUTOHEAL_CONTAINER_LABEL: autoheal
|
||||||
|
AUTOHEAL_INTERVAL: 10
|
||||||
|
AUTOHEAL_START_PERIOD: 30
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Comportamento risultante
|
||||||
|
|
||||||
|
- **Boot PC2**: backend parte → auto-open prova → healthcheck su `camera_open` valida lo stato.
|
||||||
|
- Se camera ok: tutto verde, healthy.
|
||||||
|
- Se camera fail: 4×15s = 1min → unhealthy → autoheal restart → backend riparte da capo → auto-open di nuovo. Loop finché ok (o finché USB realmente morta).
|
||||||
|
- **Runtime stuck**: client kill, SDK si pianta, `camera_open` torna False → 4×15s di unhealthy → restart automatico.
|
||||||
|
- **Latenza max recovery**: ~1 minuto (4 retry × 15s).
|
||||||
|
|
||||||
|
### 3.4 Deploy
|
||||||
|
|
||||||
|
Su PC2:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh unitree@192.168.123.99
|
||||||
|
cd ~/Robot_2026/RealSense-D435 # path da STATUS.md
|
||||||
|
git pull # se è in git, altrimenti rsync il compose nuovo
|
||||||
|
sg docker -c "docker compose up -d --remove-orphans"
|
||||||
|
# verifica:
|
||||||
|
sg docker -c "docker compose ps"
|
||||||
|
sg docker -c "docker compose logs -f autoheal" & # log autoheal in background
|
||||||
|
curl -s http://localhost:8878/api/status | jq .data.camera_open
|
||||||
|
# atteso: true entro 30-60s
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. Soluzione fallback (Opzione B): watchdog INTERNO al backend
|
||||||
|
|
||||||
|
Se per qualche motivo NON vuoi aggiungere un container extra (autoheal), puoi inserire watchdog logic DENTRO `main.py`. Meno production-grade ma self-contained.
|
||||||
|
|
||||||
|
### 4.1 Modifica `src/backend/main.py`
|
||||||
|
|
||||||
|
Aggiungi dopo l'auto-open un thread watchdog:
|
||||||
|
|
||||||
|
```python
|
||||||
|
import threading
|
||||||
|
import os
|
||||||
|
|
||||||
|
def _camera_watchdog(camera, settings):
|
||||||
|
"""Thread daemon che monitora camera.is_open(). Se False per N cicli
|
||||||
|
consecutivi → os._exit(1) → restart_policy 'unless-stopped' rilancia
|
||||||
|
il container."""
|
||||||
|
consecutive_bad = 0
|
||||||
|
BAD_THRESHOLD = 3 # 3 cicli × 20s = 1 minuto di stato cattivo
|
||||||
|
INTERVAL_S = 20
|
||||||
|
while True:
|
||||||
|
time.sleep(INTERVAL_S)
|
||||||
|
if not camera.is_open():
|
||||||
|
consecutive_bad += 1
|
||||||
|
logger.warning(
|
||||||
|
f"Watchdog: camera_open=False ({consecutive_bad}/{BAD_THRESHOLD} consecutive). "
|
||||||
|
f"Tento auto-recovery..."
|
||||||
|
)
|
||||||
|
# Prima prova: re-open in-process
|
||||||
|
try:
|
||||||
|
if camera.open(0):
|
||||||
|
logger.info("Watchdog: re-open succeeded")
|
||||||
|
consecutive_bad = 0
|
||||||
|
continue
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"Watchdog: re-open failed: {exc}")
|
||||||
|
# Se ancora cattivo dopo BAD_THRESHOLD → esce con errore →
|
||||||
|
# restart_policy rilancia il container fresh.
|
||||||
|
if consecutive_bad >= BAD_THRESHOLD:
|
||||||
|
logger.error(
|
||||||
|
f"Watchdog: camera stuck per {BAD_THRESHOLD * INTERVAL_S}s. "
|
||||||
|
f"Killing process to trigger restart_policy."
|
||||||
|
)
|
||||||
|
os._exit(1)
|
||||||
|
else:
|
||||||
|
consecutive_bad = 0
|
||||||
|
|
||||||
|
# E dopo l'auto-open (line ~66):
|
||||||
|
threading.Thread(target=_camera_watchdog, args=(camera, settings), daemon=True).start()
|
||||||
|
logger.info("Camera watchdog thread avviato (check ogni 20s)")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Aggiungi anche retry sull'auto-open iniziale
|
||||||
|
|
||||||
|
Dopo il blocco `if sdk_ok and settings.camera_auto_open:` esistente, sostituiscilo con un retry loop:
|
||||||
|
|
||||||
|
```python
|
||||||
|
if sdk_ok and settings.camera_auto_open:
|
||||||
|
MAX_RETRY = 5
|
||||||
|
RETRY_BACKOFF_S = 3
|
||||||
|
for attempt in range(1, MAX_RETRY + 1):
|
||||||
|
try:
|
||||||
|
devices = camera.discover()
|
||||||
|
if not devices:
|
||||||
|
logger.warning(f"Auto-open attempt {attempt}/{MAX_RETRY}: no devices")
|
||||||
|
time.sleep(RETRY_BACKOFF_S)
|
||||||
|
continue
|
||||||
|
if camera.open(0):
|
||||||
|
logger.info(f"Auto-opened camera 0 (serial={devices[0].serial}) at attempt {attempt}")
|
||||||
|
break
|
||||||
|
logger.warning(f"Auto-open attempt {attempt}/{MAX_RETRY}: open(0) returned False")
|
||||||
|
except Exception as exc:
|
||||||
|
logger.warning(f"Auto-open attempt {attempt}/{MAX_RETRY}: {exc}")
|
||||||
|
time.sleep(RETRY_BACKOFF_S)
|
||||||
|
else:
|
||||||
|
logger.error(f"Auto-open failed after {MAX_RETRY} attempts. Watchdog continuerà a riprovare.")
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Deploy
|
||||||
|
|
||||||
|
Stesso pattern di Opzione A ma serve **rebuild dell'immagine**:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ssh unitree@192.168.123.99
|
||||||
|
cd ~/Robot_2026/RealSense-D435
|
||||||
|
git pull # o rsync di src/backend/main.py
|
||||||
|
sg docker -c "docker compose build backend"
|
||||||
|
sg docker -c "docker compose up -d"
|
||||||
|
sg docker -c "docker compose logs -f backend" # verifica le righe "Auto-opened" o "Watchdog"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. Soluzioni minori (NON raccomandate, ma per completezza)
|
||||||
|
|
||||||
|
### Opzione C — Solo retry al startup, no watchdog runtime
|
||||||
|
|
||||||
|
Implementa solo il punto 4.2 di Opzione B (retry sull'auto-open iniziale). Risolve il caso "fallisce al boot PC2" ma NON il caso "si pianta a runtime dopo crash client". Da scartare a meno che il caso runtime non sia mai stato osservato (oggi lo è stato, quindi NON basta).
|
||||||
|
|
||||||
|
### Opzione D — Cron/systemd timer ESTERNO su PC2
|
||||||
|
|
||||||
|
Watchdog systemd-timer SU PC2 (non dentro container):
|
||||||
|
|
||||||
|
```ini
|
||||||
|
# /etc/systemd/system/d435-watchdog.service
|
||||||
|
[Unit]
|
||||||
|
Description=D435 camera health watchdog
|
||||||
|
[Service]
|
||||||
|
Type=oneshot
|
||||||
|
ExecStart=/bin/bash -c '/usr/bin/curl -s --max-time 5 http://localhost:8878/api/status | /usr/bin/jq -e ".data.camera_open" >/dev/null || (cd /home/unitree/Robot_2026/RealSense-D435 && /usr/bin/docker compose restart backend)'
|
||||||
|
|
||||||
|
# /etc/systemd/system/d435-watchdog.timer
|
||||||
|
[Unit]
|
||||||
|
Description=Run D435 watchdog every 5 minutes
|
||||||
|
[Timer]
|
||||||
|
OnBootSec=2min
|
||||||
|
OnUnitActiveSec=5min
|
||||||
|
[Install]
|
||||||
|
WantedBy=timers.target
|
||||||
|
```
|
||||||
|
|
||||||
|
Setup una tantum su PC2:
|
||||||
|
```bash
|
||||||
|
sudo systemctl daemon-reload
|
||||||
|
sudo systemctl enable --now d435-watchdog.timer
|
||||||
|
sudo systemctl list-timers | grep d435
|
||||||
|
```
|
||||||
|
|
||||||
|
Pro: zero modifiche al container, deploy via SSH una tantum. Contro: 5min di latenza al recovery, serve sysadmin PC2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. Verifica end-to-end (test del fix dopo deploy)
|
||||||
|
|
||||||
|
Test 1 — **Boot scenario**:
|
||||||
|
```bash
|
||||||
|
ssh unitree@192.168.123.99 'sudo reboot'
|
||||||
|
sleep 90 # aspetta boot + container start + auto-open
|
||||||
|
curl -s --max-time 5 http://192.168.123.99:8878/api/status | jq .data.camera_open
|
||||||
|
# atteso: true
|
||||||
|
```
|
||||||
|
|
||||||
|
Test 2 — **Stuck scenario** (simulare il problema):
|
||||||
|
```bash
|
||||||
|
# Da una qualunque shell, lancia uno script Python che apre la pipeline poi viene killato sporco:
|
||||||
|
python3 -c "
|
||||||
|
import pyrealsense2 as rs
|
||||||
|
p = rs.pipeline()
|
||||||
|
p.start()
|
||||||
|
import time; time.sleep(2)
|
||||||
|
import os; os._exit(0) # exit senza p.stop()
|
||||||
|
" &
|
||||||
|
sleep 1
|
||||||
|
# Adesso prova ad aprire dal container:
|
||||||
|
curl -s -X POST http://192.168.123.99:8878/api/open
|
||||||
|
# Probabilmente OPEN_FAILED. Aspetta il watchdog/autoheal:
|
||||||
|
for i in 1 2 3 4 5; do
|
||||||
|
sleep 20
|
||||||
|
state=$(curl -s --max-time 3 http://192.168.123.99:8878/api/status | jq -r .data.camera_open)
|
||||||
|
echo "t=+${i}*20s camera_open=$state"
|
||||||
|
done
|
||||||
|
# atteso (con Opzione A o B): "true" entro t=+3*20s = 60s.
|
||||||
|
```
|
||||||
|
|
||||||
|
Test 3 — **Healthcheck visibility**:
|
||||||
|
```bash
|
||||||
|
ssh unitree@192.168.123.99 'sg docker -c "docker inspect --format=\"{{.State.Health.Status}}\" robotserver-realsense-d435i-g1-backend-1"'
|
||||||
|
# atteso: "healthy" quando camera_open=true, "unhealthy" quando camera stuck.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. Riferimenti
|
||||||
|
|
||||||
|
**File da modificare**:
|
||||||
|
- [`docker-compose.yml`](docker-compose.yml) — healthcheck + servizio autoheal (Opzione A) oppure niente (Opzione B).
|
||||||
|
- [`src/backend/main.py`](src/backend/main.py) — retry auto-open + watchdog thread (Opzione B).
|
||||||
|
- (nessuno per Opzione D, solo file su PC2 fuori repo)
|
||||||
|
|
||||||
|
**Documentazione esistente**:
|
||||||
|
- [`README.md`](README.md) — overview del container.
|
||||||
|
- [`STATUS.md`](STATUS.md) — stato funzionale + setup PC2 (gruppo docker, proxy, ecc.).
|
||||||
|
|
||||||
|
**Doc esterna correlata** (nel repo principale `/home/harry/robot/workspace/`):
|
||||||
|
- `docs/hardware/g1-KB/g1_arm_control_dds.md` — describes the dual-publisher arm_sdk pattern.
|
||||||
|
- `oneshot_servo_comparison_h1_vs_g1.md` — note sul comportamento robot quando cam fail.
|
||||||
|
|
||||||
|
**Riferimenti tecnici upstream**:
|
||||||
|
- [pyrealsense2 GitHub issue: pipeline stuck after process kill](https://github.com/IntelRealSense/librealsense/issues?q=is%3Aissue+pipeline+stuck+is%3Aclosed) — molti reports simili.
|
||||||
|
- [willfarrell/autoheal](https://github.com/willfarrell/autoheal) — container Docker per auto-restart su unhealthy.
|
||||||
|
- [Docker healthcheck docs](https://docs.docker.com/reference/dockerfile/#healthcheck) — sintassi e semantica `interval`/`retries`/`start_period`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. Priorità + scope
|
||||||
|
|
||||||
|
- **Opzione A** = scelta raccomandata. ~30 LoC modificate (compose), 0 di backend code change. 1 rebuild non necessario (compose change basta).
|
||||||
|
- **Opzione B** = se per qualche motivo (es. policy aziendale) non puoi aggiungere servizi a un compose esistente. ~60 LoC backend Python. Serve rebuild image.
|
||||||
|
- **Opzione D** = se accetti latenza 5min e vuoi zero modifiche al repo. Setup PC2 una tantum.
|
||||||
|
|
||||||
|
Stima effort:
|
||||||
|
- Opzione A: 1-2 ore (compose modif + test).
|
||||||
|
- Opzione B: 3-4 ore (Python modif + test + rebuild).
|
||||||
|
- Opzione D: 1 ora (script systemd su PC2).
|
||||||
|
|
||||||
|
**Vincoli operativi noti**:
|
||||||
|
- `PC2 = 192.168.123.99`, user `unitree` di solito ha permessi docker via `sg docker -c`.
|
||||||
|
- Path container sul PC2: `~/Robot_2026/RealSense-D435/` (confermato da STATUS.md).
|
||||||
|
- Network host (proxy tinyproxy `localhost:8888`) — vedi STATUS.md per dettagli build.
|
||||||
|
|
||||||
|
Buona fortuna.
|
||||||
+35
-3
@@ -40,11 +40,43 @@ services:
|
|||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
required: false
|
required: false
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
labels:
|
||||||
|
# autoheal monitora SOLO i container con questa label = true
|
||||||
|
autoheal: "true"
|
||||||
healthcheck:
|
healthcheck:
|
||||||
test: ["CMD", "python3", "-c", "import urllib.request; urllib.request.urlopen('http://localhost:8878/api/health')"]
|
# Healthcheck "vero": fallisce se camera_open != true.
|
||||||
interval: 10s
|
# Usa /api/health (pubblico, no API key) che già ritorna camera_open.
|
||||||
|
# 4 retry × 15s ⇒ ~1 min di stato cattivo prima dell'autoheal restart.
|
||||||
|
test:
|
||||||
|
- CMD
|
||||||
|
- python3
|
||||||
|
- -c
|
||||||
|
- |
|
||||||
|
import urllib.request, json, sys
|
||||||
|
try:
|
||||||
|
r = urllib.request.urlopen('http://localhost:8878/api/health', timeout=3)
|
||||||
|
d = json.loads(r.read())
|
||||||
|
sys.exit(0 if d.get('data', {}).get('camera_open') else 1)
|
||||||
|
except Exception:
|
||||||
|
sys.exit(1)
|
||||||
|
interval: 15s
|
||||||
timeout: 5s
|
timeout: 5s
|
||||||
retries: 3
|
retries: 4
|
||||||
|
start_period: 30s
|
||||||
|
|
||||||
|
# -- Autoheal -------------------------------
|
||||||
|
# Monitora i container con label `autoheal=true` e li restarta automaticamente
|
||||||
|
# quando Docker li marca "unhealthy". Risolve il caso "pyrealsense2 SDK stuck".
|
||||||
|
autoheal:
|
||||||
|
image: willfarrell/autoheal:latest
|
||||||
|
container_name: autoheal-d435
|
||||||
|
restart: always
|
||||||
|
environment:
|
||||||
|
AUTOHEAL_CONTAINER_LABEL: autoheal
|
||||||
|
AUTOHEAL_INTERVAL: 10
|
||||||
|
AUTOHEAL_START_PERIOD: 30
|
||||||
|
volumes:
|
||||||
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
|
|
||||||
# -- Frontend React Dev (hot reload) --------
|
# -- Frontend React Dev (hot reload) --------
|
||||||
frontend-dev:
|
frontend-dev:
|
||||||
|
|||||||
Reference in new issue
Block a user