diff --git a/app.py b/app.py index fd5b564..c339d4b 100644 --- a/app.py +++ b/app.py @@ -17,6 +17,10 @@ DEFAULT_TAIL = int(os.getenv("LOGBOARD_TAIL", "500")) BASIC_USER = os.getenv("LOGBOARD_USER", "admin") BASIC_PASS = os.getenv("LOGBOARD_PASS", "admin") DEFAULT_PROJECT = os.getenv("COMPOSE_PROJECT_NAME") # filter by compose project +SKIP_UNHEALTHY = os.getenv("LOGBOARD_SKIP_UNHEALTHY", "true").lower() == "true" +CONTAINER_LIST_TIMEOUT = int(os.getenv("LOGBOARD_CONTAINER_LIST_TIMEOUT", "10")) +CONTAINER_INFO_TIMEOUT = int(os.getenv("LOGBOARD_CONTAINER_INFO_TIMEOUT", "3")) +HEALTH_CHECK_TIMEOUT = int(os.getenv("LOGBOARD_HEALTH_CHECK_TIMEOUT", "2")) security = HTTPBasic() app = FastAPI(title="LogBoard+") @@ -48,21 +52,105 @@ def verify_ws_token(token: str) -> bool: # ---------- DOCKER HELPERS ---------- def list_containers(project: Optional[str] = None, include_stopped: bool = False) -> List[Dict]: + """ + Получает список контейнеров, пропуская контейнеры с проблемными health check + Автор: Сергей Антропов + Сайт: https://devops.org.ru + """ + import signal + import time + items = [] - for c in docker_client.containers.list(all=include_stopped): - labels = c.labels or {} - proj = labels.get("com.docker.compose.project") - svc = labels.get("com.docker.compose.service") or c.name - if project and proj != project: - continue - items.append({ - "id": c.id[:12], - "name": c.name, - "image": (c.image.tags[0] if c.image and c.image.tags else c.image.short_id), - "status": c.status, - "service": svc, - "project": proj, - }) + + # Функция для обработки таймаута + def timeout_handler(signum, frame): + raise TimeoutError("Timeout getting container list") + + try: + # Устанавливаем таймаут на получение списка контейнеров + signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(CONTAINER_LIST_TIMEOUT) + + # Получаем список контейнеров с обработкой ошибок + containers = [] + try: + containers = docker_client.containers.list(all=include_stopped) + except Exception as e: + print(f"❌ Ошибка получения списка контейнеров: {e}") + return [] + + for c in containers: + try: + # Проверяем health status контейнера с таймаутом + health_status = None + try: + # Устанавливаем таймаут на получение health status + signal.alarm(HEALTH_CHECK_TIMEOUT) + health_status = c.attrs.get("State", {}).get("Health", {}).get("Status") + signal.alarm(0) # Отменяем таймаут + except TimeoutError: + print(f"⚠️ Таймаут при получении health status для контейнера {c.name} (ID: {c.id[:12]})") + continue + except Exception as e: + print(f"⚠️ Пропускаем контейнер {c.name} (ID: {c.id[:12]}): не удается получить health status - {e}") + continue + + # Пропускаем контейнеры с проблемными health check (если включено) + if SKIP_UNHEALTHY and health_status == "unhealthy": + print(f"⚠️ Пропускаем нездоровый контейнер {c.name} (ID: {c.id[:12]})") + continue + + # Получаем информацию о контейнере с таймаутом + try: + signal.alarm(CONTAINER_INFO_TIMEOUT) + labels = c.labels or {} + proj = labels.get("com.docker.compose.project") + svc = labels.get("com.docker.compose.service") or c.name + signal.alarm(0) + except TimeoutError: + print(f"⚠️ Таймаут при получении меток контейнера {c.name} (ID: {c.id[:12]})") + continue + + if project and proj != project: + continue + + # Получаем информацию об образе с таймаутом + try: + signal.alarm(HEALTH_CHECK_TIMEOUT) + image_info = c.image.tags[0] if c.image and c.image.tags else c.image.short_id + signal.alarm(0) + except TimeoutError: + print(f"⚠️ Таймаут при получении информации об образе для контейнера {c.name} (ID: {c.id[:12]})") + image_info = "unknown" + except Exception: + image_info = "unknown" + + items.append({ + "id": c.id[:12], + "name": c.name, + "image": image_info, + "status": c.status, + "service": svc, + "project": proj, + "health": health_status, + }) + + except Exception as e: + # Пропускаем контейнеры, которые вызывают ошибки + print(f"⚠️ Пропускаем проблемный контейнер {c.name if hasattr(c, 'name') else 'unknown'} (ID: {c.id[:12]}): {e}") + continue + + signal.alarm(0) # Отменяем таймаут + + except TimeoutError: + print("❌ Таймаут при получении списка контейнеров") + signal.alarm(0) + return [] + except Exception as e: + print(f"❌ Критическая ошибка при получении списка контейнеров: {e}") + signal.alarm(0) + return [] + items.sort(key=lambda x: (x.get("project") or "", x.get("service") or "", x.get("name") or "")) return items diff --git a/env.example b/env.example index 14602a9..c49af5c 100644 --- a/env.example +++ b/env.example @@ -48,6 +48,15 @@ MAX_CONNECTIONS=100 CONNECTION_TIMEOUT=30 READ_TIMEOUT=60 +# Настройки фильтрации контейнеров +# Пропускать контейнеры с проблемными health check (true/false) +LOGBOARD_SKIP_UNHEALTHY=true + +# Настройки таймаутов (в секундах) +LOGBOARD_CONTAINER_LIST_TIMEOUT=10 +LOGBOARD_CONTAINER_INFO_TIMEOUT=3 +LOGBOARD_HEALTH_CHECK_TIMEOUT=2 + # Настройки аутентификации AUTH_ENABLED=true AUTH_METHOD=basic