feat: добавить обработку таймаутов и пропуск нездоровых контейнеров
- Добавлена функция пропуска контейнеров с проблемными health check - Добавлены таймауты для предотвращения зависания приложения - Добавлены переменные окружения для настройки таймаутов - Улучшена обработка ошибок при получении информации о контейнерах - Добавлено подробное логирование проблемных контейнеров Автор: Сергей Антропов Сайт: https://devops.org.ru
This commit is contained in:
92
app.py
92
app.py
@@ -17,6 +17,10 @@ DEFAULT_TAIL = int(os.getenv("LOGBOARD_TAIL", "500"))
|
|||||||
BASIC_USER = os.getenv("LOGBOARD_USER", "admin")
|
BASIC_USER = os.getenv("LOGBOARD_USER", "admin")
|
||||||
BASIC_PASS = os.getenv("LOGBOARD_PASS", "admin")
|
BASIC_PASS = os.getenv("LOGBOARD_PASS", "admin")
|
||||||
DEFAULT_PROJECT = os.getenv("COMPOSE_PROJECT_NAME") # filter by compose project
|
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()
|
security = HTTPBasic()
|
||||||
app = FastAPI(title="LogBoard+")
|
app = FastAPI(title="LogBoard+")
|
||||||
@@ -48,21 +52,105 @@ def verify_ws_token(token: str) -> bool:
|
|||||||
|
|
||||||
# ---------- DOCKER HELPERS ----------
|
# ---------- DOCKER HELPERS ----------
|
||||||
def list_containers(project: Optional[str] = None, include_stopped: bool = False) -> List[Dict]:
|
def list_containers(project: Optional[str] = None, include_stopped: bool = False) -> List[Dict]:
|
||||||
|
"""
|
||||||
|
Получает список контейнеров, пропуская контейнеры с проблемными health check
|
||||||
|
Автор: Сергей Антропов
|
||||||
|
Сайт: https://devops.org.ru
|
||||||
|
"""
|
||||||
|
import signal
|
||||||
|
import time
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
for c in docker_client.containers.list(all=include_stopped):
|
|
||||||
|
# Функция для обработки таймаута
|
||||||
|
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 {}
|
labels = c.labels or {}
|
||||||
proj = labels.get("com.docker.compose.project")
|
proj = labels.get("com.docker.compose.project")
|
||||||
svc = labels.get("com.docker.compose.service") or c.name
|
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:
|
if project and proj != project:
|
||||||
continue
|
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({
|
items.append({
|
||||||
"id": c.id[:12],
|
"id": c.id[:12],
|
||||||
"name": c.name,
|
"name": c.name,
|
||||||
"image": (c.image.tags[0] if c.image and c.image.tags else c.image.short_id),
|
"image": image_info,
|
||||||
"status": c.status,
|
"status": c.status,
|
||||||
"service": svc,
|
"service": svc,
|
||||||
"project": proj,
|
"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 ""))
|
items.sort(key=lambda x: (x.get("project") or "", x.get("service") or "", x.get("name") or ""))
|
||||||
return items
|
return items
|
||||||
|
|
||||||
|
|||||||
@@ -48,6 +48,15 @@ MAX_CONNECTIONS=100
|
|||||||
CONNECTION_TIMEOUT=30
|
CONNECTION_TIMEOUT=30
|
||||||
READ_TIMEOUT=60
|
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_ENABLED=true
|
||||||
AUTH_METHOD=basic
|
AUTH_METHOD=basic
|
||||||
|
|||||||
Reference in New Issue
Block a user