feat: добавить обработку таймаутов и пропуск нездоровых контейнеров

- Добавлена функция пропуска контейнеров с проблемными health check
- Добавлены таймауты для предотвращения зависания приложения
- Добавлены переменные окружения для настройки таймаутов
- Улучшена обработка ошибок при получении информации о контейнерах
- Добавлено подробное логирование проблемных контейнеров

Автор: Сергей Антропов
Сайт: https://devops.org.ru
This commit is contained in:
Сергей Антропов 2025-08-16 12:15:32 +03:00
parent e2d532c1f5
commit 9239925206
2 changed files with 111 additions and 14 deletions

116
app.py
View File

@ -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

View File

@ -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