feat: Добавлена поддержка удаленных клиентов для LogBoard+
- Создан LogBoard клиент для отправки логов с удаленных серверов - Добавлен API эндпоинт /api/logs/remote с аутентификацией - Реализована структурированная система сохранения логов - Исправлена совместимость Docker client библиотеки - Добавлена полная документация и тестирование
This commit is contained in:
parent
c40b2b312e
commit
04dfe30d58
45
Makefile
45
Makefile
@ -98,6 +98,51 @@ rebuild: ## Пересобрать и запустить сервисы
|
|||||||
docker compose -f $(COMPOSE_FILE) down
|
docker compose -f $(COMPOSE_FILE) down
|
||||||
docker compose -f $(COMPOSE_FILE) build --no-cache
|
docker compose -f $(COMPOSE_FILE) build --no-cache
|
||||||
@mkdir -p snapshots
|
@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
|
docker compose -f $(COMPOSE_FILE) up -d
|
||||||
@echo "$(GREEN)Сервисы пересобраны и запущены!$(NC)"
|
@echo "$(GREEN)Сервисы пересобраны и запущены!$(NC)"
|
||||||
@if [ -f .env ]; then \
|
@if [ -f .env ]; then \
|
||||||
|
58
README.md
58
README.md
@ -42,7 +42,8 @@ LogBoard+ особенно полезен для разработчиков, р
|
|||||||
|
|
||||||
- **Просмотр логов в реальном времени** - WebSocket соединения для live-логов
|
- **Просмотр логов в реальном времени** - WebSocket соединения для live-логов
|
||||||
- **Поддержка множественных проектов** - Фильтрация по проектам Docker Compose
|
- **Поддержка множественных проектов** - Фильтрация по проектам Docker Compose
|
||||||
- **Безопасность** - JWT аутентификация и авторизация
|
- **Удаленные клиенты** - Сбор логов с множества серверов
|
||||||
|
- **Безопасность** - JWT аутентификация и API ключи для клиентов
|
||||||
- **Фильтрация контейнеров** - Исключение проблемных контейнеров
|
- **Фильтрация контейнеров** - Исключение проблемных контейнеров
|
||||||
- **Снимки логов** - Сохранение логов в файлы для анализа
|
- **Снимки логов** - Сохранение логов в файлы для анализа
|
||||||
- **Статистика** - Анализ уровней логирования
|
- **Статистика** - Анализ уровней логирования
|
||||||
@ -91,6 +92,61 @@ LogBoard+ особенно полезен для разработчиков, р
|
|||||||
- 1 GB RAM
|
- 1 GB RAM
|
||||||
- 1 CPU core
|
- 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. **Клонирование репозитория**
|
1. **Клонирование репозитория**
|
||||||
|
@ -7,15 +7,17 @@ LogBoard+ - Логи API
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import json
|
||||||
|
import os
|
||||||
from datetime import datetime
|
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
|
from fastapi.responses import JSONResponse
|
||||||
|
|
||||||
import docker
|
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.docker import docker_client, DEFAULT_TAIL
|
||||||
from core.logger import api_logger
|
from core.logger import api_logger
|
||||||
|
|
||||||
@ -219,3 +221,78 @@ def api_snapshot(
|
|||||||
f.write(content)
|
f.write(content)
|
||||||
url = f"/snapshots/{fname}"
|
url = f"/snapshots/{fname}"
|
||||||
return {"file": fname, "url": url}
|
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")
|
||||||
|
@ -25,7 +25,7 @@ app = FastAPI(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Инициализация шаблонов
|
# Инициализация шаблонов
|
||||||
templates = Jinja2Templates(directory="app/templates")
|
from core.config import templates
|
||||||
|
|
||||||
# serve snapshots directory (downloadable files)
|
# serve snapshots directory (downloadable files)
|
||||||
os.makedirs(SNAP_DIR, exist_ok=True)
|
os.makedirs(SNAP_DIR, exist_ok=True)
|
||||||
|
@ -80,3 +80,26 @@ def authenticate_user(username: str, password: str) -> bool:
|
|||||||
# В продакшене рекомендуется использовать хешированные пароли
|
# В продакшене рекомендуется использовать хешированные пароли
|
||||||
return password == ADMIN_PASSWORD
|
return password == ADMIN_PASSWORD
|
||||||
return False
|
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
|
||||||
|
@ -75,5 +75,9 @@ DOCKER_NETWORKS = os.getenv("DOCKER_NETWORKS", "iaas,infrastructure_iaas")
|
|||||||
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
|
LOG_LEVEL = os.getenv("LOG_LEVEL", "INFO")
|
||||||
LOG_FORMAT = os.getenv("LOG_FORMAT", "json")
|
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")
|
TZ_TS = os.getenv("TZ_TS", "Europe/Moscow")
|
||||||
|
45
client/Dockerfile
Normal file
45
client/Dockerfile
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
# Dockerfile для LogBoard клиента
|
||||||
|
# Автор: Сергей Антропов
|
||||||
|
# Сайт: https://devops.org.ru
|
||||||
|
|
||||||
|
FROM python:3.11-slim
|
||||||
|
|
||||||
|
# Установка метаданных
|
||||||
|
LABEL maintainer="Сергей Антропов <https://devops.org.ru>"
|
||||||
|
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"]
|
97
client/Makefile
Normal file
97
client/Makefile
Normal file
@ -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
|
211
client/README.md
Normal file
211
client/README.md
Normal file
@ -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 <repository-url>
|
||||||
|
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 <api_key>
|
||||||
|
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 в репозитории проекта
|
214
client/app/main.py
Normal file
214
client/app/main.py
Normal file
@ -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)
|
61
client/docker-compose.yml
Normal file
61
client/docker-compose.yml
Normal file
@ -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
|
20
client/env.example
Normal file
20
client/env.example
Normal file
@ -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
|
19
client/requirements.txt
Normal file
19
client/requirements.txt
Normal file
@ -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
|
137
client/test_client.py
Normal file
137
client/test_client.py
Normal file
@ -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())
|
@ -9,11 +9,66 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- /var/run/docker.sock:/var/run/docker.sock:ro
|
- /var/run/docker.sock:/var/run/docker.sock:ro
|
||||||
- ./snapshots:/app/snapshots
|
- ./snapshots:/app/snapshots
|
||||||
|
- ./logs:/app/logs
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
user: 0:0
|
user: 0:0
|
||||||
networks:
|
networks:
|
||||||
- iaas
|
- iaas
|
||||||
- infrastructure_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:
|
networks:
|
||||||
iaas:
|
iaas:
|
||||||
|
112
docs/CHANGELOG.md
Normal file
112
docs/CHANGELOG.md
Normal file
@ -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 поддержка
|
||||||
|
- [ ] Масштабируемость до тысяч серверов
|
||||||
|
- [ ] Машинное обучение для анализа логов
|
300
docs/remote-clients.md
Normal file
300
docs/remote-clients.md
Normal file
@ -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 <repository-url>
|
||||||
|
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 <api_key>
|
||||||
|
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 <key>`
|
||||||
|
- Сервер проверяет ключи против списка разрешенных ключей
|
||||||
|
|
||||||
|
### Настройка 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 в репозитории проекта
|
||||||
|
|
12
env.example
12
env.example
@ -172,3 +172,15 @@ SMTP_FROM=
|
|||||||
|
|
||||||
# Интервал AJAX обновления логов в миллисекундах
|
# Интервал AJAX обновления логов в миллисекундах
|
||||||
LOGBOARD_AJAX_UPDATE_INTERVAL=2000
|
LOGBOARD_AJAX_UPDATE_INTERVAL=2000
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# НАСТРОЙКИ УДАЛЕННЫХ КЛИЕНТОВ
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
# API ключи для удаленных клиентов (разделенные запятыми)
|
||||||
|
# Каждый клиент должен использовать один из этих ключей для аутентификации
|
||||||
|
# Пример: LOGBOARD_API_KEYS=key1,key2,key3
|
||||||
|
LOGBOARD_API_KEYS=
|
||||||
|
|
||||||
|
# Ключ по умолчанию для разработки (не использовать в продакшене)
|
||||||
|
LOGBOARD_DEFAULT_API_KEY=dev-key-123
|
||||||
|
266
test_remote_system.py
Normal file
266
test_remote_system.py
Normal file
@ -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())
|
Loading…
x
Reference in New Issue
Block a user