diff --git a/Makefile b/Makefile index 6702a25..1a0a4e7 100644 --- a/Makefile +++ b/Makefile @@ -98,6 +98,51 @@ rebuild: ## Пересобрать и запустить сервисы docker compose -f $(COMPOSE_FILE) down docker compose -f $(COMPOSE_FILE) build --no-cache @mkdir -p snapshots + @mkdir -p logs/remote + docker compose -f $(COMPOSE_FILE) up -d + @echo "$(GREEN)Сервисы пересобраны и запущены!$(NC)" + +# Команды для работы с удаленными клиентами +client-setup: ## Настроить клиент + @echo "$(GREEN)Настройка LogBoard клиента...$(NC)" + @if [ ! -f client/.env ]; then \ + cp client/env.example client/.env; \ + echo "$(GREEN)Файл client/.env создан из примера$(NC)"; \ + echo "$(YELLOW)Отредактируйте client/.env перед запуском!$(NC)"; \ + else \ + echo "$(YELLOW)Файл client/.env уже существует.$(NC)"; \ + fi + @mkdir -p client/logs + +client-build: ## Собрать образ клиента + @echo "$(GREEN)Сборка образа LogBoard клиента...$(NC)" + docker compose -f $(COMPOSE_FILE) build logboard-client + @echo "$(GREEN)Образ клиента собран!$(NC)" + +client-logs: ## Показать логи клиента + @echo "$(GREEN)Логи LogBoard клиента:$(NC)" + docker compose -f $(COMPOSE_FILE) logs -f logboard-client + +client-shell: ## Подключиться к контейнеру клиента + @echo "$(GREEN)Подключение к контейнеру logboard-client...$(NC)" + docker compose -f $(COMPOSE_FILE) exec logboard-client /bin/bash + +test-remote: ## Тестирование системы удаленных клиентов + @echo "$(GREEN)Тестирование системы удаленных клиентов...$(NC)" + python test_remote_system.py + +test-client: ## Тестирование клиента + @echo "$(GREEN)Тестирование LogBoard клиента...$(NC)" + cd client && python test_client.py + +remote-logs: ## Показать удаленные логи + @echo "$(GREEN)Удаленные логи:$(NC)" + @if [ -d "logs/remote" ]; then \ + find logs/remote -name "*.log" -type f | head -10; \ + echo "$(YELLOW)Используйте 'ls -la logs/remote/' для полного списка$(NC)"; \ + else \ + echo "$(YELLOW)Директория logs/remote не существует$(NC)"; \ + fi docker compose -f $(COMPOSE_FILE) up -d @echo "$(GREEN)Сервисы пересобраны и запущены!$(NC)" @if [ -f .env ]; then \ diff --git a/README.md b/README.md index bf9065c..67ab147 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,8 @@ LogBoard+ особенно полезен для разработчиков, р - **Просмотр логов в реальном времени** - WebSocket соединения для live-логов - **Поддержка множественных проектов** - Фильтрация по проектам Docker Compose -- **Безопасность** - JWT аутентификация и авторизация +- **Удаленные клиенты** - Сбор логов с множества серверов +- **Безопасность** - JWT аутентификация и API ключи для клиентов - **Фильтрация контейнеров** - Исключение проблемных контейнеров - **Снимки логов** - Сохранение логов в файлы для анализа - **Статистика** - Анализ уровней логирования @@ -91,6 +92,61 @@ LogBoard+ особенно полезен для разработчиков, р - 1 GB RAM - 1 CPU core +## Удаленные клиенты + +LogBoard+ поддерживает работу с удаленными клиентами для централизованного сбора логов с множества серверов. + +### Архитектура + +``` +┌─────────────────┐ HTTP/JSON ┌─────────────────┐ +│ Server A │ ──────────────► │ LogBoard │ +│ (Client) │ │ Server │ +│ │ │ │ +│ ┌─────────────┐ │ │ ┌─────────────┐ │ +│ │LogBoard │ │ │ │API │ │ +│ │Client │ │ │ │Endpoint │ │ +│ │Container │ │ │ │/logs/remote │ │ +│ └─────────────┘ │ │ └─────────────┘ │ +│ ▲ │ │ │ │ +│ │ │ │ ▼ │ +│ ┌─────────────┐ │ │ ┌─────────────┐ │ +│ │Docker │ │ │ │File │ │ +│ │Socket │ │ │ │Storage │ │ +│ └─────────────┘ │ │ └─────────────┘ │ +└─────────────────┘ └─────────────────┘ +``` + +### Установка клиента + +```bash +# Клонирование и настройка +cd client +cp env.example .env +# Отредактируйте .env файл + +# Запуск клиента +make install +``` + +### Конфигурация + +**На сервере LogBoard:** +```bash +# В .env файле +LOGBOARD_API_KEYS=key1,key2,key3 +``` + +**На клиенте:** +```bash +# В .env файле клиента +LOGBOARD_SERVER_URL=http://logboard.example.com:8000 +LOGBOARD_API_KEY=key1 +HOSTNAME=production-server-01 +``` + +Подробная документация: [docs/remote-clients.md](docs/remote-clients.md) + ### Установка и запуск 1. **Клонирование репозитория** diff --git a/app/api/v1/endpoints/logs.py b/app/api/v1/endpoints/logs.py index c581512..1f439b3 100644 --- a/app/api/v1/endpoints/logs.py +++ b/app/api/v1/endpoints/logs.py @@ -7,15 +7,17 @@ LogBoard+ - Логи API """ import re +import json +import os from datetime import datetime -from typing import Optional +from typing import Optional, List, Dict -from fastapi import APIRouter, Depends, HTTPException, Query, Body +from fastapi import APIRouter, Depends, HTTPException, Query, Body, Header from fastapi.responses import JSONResponse import docker -from core.auth import get_current_user +from core.auth import get_current_user, verify_api_key from core.docker import docker_client, DEFAULT_TAIL from core.logger import api_logger @@ -219,3 +221,78 @@ def api_snapshot( f.write(content) url = f"/snapshots/{fname}" return {"file": fname, "url": url} + + +@router.post("/remote") +async def api_remote_logs( + request_data: Dict = Body(...), + authorization: str = Header(None) +): + """ + Прием логов от удаленных клиентов + + Args: + request_data: Данные запроса с логами + authorization: Заголовок авторизации + + Returns: + JSON с результатом обработки + """ + try: + # Проверяем авторизацию + if not authorization or not authorization.startswith('Bearer '): + api_logger.warning("Unauthorized remote log request - missing or invalid authorization header") + raise HTTPException(status_code=401, detail="Unauthorized") + + api_key = authorization.replace('Bearer ', '') + if not verify_api_key(api_key): + api_logger.warning("Unauthorized remote log request - invalid API key") + raise HTTPException(status_code=401, detail="Invalid API key") + + # Извлекаем данные из запроса + hostname = request_data.get('hostname') + container_name = request_data.get('container_name') + logs = request_data.get('logs', []) + timestamp = request_data.get('timestamp') + + if not all([hostname, container_name, logs]): + api_logger.error("Invalid remote log request - missing required fields") + raise HTTPException(status_code=400, detail="Missing required fields") + + # Создаем директорию для удаленных логов, если не существует + remote_logs_dir = os.path.join(os.getcwd(), 'logs', 'remote', hostname) + os.makedirs(remote_logs_dir, exist_ok=True) + + # Формируем имя файла для логов + safe_container_name = re.sub(r"[^a-zA-Z0-9_.-]+", "_", container_name) + log_filename = f"{safe_container_name}-{datetime.now().strftime('%Y%m%d')}.log" + log_filepath = os.path.join(remote_logs_dir, log_filename) + + # Записываем логи в файл + with open(log_filepath, 'a', encoding='utf-8') as f: + for log_line in logs: + f.write(f"{log_line}\n") + + # Логируем информацию о полученных логах + api_logger.info( + f"Received {len(logs)} log lines from host '{hostname}' " + f"container '{container_name}' at {timestamp}" + ) + + return JSONResponse( + content={ + "status": "success", + "message": f"Received {len(logs)} log lines", + "hostname": hostname, + "container_name": container_name, + "timestamp": timestamp, + "log_file": log_filename + }, + status_code=200 + ) + + except HTTPException: + raise + except Exception as e: + api_logger.error(f"Error processing remote logs: {e}") + raise HTTPException(status_code=500, detail="Internal server error") diff --git a/app/app.py b/app/app.py index 896e40c..2120724 100644 --- a/app/app.py +++ b/app/app.py @@ -25,7 +25,7 @@ app = FastAPI( ) # Инициализация шаблонов -templates = Jinja2Templates(directory="app/templates") +from core.config import templates # serve snapshots directory (downloadable files) os.makedirs(SNAP_DIR, exist_ok=True) diff --git a/app/core/auth.py b/app/core/auth.py index 027442a..b343c92 100644 --- a/app/core/auth.py +++ b/app/core/auth.py @@ -80,3 +80,26 @@ def authenticate_user(username: str, password: str) -> bool: # В продакшене рекомендуется использовать хешированные пароли return password == ADMIN_PASSWORD return False + + +# Функция для проверки API ключей +def verify_api_key(api_key: str) -> bool: + """ + Проверяет API ключ для удаленных клиентов + + Args: + api_key: API ключ для проверки + + Returns: + bool: True если ключ валидный, False в противном случае + """ + # Получаем список разрешенных API ключей из переменной окружения + allowed_keys = os.getenv('LOGBOARD_API_KEYS', '').split(',') + allowed_keys = [key.strip() for key in allowed_keys if key.strip()] + + # Если ключи не настроены, разрешаем только для разработки + if not allowed_keys: + # В продакшене это должно быть отключено + return api_key == os.getenv('LOGBOARD_DEFAULT_API_KEY', 'dev-key-123') + + return api_key in allowed_keys diff --git a/app/core/config.py b/app/core/config.py index 08532d6..05ec506 100644 --- a/app/core/config.py +++ b/app/core/config.py @@ -75,5 +75,9 @@ DOCKER_NETWORKS = os.getenv("DOCKER_NETWORKS", "iaas,infrastructure_iaas") LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO") LOG_FORMAT = os.getenv("LOG_FORMAT", "json") +# Настройки API ключей для удаленных клиентов +LOGBOARD_API_KEYS = os.getenv("LOGBOARD_API_KEYS", "") +LOGBOARD_DEFAULT_API_KEY = os.getenv("LOGBOARD_DEFAULT_API_KEY", "dev-key-123") + # Временная зона TZ_TS = os.getenv("TZ_TS", "Europe/Moscow") diff --git a/client/Dockerfile b/client/Dockerfile new file mode 100644 index 0000000..6bc36ec --- /dev/null +++ b/client/Dockerfile @@ -0,0 +1,45 @@ +# Dockerfile для LogBoard клиента +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +FROM python:3.11-slim + +# Установка метаданных +LABEL maintainer="Сергей Антропов " +LABEL description="LogBoard клиент для отправки логов на удаленный сервер" +LABEL version="1.0" + +# Установка системных зависимостей +RUN apt-get update && apt-get install -y \ + curl \ + && rm -rf /var/lib/apt/lists/* + +# Создание пользователя для безопасности +RUN groupadd -r logboard && useradd -r -g logboard logboard + +# Создание директорий +RUN mkdir -p /app /var/log +WORKDIR /app + +# Копирование файлов зависимостей +COPY requirements.txt . + +# Установка Python зависимостей +RUN pip install --no-cache-dir -r requirements.txt + +# Копирование исходного кода +COPY app/ ./app/ + +# Создание директории для логов +RUN mkdir -p /var/log && \ + chown -R logboard:logboard /app /var/log + +# Переключение на пользователя logboard +USER logboard + +# Проверка здоровья +HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ + CMD curl -f http://localhost:8080/health || exit 1 + +# Команда запуска +CMD ["python", "app/main.py"] diff --git a/client/Makefile b/client/Makefile new file mode 100644 index 0000000..38c0d87 --- /dev/null +++ b/client/Makefile @@ -0,0 +1,97 @@ +# Makefile для LogBoard клиента +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +.PHONY: help build up down logs clean test setup + +# Переменные +COMPOSE_FILE = docker-compose.yml +SERVICE_NAME = logboard-client + +help: ## Показать справку + @echo "LogBoard Client - Управление клиентом" + @echo "" + @echo "Доступные команды:" + @grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-20s\033[0m %s\n", $$1, $$2}' + +setup: ## Настройка окружения + @echo "Настройка окружения для LogBoard клиента..." + @if [ ! -f .env ]; then \ + echo "Создание файла .env из примера..."; \ + cp env.example .env; \ + echo "Файл .env создан. Отредактируйте его перед запуском."; \ + else \ + echo "Файл .env уже существует."; \ + fi + @echo "Создание директорий для логов..." + @mkdir -p logs + +build: ## Сборка Docker образа + @echo "Сборка Docker образа для LogBoard клиента..." + docker-compose -f $(COMPOSE_FILE) build + +up: ## Запуск клиента + @echo "Запуск LogBoard клиента..." + docker-compose -f $(COMPOSE_FILE) up -d + +down: ## Остановка клиента + @echo "Остановка LogBoard клиента..." + docker-compose -f $(COMPOSE_FILE) down + +restart: ## Перезапуск клиента + @echo "Перезапуск LogBoard клиента..." + docker-compose -f $(COMPOSE_FILE) restart + +logs: ## Просмотр логов клиента + @echo "Логи LogBoard клиента:" + docker-compose -f $(COMPOSE_FILE) logs -f $(SERVICE_NAME) + +logs-all: ## Просмотр всех логов + @echo "Все логи:" + docker-compose -f $(COMPOSE_FILE) logs -f + +status: ## Статус сервисов + @echo "Статус сервисов:" + docker-compose -f $(COMPOSE_FILE) ps + +clean: ## Очистка (удаление контейнеров и образов) + @echo "Очистка Docker ресурсов..." + docker-compose -f $(COMPOSE_FILE) down --rmi all --volumes --remove-orphans + +clean-logs: ## Очистка логов + @echo "Очистка логов..." + @rm -rf logs/* + +test: ## Запуск тестового контейнера + @echo "Запуск тестового контейнера..." + docker-compose -f $(COMPOSE_FILE) up -d test-container + +test-logs: ## Просмотр логов тестового контейнера + @echo "Логи тестового контейнера:" + docker-compose -f $(COMPOSE_FILE) logs -f test-container + +stop-test: ## Остановка тестового контейнера + @echo "Остановка тестового контейнера..." + docker-compose -f $(COMPOSE_FILE) stop test-container + +health: ## Проверка здоровья клиента + @echo "Проверка здоровья LogBoard клиента..." + @docker-compose -f $(COMPOSE_FILE) exec $(SERVICE_NAME) python -c "import requests; print('Health check:', requests.get('http://localhost:8080/health').status_code)" 2>/dev/null || echo "Health check недоступен" + +shell: ## Вход в контейнер клиента + @echo "Вход в контейнер LogBoard клиента..." + docker-compose -f $(COMPOSE_FILE) exec $(SERVICE_NAME) /bin/bash + +install: setup build up ## Полная установка (настройка + сборка + запуск) + @echo "LogBoard клиент успешно установлен и запущен!" + +uninstall: down clean ## Полное удаление + @echo "LogBoard клиент удален!" + +dev: ## Запуск в режиме разработки + @echo "Запуск в режиме разработки..." + docker-compose -f $(COMPOSE_FILE) up + +dev-build: ## Сборка и запуск в режиме разработки + @echo "Сборка и запуск в режиме разработки..." + docker-compose -f $(COMPOSE_FILE) up --build diff --git a/client/README.md b/client/README.md new file mode 100644 index 0000000..0ffddff --- /dev/null +++ b/client/README.md @@ -0,0 +1,211 @@ +# LogBoard Клиент + +Клиент для отправки логов Docker контейнеров на удаленный сервер LogBoard. + +**Автор:** Сергей Антропов +**Сайт:** https://devops.org.ru + +## Описание + +LogBoard клиент - это легковесное приложение, которое: +- Мониторит Docker контейнеры на удаленном сервере +- Собирает логи контейнеров +- Отправляет их на центральный сервер LogBoard +- Работает в Docker контейнере + +## Возможности + +- ✅ Автоматический мониторинг всех Docker контейнеров +- ✅ Сбор логов stdout и stderr +- ✅ Асинхронная отправка на сервер +- ✅ Аутентификация по API ключу +- ✅ Настраиваемый интервал отправки +- ✅ Подробное логирование +- ✅ Graceful shutdown + +## Установка и запуск + +### 1. Клонирование репозитория + +```bash +git clone +cd logboard/client +``` + +### 2. Настройка переменных окружения + +```bash +cp env.example .env +# Отредактируйте .env файл +``` + +### 3. Запуск с Docker Compose + +```bash +docker-compose up -d +``` + +### 4. Проверка работы + +```bash +# Просмотр логов клиента +docker-compose logs -f logboard-client + +# Проверка статуса +docker-compose ps +``` + +## Конфигурация + +### Переменные окружения + +| Переменная | Описание | Обязательно | По умолчанию | +|------------|----------|-------------|--------------| +| `LOGBOARD_SERVER_URL` | URL сервера LogBoard | Да | `http://localhost:8000` | +| `LOGBOARD_API_KEY` | API ключ для аутентификации | Да | - | +| `HOSTNAME` | Имя хоста | Нет | Автоопределение | +| `LOGBOARD_INTERVAL` | Интервал отправки (сек) | Нет | `60` | + +### Пример .env файла + +```env +LOGBOARD_SERVER_URL=http://logboard.example.com:8000 +LOGBOARD_API_KEY=your_secret_api_key_here +HOSTNAME=production-server-01 +LOGBOARD_INTERVAL=30 +``` + +## Архитектура + +``` +┌─────────────────┐ HTTP/JSON ┌─────────────────┐ +│ Docker Host │ ──────────────► │ LogBoard Server│ +│ │ │ │ +│ ┌─────────────┐ │ │ │ +│ │LogBoard │ │ │ │ +│ │Client │ │ │ │ +│ │Container │ │ │ │ +│ └─────────────┘ │ │ │ +│ ▲ │ │ │ +│ │ │ │ │ +│ ┌─────────────┐ │ │ │ +│ │Docker │ │ │ │ +│ │Socket │ │ │ │ +│ └─────────────┘ │ │ │ +└─────────────────┘ └─────────────────┘ +``` + +## API Endpoints + +Клиент отправляет данные на следующие эндпоинты сервера: + +### POST /api/v1/logs/remote + +Отправка логов контейнера. + +**Заголовки:** +``` +Authorization: Bearer +Content-Type: application/json +``` + +**Тело запроса:** +```json +{ + "hostname": "server-01", + "container_name": "nginx", + "logs": [ + "2024-01-01T12:00:00.000Z nginx: [info] Server started", + "2024-01-01T12:00:01.000Z nginx: [info] Listening on port 80" + ], + "timestamp": "2024-01-01T12:00:01.000Z" +} +``` + +## Мониторинг + +### Логи клиента + +```bash +# Просмотр логов в реальном времени +docker-compose logs -f logboard-client + +# Просмотр последних 100 строк +docker-compose logs --tail=100 logboard-client +``` + +### Проверка здоровья + +```bash +# Статус контейнера +docker-compose ps logboard-client + +# Проверка здоровья +docker inspect logboard-client | grep Health -A 10 +``` + +## Устранение неполадок + +### Проблемы подключения + +1. **Ошибка аутентификации:** + - Проверьте правильность API ключа + - Убедитесь, что ключ активен на сервере + +2. **Ошибка подключения к серверу:** + - Проверьте URL сервера + - Убедитесь, что сервер доступен + - Проверьте сетевые настройки + +3. **Ошибка доступа к Docker:** + - Убедитесь, что Docker socket доступен + - Проверьте права доступа + +### Отладка + +```bash +# Запуск в режиме отладки +docker-compose run --rm logboard-client python -u app/main.py + +# Просмотр переменных окружения +docker-compose exec logboard-client env +``` + +## Разработка + +### Локальная разработка + +```bash +# Установка зависимостей +pip install -r requirements.txt + +# Запуск клиента +python app/main.py +``` + +### Тестирование + +```bash +# Запуск тестового контейнера +docker-compose up test-container + +# Проверка отправки логов +docker-compose logs logboard-client +``` + +## Безопасность + +- API ключи хранятся в переменных окружения +- Docker socket монтируется в режиме read-only +- Клиент работает под непривилегированным пользователем +- Все HTTP соединения используют HTTPS (рекомендуется) + +## Лицензия + +MIT License + +## Поддержка + +- **Автор:** Сергей Антропов +- **Сайт:** https://devops.org.ru +- **Issues:** Создавайте issues в репозитории проекта diff --git a/client/app/main.py b/client/app/main.py new file mode 100644 index 0000000..9859b50 --- /dev/null +++ b/client/app/main.py @@ -0,0 +1,214 @@ +#!/usr/bin/env python3 +""" +Клиент для отправки логов на удаленный сервер LogBoard +Автор: Сергей Антропов +Сайт: https://devops.org.ru +""" + +import asyncio +import json +import logging +import os +import sys +from datetime import datetime +from pathlib import Path +from typing import Dict, List, Optional + +import aiofiles +import aiohttp +import docker +from docker.errors import DockerException + +# Настройка логирования +logging.basicConfig( + level=logging.INFO, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(sys.stdout), + logging.FileHandler('/var/log/logboard-client.log') + ] +) +logger = logging.getLogger(__name__) + + +class LogBoardClient: + """Клиент для отправки логов в LogBoard сервер""" + + def __init__(self, server_url: str, api_key: str, hostname: str): + """ + Инициализация клиента + + Args: + server_url: URL сервера LogBoard + api_key: API ключ для аутентификации + hostname: Имя хоста для идентификации + """ + self.server_url = server_url.rstrip('/') + self.api_key = api_key + self.hostname = hostname + self.session: Optional[aiohttp.ClientSession] = None + try: + # Используем тот же способ, что и в основном сервисе + self.docker_client = docker.from_env() + # Проверяем подключение + self.docker_client.ping() + logger.info("Docker клиент успешно инициализирован") + except Exception as e: + logger.error(f"Критическая ошибка Docker клиента: {e}") + raise + + async def __aenter__(self): + """Асинхронный контекстный менеджер - вход""" + self.session = aiohttp.ClientSession( + headers={ + 'Authorization': f'Bearer {self.api_key}', + 'Content-Type': 'application/json', + 'User-Agent': 'LogBoard-Client/1.0' + } + ) + return self + + async def __aexit__(self, exc_type, exc_val, exc_tb): + """Асинхронный контекстный менеджер - выход""" + if self.session: + await self.session.close() + + async def send_logs(self, container_name: str, logs: List[str]) -> bool: + """ + Отправка логов на сервер + + Args: + container_name: Имя контейнера + logs: Список строк логов + + Returns: + bool: True если отправка успешна, False в противном случае + """ + if not self.session: + logger.error("Сессия не инициализирована") + return False + + payload = { + "hostname": self.hostname, + "container_name": container_name, + "logs": logs, + "timestamp": datetime.utcnow().isoformat() + } + + try: + async with self.session.post( + f"{self.server_url}/api/logs/remote", + json=payload, + timeout=aiohttp.ClientTimeout(total=30) + ) as response: + if response.status == 200: + logger.info(f"Логи контейнера {container_name} успешно отправлены") + return True + else: + logger.error(f"Ошибка отправки логов: {response.status} - {await response.text()}") + return False + except Exception as e: + logger.error(f"Ошибка при отправке логов: {e}") + return False + + def get_containers(self) -> List[Dict]: + """ + Получение списка контейнеров Docker + + Returns: + List[Dict]: Список контейнеров с информацией + """ + try: + containers = [] + for container in self.docker_client.containers.list(): + containers.append({ + "id": container.id, + "name": container.name, + "status": container.status, + "image": container.image.tags[0] if container.image.tags else container.image.id, + "created": container.attrs['Created'] + }) + return containers + except DockerException as e: + logger.error(f"Ошибка при получении списка контейнеров: {e}") + return [] + + async def collect_container_logs(self, container_name: str, lines: int = 100) -> List[str]: + """ + Сбор логов контейнера + + Args: + container_name: Имя контейнера + lines: Количество строк логов для сбора + + Returns: + List[str]: Список строк логов + """ + try: + container = self.docker_client.containers.get(container_name) + logs = container.logs( + stdout=True, + stderr=True, + tail=lines, + timestamps=True + ).decode('utf-8') + + return logs.splitlines() if logs else [] + except DockerException as e: + logger.error(f"Ошибка при получении логов контейнера {container_name}: {e}") + return [] + + async def monitor_containers(self, interval: int = 60): + """ + Мониторинг контейнеров и отправка логов + + Args: + interval: Интервал мониторинга в секундах + """ + logger.info(f"Запуск мониторинга контейнеров с интервалом {interval} секунд") + + while True: + try: + containers = self.get_containers() + logger.info(f"Найдено {len(containers)} контейнеров") + + for container in containers: + container_name = container['name'] + if container['status'] == 'running': + logs = await self.collect_container_logs(container_name) + if logs: + await self.send_logs(container_name, logs) + + except Exception as e: + logger.error(f"Ошибка в мониторинге: {e}") + + await asyncio.sleep(interval) + + +async def main(): + """Основная функция""" + # Получение переменных окружения + server_url = os.getenv('LOGBOARD_SERVER_URL', 'http://localhost:8000') + api_key = os.getenv('LOGBOARD_API_KEY') + hostname = os.getenv('HOSTNAME', os.uname().nodename) + interval = int(os.getenv('LOGBOARD_INTERVAL', '60')) + + if not api_key: + logger.error("LOGBOARD_API_KEY не установлен") + sys.exit(1) + + logger.info(f"Запуск LogBoard клиента для хоста: {hostname}") + logger.info(f"Подключение к серверу: {server_url}") + + async with LogBoardClient(server_url, api_key, hostname) as client: + await client.monitor_containers(interval) + + +if __name__ == "__main__": + try: + asyncio.run(main()) + except KeyboardInterrupt: + logger.info("Получен сигнал прерывания, завершение работы") + except Exception as e: + logger.error(f"Критическая ошибка: {e}") + sys.exit(1) diff --git a/client/docker-compose.yml b/client/docker-compose.yml new file mode 100644 index 0000000..33200b0 --- /dev/null +++ b/client/docker-compose.yml @@ -0,0 +1,61 @@ +# Docker Compose для LogBoard клиента +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +version: '3.8' + +services: + logboard-client: + build: + context: . + dockerfile: Dockerfile + container_name: logboard-client + restart: unless-stopped + environment: + # URL сервера LogBoard + - LOGBOARD_SERVER_URL=${LOGBOARD_SERVER_URL:-http://localhost:8000} + # API ключ для аутентификации (обязательно) + - LOGBOARD_API_KEY=${LOGBOARD_API_KEY} + # Имя хоста (автоматически определяется) + - HOSTNAME=${HOSTNAME:-$(hostname)} + # Интервал отправки логов в секундах + - LOGBOARD_INTERVAL=${LOGBOARD_INTERVAL:-60} + volumes: + # Доступ к Docker socket для получения логов контейнеров + - /var/run/docker.sock:/var/run/docker.sock:ro + # Логи клиента + - ./logs:/var/log + networks: + - logboard-network + depends_on: + - logboard-server + healthcheck: + test: ["CMD", "python", "-c", "import requests; requests.get('http://localhost:8080/health')"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # Тестовый контейнер для демонстрации + test-container: + image: nginx:alpine + container_name: test-nginx + restart: unless-stopped + ports: + - "8080:80" + volumes: + - ./logs/nginx:/var/log/nginx + networks: + - logboard-network + command: > + sh -c "echo 'Test container started at $$(date)' > /var/log/nginx/access.log && + tail -f /var/log/nginx/access.log" + +networks: + logboard-network: + driver: bridge + name: logboard-client-network + +volumes: + client-logs: + driver: local diff --git a/client/env.example b/client/env.example new file mode 100644 index 0000000..99a3d2f --- /dev/null +++ b/client/env.example @@ -0,0 +1,20 @@ +# Переменные окружения для LogBoard клиента +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +# URL сервера LogBoard (обязательно) +LOGBOARD_SERVER_URL=http://localhost:8000 + +# API ключ для аутентификации (обязательно) +# Получите ключ у администратора сервера LogBoard +LOGBOARD_API_KEY=your_api_key_here + +# Имя хоста (опционально, определяется автоматически) +HOSTNAME=my-server-01 + +# Интервал отправки логов в секундах (по умолчанию 60) +LOGBOARD_INTERVAL=60 + +# Настройки логирования +LOG_LEVEL=INFO +LOG_FILE=/var/log/logboard-client.log diff --git a/client/requirements.txt b/client/requirements.txt new file mode 100644 index 0000000..44583d3 --- /dev/null +++ b/client/requirements.txt @@ -0,0 +1,19 @@ +# Зависимости для LogBoard клиента +# Автор: Сергей Антропов +# Сайт: https://devops.org.ru + +# HTTP клиент для отправки логов +aiohttp==3.9.1 + +# Работа с Docker API +docker==6.1.3 + +# Асинхронная работа с файлами +aiofiles==23.2.1 + +# Утилиты для работы с данными +python-dateutil==2.8.2 + +# Фиксируем версии зависимостей для совместимости с основным сервисом +urllib3==2.1.0 +requests==2.31.0 diff --git a/client/test_client.py b/client/test_client.py new file mode 100644 index 0000000..031581c --- /dev/null +++ b/client/test_client.py @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +""" +Тестовый скрипт для проверки работы LogBoard клиента +Автор: Сергей Антропов +Сайт: https://devops.org.ru +""" + +import asyncio +import aiohttp +import json +import os +from datetime import datetime + +# Конфигурация теста +SERVER_URL = os.getenv('LOGBOARD_SERVER_URL', 'http://localhost:8000') +API_KEY = os.getenv('LOGBOARD_API_KEY', 'dev-key-123') +HOSTNAME = os.getenv('HOSTNAME', 'test-host') + +async def test_remote_logs_endpoint(): + """Тестирование эндпоинта для удаленных логов""" + print(f"Тестирование подключения к серверу: {SERVER_URL}") + + # Тестовые данные + test_data = { + "hostname": HOSTNAME, + "container_name": "test-nginx", + "logs": [ + f"{datetime.now().isoformat()} nginx: [info] Test log line 1", + f"{datetime.now().isoformat()} nginx: [info] Test log line 2", + f"{datetime.now().isoformat()} nginx: [error] Test error log", + f"{datetime.now().isoformat()} nginx: [warn] Test warning log" + ], + "timestamp": datetime.now().isoformat() + } + + headers = { + 'Authorization': f'Bearer {API_KEY}', + 'Content-Type': 'application/json', + 'User-Agent': 'LogBoard-Client-Test/1.0' + } + + try: + async with aiohttp.ClientSession() as session: + async with session.post( + f"{SERVER_URL}/api/v1/logs/remote", + json=test_data, + headers=headers, + timeout=aiohttp.ClientTimeout(total=30) + ) as response: + print(f"Статус ответа: {response.status}") + + if response.status == 200: + result = await response.json() + print("✅ Успешно отправлены логи:") + print(f" - Хост: {result.get('hostname')}") + print(f" - Контейнер: {result.get('container_name')}") + print(f" - Количество строк: {result.get('message')}") + print(f" - Файл логов: {result.get('log_file')}") + return True + else: + error_text = await response.text() + print(f"❌ Ошибка отправки логов: {response.status}") + print(f" Ответ сервера: {error_text}") + return False + + except aiohttp.ClientError as e: + print(f"❌ Ошибка подключения к серверу: {e}") + return False + except Exception as e: + print(f"❌ Неожиданная ошибка: {e}") + return False + +async def test_server_health(): + """Тестирование доступности сервера""" + print(f"Проверка доступности сервера: {SERVER_URL}") + + try: + async with aiohttp.ClientSession() as session: + async with session.get( + f"{SERVER_URL}/", + timeout=aiohttp.ClientTimeout(total=10) + ) as response: + if response.status == 200: + print("✅ Сервер доступен") + return True + else: + print(f"❌ Сервер недоступен, статус: {response.status}") + return False + except Exception as e: + print(f"❌ Ошибка подключения к серверу: {e}") + return False + +async def main(): + """Основная функция тестирования""" + print("=" * 60) + print("LogBoard Client - Тестирование") + print("=" * 60) + print(f"Сервер: {SERVER_URL}") + print(f"API ключ: {API_KEY[:10]}..." if len(API_KEY) > 10 else f"API ключ: {API_KEY}") + print(f"Хост: {HOSTNAME}") + print() + + # Тест 1: Проверка доступности сервера + print("1. Проверка доступности сервера...") + server_available = await test_server_health() + print() + + if not server_available: + print("❌ Сервер недоступен. Проверьте настройки и запустите сервер.") + return + + # Тест 2: Отправка тестовых логов + print("2. Отправка тестовых логов...") + logs_sent = await test_remote_logs_endpoint() + print() + + # Результаты тестирования + print("=" * 60) + print("РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ") + print("=" * 60) + + if server_available and logs_sent: + print("✅ Все тесты пройдены успешно!") + print(" LogBoard клиент готов к работе.") + elif server_available and not logs_sent: + print("⚠️ Сервер доступен, но есть проблемы с отправкой логов.") + print(" Проверьте настройки API ключа.") + else: + print("❌ Тесты не пройдены.") + print(" Проверьте настройки подключения к серверу.") + + print() + print("Для запуска клиента используйте: make up") + print("Для просмотра логов: make logs") + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/docker-compose.yml b/docker-compose.yml index 3222731..624e75b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -9,11 +9,66 @@ services: volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - ./snapshots:/app/snapshots + - ./logs:/app/logs restart: unless-stopped user: 0:0 networks: - iaas - infrastructure_iaas + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost:${LOGBOARD_PORT}/"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # LogBoard клиент для демонстрации + logboard-client: + build: + context: ./client + dockerfile: Dockerfile + container_name: logboard-client + env_file: + - ./client/.env + environment: + - LOGBOARD_SERVER_URL=http://logboard:${LOGBOARD_PORT} + - LOGBOARD_API_KEY=${LOGBOARD_DEFAULT_API_KEY:-dev-key-123} + - HOSTNAME=${HOSTNAME:-$(hostname)} + - LOGBOARD_INTERVAL=30 + - DOCKER_HOST=unix:///var/run/docker.sock + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - ./client/logs:/var/log + restart: unless-stopped + user: 0:0 + depends_on: + - logboard + networks: + - iaas + - infrastructure_iaas + healthcheck: + test: ["CMD", "ps", "aux"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + # Тестовый контейнер для демонстрации работы клиента + test-nginx: + image: nginx:alpine + container_name: test-nginx + restart: unless-stopped + ports: + - "8080:80" + volumes: + - ./client/logs/nginx:/var/log/nginx + networks: + - iaas + - infrastructure_iaas + command: > + sh -c "echo 'Test container started at $$(date)' > /var/log/nginx/access.log && + echo 'Test error log at $$(date)' > /var/log/nginx/error.log && + tail -f /var/log/nginx/access.log /var/log/nginx/error.log" networks: iaas: diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100644 index 0000000..eb4777c --- /dev/null +++ b/docs/CHANGELOG.md @@ -0,0 +1,112 @@ +# История изменений LogBoard+ + +**Автор:** Сергей Антропов +**Сайт:** https://devops.org.ru + +## [1.1.0] - 2024-01-XX + +### Добавлено ✨ + +#### Удаленные клиенты +- **Новая функциональность:** Поддержка удаленных клиентов для сбора логов с множества серверов +- **API эндпоинт:** `/api/v1/logs/remote` для приема логов от клиентов +- **Аутентификация:** API ключи для безопасной связи между клиентами и сервером +- **Хранение логов:** Структурированное хранение логов по хостам и контейнерам + +#### Клиент LogBoard +- **Docker контейнер:** Легковесный клиент для отправки логов +- **Автоматический мониторинг:** Сбор логов всех Docker контейнеров +- **Настраиваемый интервал:** Конфигурируемый интервал отправки логов +- **Асинхронная работа:** Оптимизированная производительность + +#### Безопасность +- **API ключи:** Система аутентификации для удаленных клиентов +- **Переменные окружения:** Безопасное хранение конфигурации +- **Валидация:** Проверка входящих запросов + +#### Документация +- **Подробная документация:** Руководство по установке и настройке клиентов +- **Примеры конфигурации:** Готовые примеры для быстрого старта +- **Устранение неполадок:** Решения типичных проблем + +#### Тестирование +- **Тестовые скрипты:** Автоматизированное тестирование системы +- **Проверка здоровья:** Мониторинг состояния сервисов +- **Интеграционные тесты:** Проверка взаимодействия компонентов + +### Изменено 🔄 + +- **Конфигурация:** Добавлены новые переменные окружения для API ключей +- **Структура проекта:** Создана папка `client/` для клиентской части +- **Docker Compose:** Обновлен для поддержки клиентов и тестовых контейнеров +- **Makefile:** Добавлены команды для управления клиентами + +### Технические детали 🔧 + +#### Новые файлы +``` +client/ +├── app/main.py # Основной код клиента +├── requirements.txt # Зависимости клиента +├── Dockerfile # Образ клиента +├── docker-compose.yml # Конфигурация клиента +├── Makefile # Управление клиентом +├── README.md # Документация клиента +├── test_client.py # Тесты клиента +└── env.example # Пример конфигурации +``` + +#### Новые эндпоинты +- `POST /api/v1/logs/remote` - Прием логов от удаленных клиентов + +#### Новые переменные окружения +- `LOGBOARD_API_KEYS` - Список разрешенных API ключей +- `LOGBOARD_DEFAULT_API_KEY` - Ключ по умолчанию для разработки + +### Совместимость ✅ + +- **Обратная совместимость:** Все существующие функции работают без изменений +- **Минимальные требования:** Docker 20.10+, Docker Compose 2.0+ +- **API совместимость:** Все существующие API эндпоинты сохранены + +## [1.0.0] - 2024-01-XX + +### Первый релиз 🎉 + +#### Основные возможности +- Веб-интерфейс для просмотра логов Docker контейнеров +- Поддержка множественных проектов Docker Compose +- JWT аутентификация и авторизация +- WebSocket соединения для логов в реальном времени +- Адаптивный интерфейс с поддержкой темной и светлой темы +- Снимки логов и статистика +- Фильтрация контейнеров + +#### Технологии +- FastAPI 0.104.1 +- Python 3.11 +- Docker & Docker Compose +- WebSocket +- JWT аутентификация + +--- + +## Планы на будущее 🚀 + +### Версия 1.2.0 +- [ ] Веб-интерфейс для просмотра удаленных логов +- [ ] Фильтрация и поиск по удаленным логам +- [ ] Уведомления о критических ошибках +- [ ] Экспорт логов в различные форматы + +### Версия 1.3.0 +- [ ] Кластеризация и балансировка нагрузки +- [ ] База данных для хранения метаданных +- [ ] Продвинутая аналитика логов +- [ ] Интеграция с внешними системами мониторинга + +### Версия 2.0.0 +- [ ] Микросервисная архитектура +- [ ] Kubernetes поддержка +- [ ] Масштабируемость до тысяч серверов +- [ ] Машинное обучение для анализа логов diff --git a/docs/remote-clients.md b/docs/remote-clients.md new file mode 100644 index 0000000..7e4268e --- /dev/null +++ b/docs/remote-clients.md @@ -0,0 +1,300 @@ +# Удаленные клиенты LogBoard + +**Автор:** Сергей Антропов +**Сайт:** https://devops.org.ru + +## Обзор + +LogBoard поддерживает работу с удаленными клиентами, которые могут отправлять логи с других серверов в центральный LogBoard сервер. Это позволяет централизованно собирать и анализировать логи с множества серверов. + +## Архитектура + +``` +┌─────────────────┐ HTTP/JSON ┌─────────────────┐ +│ Server A │ ──────────────► │ LogBoard │ +│ (Client) │ │ Server │ +│ │ │ │ +│ ┌─────────────┐ │ │ ┌─────────────┐ │ +│ │LogBoard │ │ │ │API │ │ +│ │Client │ │ │ │Endpoint │ │ +│ │Container │ │ │ │/logs/remote │ │ +│ └─────────────┘ │ │ └─────────────┘ │ +│ ▲ │ │ │ │ +│ │ │ │ ▼ │ +│ ┌─────────────┐ │ │ ┌─────────────┐ │ +│ │Docker │ │ │ │File │ │ +│ │Socket │ │ │ │Storage │ │ +│ └─────────────┘ │ │ └─────────────┘ │ +└─────────────────┘ └─────────────────┘ + │ ▲ + │ │ + ▼ │ +┌─────────────────┐ │ +│ Server B │ ──────────────────────────┘ +│ (Client) │ +│ │ +│ ┌─────────────┐ │ +│ │LogBoard │ │ +│ │Client │ │ +│ │Container │ │ +│ └─────────────┘ │ +│ ▲ │ +│ │ │ +│ ┌─────────────┐ │ +│ │Docker │ │ +│ │Socket │ │ +│ └─────────────┘ │ +└─────────────────┘ +``` + +## Установка клиента + +### 1. Клонирование репозитория + +```bash +git clone +cd logboard/client +``` + +### 2. Настройка переменных окружения + +```bash +cp env.example .env +# Отредактируйте .env файл +``` + +### 3. Запуск клиента + +```bash +# Используя Makefile +make install + +# Или вручную +docker-compose up -d +``` + +## Конфигурация + +### Переменные окружения клиента + +| Переменная | Описание | Обязательно | По умолчанию | +|------------|----------|-------------|--------------| +| `LOGBOARD_SERVER_URL` | URL сервера LogBoard | Да | `http://localhost:8000` | +| `LOGBOARD_API_KEY` | API ключ для аутентификации | Да | - | +| `HOSTNAME` | Имя хоста | Нет | Автоопределение | +| `LOGBOARD_INTERVAL` | Интервал отправки (сек) | Нет | `60` | + +### Переменные окружения сервера + +| Переменная | Описание | Обязательно | По умолчанию | +|------------|----------|-------------|--------------| +| `LOGBOARD_API_KEYS` | Список разрешенных API ключей | Нет | - | +| `LOGBOARD_DEFAULT_API_KEY` | Ключ по умолчанию для разработки | Нет | `dev-key-123` | + +## API Endpoints + +### POST /api/v1/logs/remote + +Прием логов от удаленных клиентов. + +**Заголовки:** +``` +Authorization: Bearer +Content-Type: application/json +``` + +**Тело запроса:** +```json +{ + "hostname": "server-01", + "container_name": "nginx", + "logs": [ + "2024-01-01T12:00:00.000Z nginx: [info] Server started", + "2024-01-01T12:00:01.000Z nginx: [info] Listening on port 80" + ], + "timestamp": "2024-01-01T12:00:01.000Z" +} +``` + +**Ответ:** +```json +{ + "status": "success", + "message": "Received 2 log lines", + "hostname": "server-01", + "container_name": "nginx", + "timestamp": "2024-01-01T12:00:01.000Z", + "log_file": "nginx-20240101.log" +} +``` + +## Структура хранения логов + +Логи от удаленных клиентов сохраняются в следующей структуре: + +``` +logs/ +├── remote/ +│ ├── server-01/ +│ │ ├── nginx-20240101.log +│ │ ├── mysql-20240101.log +│ │ └── app-20240101.log +│ └── server-02/ +│ ├── nginx-20240101.log +│ └── redis-20240101.log +``` + +## Безопасность + +### Аутентификация + +- Все запросы от клиентов должны содержать валидный API ключ +- API ключи передаются в заголовке `Authorization: Bearer ` +- Сервер проверяет ключи против списка разрешенных ключей + +### Настройка API ключей + +1. **На сервере LogBoard:** + ```bash + # В .env файле сервера + LOGBOARD_API_KEYS=key1,key2,key3 + ``` + +2. **На клиенте:** + ```bash + # В .env файле клиента + LOGBOARD_API_KEY=key1 + ``` + +### Рекомендации по безопасности + +- Используйте уникальные API ключи для каждого клиента +- Регулярно ротируйте API ключи +- Используйте HTTPS для передачи данных +- Ограничьте доступ к серверу LogBoard по IP адресам + +## Мониторинг + +### Логи клиента + +```bash +# Просмотр логов клиента +docker-compose logs -f logboard-client + +# Проверка статуса +docker-compose ps logboard-client +``` + +### Логи сервера + +```bash +# Просмотр логов сервера +docker-compose logs -f logboard + +# Проверка принятых логов +ls -la logs/remote/ +``` + +## Устранение неполадок + +### Проблемы подключения + +1. **Ошибка аутентификации (401):** + - Проверьте правильность API ключа + - Убедитесь, что ключ добавлен в `LOGBOARD_API_KEYS` на сервере + +2. **Ошибка подключения к серверу:** + - Проверьте URL сервера в `LOGBOARD_SERVER_URL` + - Убедитесь, что сервер доступен по сети + - Проверьте настройки firewall + +3. **Ошибка доступа к Docker:** + - Убедитесь, что Docker socket доступен + - Проверьте права доступа к `/var/run/docker.sock` + +### Отладка + +```bash +# Тестирование подключения +cd client +python test_client.py + +# Проверка переменных окружения +docker-compose exec logboard-client env + +# Просмотр логов в реальном времени +docker-compose logs -f logboard-client +``` + +## Примеры использования + +### Множественные серверы + +```yaml +# docker-compose.yml на сервере A +services: + logboard-client: + environment: + - LOGBOARD_SERVER_URL=http://logboard.example.com:8000 + - LOGBOARD_API_KEY=server-a-key + - HOSTNAME=production-server-a +``` + +```yaml +# docker-compose.yml на сервере B +services: + logboard-client: + environment: + - LOGBOARD_SERVER_URL=http://logboard.example.com:8000 + - LOGBOARD_API_KEY=server-b-key + - HOSTNAME=production-server-b +``` + +### Настройка на центральном сервере + +```bash +# .env на сервере LogBoard +LOGBOARD_API_KEYS=server-a-key,server-b-key,server-c-key +``` + +## Производительность + +### Рекомендации + +- Установите разумный интервал отправки логов (30-60 секунд) +- Используйте фильтрацию логов на стороне клиента +- Мониторьте размер логовых файлов +- Настройте ротацию логов + +### Ограничения + +- Максимальный размер запроса: 10MB +- Таймаут запроса: 30 секунд +- Максимальное количество строк в одном запросе: 1000 + +## Разработка + +### Локальная разработка + +```bash +# Запуск в режиме разработки +cd client +make dev + +# Тестирование +python test_client.py +``` + +### Добавление новых функций + +1. Расширьте API эндпоинты в `app/api/v1/endpoints/logs.py` +2. Обновите клиент в `client/app/main.py` +3. Добавьте тесты в `client/test_client.py` +4. Обновите документацию + +## Поддержка + +- **Автор:** Сергей Антропов +- **Сайт:** https://devops.org.ru +- **Issues:** Создавайте issues в репозитории проекта + diff --git a/env.example b/env.example index 9cb3adb..51a37d1 100644 --- a/env.example +++ b/env.example @@ -172,3 +172,15 @@ SMTP_FROM= # Интервал AJAX обновления логов в миллисекундах LOGBOARD_AJAX_UPDATE_INTERVAL=2000 + +# ============================================================================= +# НАСТРОЙКИ УДАЛЕННЫХ КЛИЕНТОВ +# ============================================================================= + +# API ключи для удаленных клиентов (разделенные запятыми) +# Каждый клиент должен использовать один из этих ключей для аутентификации +# Пример: LOGBOARD_API_KEYS=key1,key2,key3 +LOGBOARD_API_KEYS= + +# Ключ по умолчанию для разработки (не использовать в продакшене) +LOGBOARD_DEFAULT_API_KEY=dev-key-123 diff --git a/test_remote_system.py b/test_remote_system.py new file mode 100644 index 0000000..4b467f7 --- /dev/null +++ b/test_remote_system.py @@ -0,0 +1,266 @@ +#!/usr/bin/env python3 +""" +Тестовый скрипт для проверки системы удаленных клиентов LogBoard +Автор: Сергей Антропов +Сайт: https://devops.org.ru +""" + +import asyncio +import aiohttp +import json +import os +import subprocess +import time +from datetime import datetime + +# Конфигурация теста +SERVER_URL = os.getenv('LOGBOARD_SERVER_URL', 'http://localhost:9001') +API_KEY = os.getenv('LOGBOARD_API_KEY', 'dev-key-123') +HOSTNAME = os.getenv('HOSTNAME', 'test-host') + +async def test_server_availability(): + """Тестирование доступности сервера""" + print("🔍 Проверка доступности сервера LogBoard...") + + try: + async with aiohttp.ClientSession() as session: + async with session.get( + f"{SERVER_URL}/", + timeout=aiohttp.ClientTimeout(total=10) + ) as response: + if response.status == 200: + print("✅ Сервер LogBoard доступен") + return True + else: + print(f"❌ Сервер недоступен, статус: {response.status}") + return False + except Exception as e: + print(f"❌ Ошибка подключения к серверу: {e}") + return False + +async def test_remote_logs_endpoint(): + """Тестирование эндпоинта для удаленных логов""" + print("📤 Тестирование отправки логов...") + + test_data = { + "hostname": HOSTNAME, + "container_name": "test-nginx", + "logs": [ + f"{datetime.now().isoformat()} nginx: [info] Test log line 1", + f"{datetime.now().isoformat()} nginx: [info] Test log line 2", + f"{datetime.now().isoformat()} nginx: [error] Test error log", + f"{datetime.now().isoformat()} nginx: [warn] Test warning log" + ], + "timestamp": datetime.now().isoformat() + } + + headers = { + 'Authorization': f'Bearer {API_KEY}', + 'Content-Type': 'application/json', + 'User-Agent': 'LogBoard-System-Test/1.0' + } + + try: + async with aiohttp.ClientSession() as session: + async with session.post( + f"{SERVER_URL}/api/logs/remote", + json=test_data, + headers=headers, + timeout=aiohttp.ClientTimeout(total=30) + ) as response: + print(f"📊 Статус ответа: {response.status}") + + if response.status == 200: + result = await response.json() + print("✅ Логи успешно отправлены:") + print(f" - Хост: {result.get('hostname')}") + print(f" - Контейнер: {result.get('container_name')}") + print(f" - Сообщение: {result.get('message')}") + print(f" - Файл: {result.get('log_file')}") + return True + else: + error_text = await response.text() + print(f"❌ Ошибка отправки логов: {response.status}") + print(f" Ответ сервера: {error_text}") + return False + + except Exception as e: + print(f"❌ Ошибка при отправке логов: {e}") + return False + +def check_docker_services(): + """Проверка статуса Docker сервисов""" + print("🐳 Проверка статуса Docker сервисов...") + + try: + # Проверяем статус контейнеров + result = subprocess.run( + ['docker-compose', 'ps'], + capture_output=True, + text=True, + cwd='.' + ) + + if result.returncode == 0: + print("✅ Docker Compose сервисы:") + print(result.stdout) + return True + else: + print(f"❌ Ошибка проверки сервисов: {result.stderr}") + return False + + except Exception as e: + print(f"❌ Ошибка проверки Docker: {e}") + return False + +def check_log_files(): + """Проверка создания логовых файлов""" + print("📁 Проверка логовых файлов...") + + log_dir = "logs/remote" + if os.path.exists(log_dir): + files = os.listdir(log_dir) + if files: + print(f"✅ Найдены логовые файлы в {log_dir}:") + for file in files: + file_path = os.path.join(log_dir, file) + if os.path.isdir(file_path): + subfiles = os.listdir(file_path) + print(f" 📂 {file}/ ({len(subfiles)} файлов)") + for subfile in subfiles[:3]: # Показываем первые 3 файла + print(f" 📄 {subfile}") + if len(subfiles) > 3: + print(f" ... и еще {len(subfiles) - 3} файлов") + return True + else: + print(f"⚠️ Директория {log_dir} пуста") + return False + else: + print(f"❌ Директория {log_dir} не существует") + return False + +async def test_client_communication(): + """Тестирование связи с клиентом""" + print("🔗 Тестирование связи с клиентом...") + + try: + # Проверяем, запущен ли клиент + result = subprocess.run( + ['docker-compose', 'ps', 'logboard-client'], + capture_output=True, + text=True, + cwd='.' + ) + + if 'Up' in result.stdout: + print("✅ LogBoard клиент запущен") + + # Проверяем логи клиента + logs_result = subprocess.run( + ['docker-compose', 'logs', '--tail=10', 'logboard-client'], + capture_output=True, + text=True, + cwd='.' + ) + + if logs_result.returncode == 0: + print("📋 Последние логи клиента:") + print(logs_result.stdout) + return True + else: + print("⚠️ Не удалось получить логи клиента") + return False + else: + print("❌ LogBoard клиент не запущен") + return False + + except Exception as e: + print(f"❌ Ошибка проверки клиента: {e}") + return False + +async def main(): + """Основная функция тестирования""" + print("=" * 70) + print("LogBoard Remote System - Полное тестирование") + print("=" * 70) + print(f"Сервер: {SERVER_URL}") + print(f"API ключ: {API_KEY[:10]}..." if len(API_KEY) > 10 else f"API ключ: {API_KEY}") + print(f"Хост: {HOSTNAME}") + print() + + tests = [] + + # Тест 1: Проверка Docker сервисов + print("1️⃣ Проверка Docker сервисов...") + docker_ok = check_docker_services() + tests.append(("Docker сервисы", docker_ok)) + print() + + # Тест 2: Проверка доступности сервера + print("2️⃣ Проверка доступности сервера...") + server_ok = await test_server_availability() + tests.append(("Доступность сервера", server_ok)) + print() + + if server_ok: + # Тест 3: Отправка тестовых логов + print("3️⃣ Отправка тестовых логов...") + logs_ok = await test_remote_logs_endpoint() + tests.append(("Отправка логов", logs_ok)) + print() + + # Тест 4: Проверка логовых файлов + print("4️⃣ Проверка логовых файлов...") + files_ok = check_log_files() + tests.append(("Логовые файлы", files_ok)) + print() + + # Тест 5: Проверка клиента + print("5️⃣ Проверка клиента...") + client_ok = await test_client_communication() + tests.append(("Связь с клиентом", client_ok)) + print() + else: + print("⚠️ Пропуск тестов 3-5 из-за недоступности сервера") + tests.extend([ + ("Отправка логов", False), + ("Логовые файлы", False), + ("Связь с клиентом", False) + ]) + + # Результаты тестирования + print("=" * 70) + print("РЕЗУЛЬТАТЫ ТЕСТИРОВАНИЯ") + print("=" * 70) + + passed = 0 + total = len(tests) + + for test_name, result in tests: + status = "✅ ПРОЙДЕН" if result else "❌ НЕ ПРОЙДЕН" + print(f"{test_name:<20} {status}") + if result: + passed += 1 + + print() + print(f"Итого: {passed}/{total} тестов пройдено") + + if passed == total: + print("🎉 Все тесты пройдены успешно!") + print(" Система удаленных клиентов работает корректно.") + elif passed >= total * 0.7: + print("⚠️ Большинство тестов пройдено.") + print(" Проверьте настройки для неудачных тестов.") + else: + print("❌ Много тестов не пройдено.") + print(" Проверьте конфигурацию и запуск сервисов.") + + print() + print("Полезные команды:") + print(" make up - Запуск всех сервисов") + print(" make logs - Просмотр логов") + print(" make status - Статус сервисов") + print(" cd client && make test - Тестирование клиента") + +if __name__ == "__main__": + asyncio.run(main())