Files
Stend_mont/DEPLOY.md

602 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Инструкция по развёртыванию — МОНТ Инфраструктурный Полигон
Версия проекта: **0.6.0**
---
## Содержание
1. [Что это такое](#1-что-это-такое)
2. [Архитектура](#2-архитектура)
3. [Требования к серверу](#3-требования-к-серверу)
4. [Установка зависимостей](#4-установка-зависимостей)
5. [Клонирование репозитория](#5-клонирование-репозитория)
6. [Настройка переменных окружения](#6-настройка-переменных-окружения)
7. [Настройка Traefik](#7-настройка-traefik)
8. [Сборка Docker-образов рантаймов](#8-сборка-docker-образов-рантаймов)
9. [Первый запуск](#9-первый-запуск)
10. [Инициализация базы данных](#10-инициализация-базы-данных)
11. [Проверка работоспособности](#11-проверка-работоспособности)
12. [Настройка через админку](#12-настройка-через-админку)
13. [Настройка RDP-слотов](#13-настройка-rdp-слотов)
14. [Обновление проекта](#14-обновление-проекта)
15. [Описание всех переменных окружения](#15-описание-всех-переменных-окружения)
16. [Частые проблемы и решения](#16-частые-проблемы-и-решения)
---
## 1. Что это такое
Веб-портал для выдачи пользователям браузерного доступа к стендам и сервисам.
Поддерживает три типа сервисов:
| Тип | Описание |
|-----|----------|
| **WEB** | Открывает веб-сайт в браузере Chromium внутри виртуального дисплея (noVNC-стриминг) |
| **VNC** | Подключается по VNC к внешнему хосту |
| **RDP** | Подключается по RDP к внешнему хосту; пул слотов — несколько пользователей одновременно |
Стек: **FastAPI + PostgreSQL + Traefik + Docker**.
---
## 2. Архитектура
```
Интернет
│ HTTPS :443
┌─────────┐
│ Traefik │ — edge-прокси, TLS, маршрутизация по URL
└────┬────┘
│ docker network: portal_net
├──────────────────────────────┐
▼ ▼
┌─────────┐ ┌────────────┐
│ api │ FastAPI/uvicorn │ maintenance│ (отдельный процесс очистки)
└────┬────┘ └────────────┘
├─ /var/run/docker.sock (API управляет контейнерами напрямую)
┌──────────┐
│ db │ PostgreSQL 16
└──────────┘
Динамически создаваемые контейнеры (не в compose):
portal-webpool-N — WEB-сессии (portal-universal-runtime)
portal-universal-N — VNC-сессии (portal-universal-runtime)
portal-rdpslot-SLUG-N — RDP-слоты (portal-rdp-proxy)
```
Traefik читает метки (`labels`) у динамически создаваемых контейнеров и автоматически добавляет маршруты без перезапуска.
---
## 3. Требования к серверу
| Параметр | Минимум | Рекомендовано |
|----------|---------|---------------|
| ОС | Ubuntu 22.04 / Debian 12 | Ubuntu 24.04 |
| CPU | 2 ядра | 4+ ядра |
| RAM | 4 ГБ | 8+ ГБ |
| Диск | 20 ГБ | 40+ ГБ (Docker-образы большие) |
| Docker | 24+ | 29+ |
| Docker Compose | v2.20+ | v2.40+ |
| Внешний IP | Обязателен | — |
| DNS-запись | A-запись домена → IP сервера | — |
| Порты открыты | 80, 443 | — |
> **Важно**: домен и DNS-запись нужны для автоматического получения TLS-сертификата через Let's Encrypt.
> Без домена — использовать самоподписанный сертификат (см. [раздел 7](#7-настройка-traefik)).
---
## 4. Установка зависимостей
```bash
# Обновляем систему
sudo apt-get update && sudo apt-get upgrade -y
# Устанавливаем Docker (официальный способ)
curl -fsSL https://get.docker.com | sh
# Добавляем текущего пользователя в группу docker (без sudo)
sudo usermod -aG docker $USER
newgrp docker
# Проверяем
docker --version # Docker version 29.x.x
docker compose version # Docker Compose version 2.x.x
```
---
## 5. Клонирование репозитория
Все исходники находятся в git-репозитории:
```
https://git.ruslan.xyz/ruslan/Stend_mont
```
```bash
# Рабочий каталог — можно любой, например /opt/stand или ~/docker/stand
mkdir -p ~/docker && cd ~/docker
# Репозиторий приватный — нужны credentials
git clone https://USER:TOKEN@git.ruslan.xyz/ruslan/Stend_mont.git stand
cd stand
```
> Запросите логин и токен у владельца репозитория.
Структура проекта после клонирования:
```
stand/
├── app/ # FastAPI-приложение (Dockerfile + main.py + шаблоны)
├── rdp-proxy/ # Docker-образ RDP-рантайма (xvfb + xfreerdp + noVNC)
├── universal-runtime/ # Docker-образ WEB/VNC-рантайма (Chromium + x11vnc + noVNC)
├── kiosk/ # Docker-образ kiosk-режима (опционально)
├── traefik/ # Конфиг Traefik (traefik.yml + dynamic/)
├── scripts/ # SQL-схема БД
├── docker-compose.yml
└── .env.example # Шаблон переменных окружения
```
---
## 6. Настройка переменных окружения
Скопируйте шаблон и отредактируйте:
```bash
cp .env.example .env
nano .env
```
**Минимальный набор значений, которые нужно поменять:**
```dotenv
# Домен, на котором будет работать портал
PUBLIC_HOST=your-domain.example.com
# Email для Let's Encrypt (уведомления об истечении сертификата)
LETSENCRYPT_EMAIL=admin@example.com
# Пароль PostgreSQL (придумайте сами, не менее 16 символов)
POSTGRES_PASSWORD=supersecretdbpassword
# Секретный ключ для подписи сессий (минимум 32 случайных символа)
# Генерация: python3 -c "import secrets; print(secrets.token_hex(32))"
SIGNING_KEY=your_random_signing_key_here
# Логин и пароль администратора портала
ADMIN_USERNAME=admin
ADMIN_PASSWORD=your_admin_password
```
**Остальные переменные** — описание см. в [разделе 15](#15-описание-всех-переменных-окружения).
---
## 7. Настройка Traefik
### 7.1 Файл `traefik/traefik.yml`
Откройте и проверьте email в секции `certificatesResolvers`:
```bash
nano traefik/traefik.yml
```
```yaml
certificatesResolvers:
letsencrypt:
acme:
email: admin@example.com # ← ваш email
storage: /letsencrypt/acme.json
tlsChallenge: {}
```
Порты по умолчанию: **HTTP :8288**, **HTTPS :2288** (нестандартные, прописаны в `docker-compose.yml`).
Если нужны стандартные 80/443:
```bash
nano docker-compose.yml
# Найдите секцию traefik -> ports и замените:
# - "0.0.0.0:8288:80" → - "80:80"
# - "0.0.0.0:2288:443" → - "443:443"
```
### 7.2 Создание файла хранилища сертификатов
```bash
mkdir -p traefik/letsencrypt
touch traefik/letsencrypt/acme.json
chmod 600 traefik/letsencrypt/acme.json
```
### 7.3 Работа без домена (самоподписанный сертификат)
Если домена нет и нужен только HTTP или самоподписанный HTTPS:
В `traefik/traefik.yml` удалите секцию `certificatesResolvers`.
В `docker-compose.yml` в labels контейнера `api` замените:
```yaml
- traefik.http.routers.portal.entrypoints=websecure
- traefik.http.routers.portal.tls=true
- traefik.http.routers.portal.tls.certresolver=letsencrypt
```
на:
```yaml
- traefik.http.routers.portal.entrypoints=web
```
---
## 8. Сборка Docker-образов рантаймов
Рантаймы — это образы для WEB/VNC/RDP-сессий. Они **не входят** в основной `docker compose up`, их нужно собрать отдельно.
```bash
cd ~/docker/stand
# Образ для WEB и VNC сессий (Chromium + x11vnc + noVNC)
docker build -t portal-universal-runtime:latest ./universal-runtime/
# Образ для RDP сессий (xvfb + xfreerdp + x11vnc + noVNC)
docker build -t portal-rdp-proxy:latest ./rdp-proxy/
# Образ kiosk (опционально, если используется kiosk-режим)
docker build -t portal-kiosk:latest ./kiosk/
```
> Сборка занимает 3–10 минут в зависимости от скорости интернета (скачивается Chromium и другие пакеты).
Проверьте что образы появились:
```bash
docker images | grep portal
# Должно быть:
# portal-universal-runtime latest ...
# portal-rdp-proxy latest ...
# portal-kiosk latest ...
```
---
## 9. Первый запуск
```bash
cd ~/docker/stand
# Запускаем всё (traefik, db, api, maintenance)
docker compose up -d --build
# Смотрим логи запуска
docker compose logs -f api
```
Дождитесь строки:
```
INFO: Application startup complete.
```
Затем откройте браузер: `https://your-domain.example.com`
---
## 10. Инициализация базы данных
База данных инициализируется **автоматически** при первом старте API через SQLAlchemy (ORM создаёт все таблицы).
Если нужно создать таблицы вручную (на случай сбоя):
```bash
# Подключитесь к контейнеру PostgreSQL
docker exec -it stend_mont-db-1 psql -U portal -d portal
# Выполните скрипт схемы
\i /dev/stdin
# вставьте содержимое scripts/schema.sql и нажмите Ctrl+D
```
Или через файл:
```bash
docker exec -i stend_mont-db-1 psql -U portal -d portal < scripts/schema.sql
```
> Таблица `rdp_slots` создаётся автоматически ORM, её нет в `schema.sql` — это нормально.
---
## 11. Проверка работоспособности
```bash
# Статус всех контейнеров
docker compose ps
# Логи отдельных сервисов
docker compose logs api # FastAPI
docker compose logs db # PostgreSQL
docker compose logs traefik # Traefik (маршруты, сертификаты)
docker compose logs maintenance # Фоновая очистка сессий
# Проверка что база доступна
docker exec stend_mont-db-1 pg_isready -U portal
# Проверка API (изнутри сети)
docker exec stend_mont-api-1 curl -s http://localhost:8000/health
```
Ожидаемый вывод `docker compose ps`:
```
NAME STATUS
stend_mont-api-1 Up
stend_mont-db-1 Up (healthy)
stend_mont-maintenance-1 Up
stend_mont-traefik-1 Up
```
---
## 12. Настройка через админку
1. Откройте `https://your-domain.example.com/admin`
2. Войдите с данными `ADMIN_USERNAME` / `ADMIN_PASSWORD` из `.env`
### Создание пользователей
**Пользователи → Добавить пользователя:**
- Логин, пароль, срок действия аккаунта
- После создания — назначить права доступа к сервисам (раздел ACL)
### Создание сервисов
**Сервисы → Добавить сервис:**
| Поле | Описание |
|------|----------|
| Название | Отображаемое название |
| Slug | URL-идентификатор (латиница, цифры, дефис) |
| Тип | WEB / VNC / RDP |
| Адрес | `host:port` для VNC/RDP; URL для WEB |
| Иконка | PNG/SVG, загружается через форму |
| Размер пула | Для WEB: количество прогретых контейнеров (0 = по запросу) |
### Назначение доступа (ACL)
**ACL → выберите пользователя → отметьте сервисы → Сохранить**
---
## 13. Настройка RDP-слотов
RDP-сервисы используют **пул слотов**: каждый слот = отдельный RDP-пользователь + отдельный контейнер.
Пользователи занимают свободные слоты, при их нехватке получают ошибку 503.
### Шаг 1: Создайте RDP-сервис
В админке создайте сервис с типом **RDP**:
- **Адрес**: `hostname_или_ip:3389` (без протокола)
- Домен, безопасность — если нужны
### Шаг 2: Добавьте слоты (RDP-пользователей)
В карточке сервиса появится раздел **«RDP пользователи (слоты пула)»**:
- Введите RDP-логин и пароль пользователя на RDP-сервере
- Нажмите **Добавить**
- Повторите для каждого параллельного пользователя (слота)
### Шаг 3: Запустите контейнеры слотов
```bash
# Перезапустите API с флагом инициализации
cd ~/docker/stand
ENABLE_STARTUP_MAINTENANCE=1 docker compose up -d api maintenance
```
После запуска появятся контейнеры вида `portal-rdpslot-SLUG-N`.
Проверка:
```bash
docker ps | grep rdpslot
```
### Назначение прав на RDP-сервис
В разделе ACL назначьте нужным пользователям доступ к RDP-сервису (обычный чекбокс).
Любой пользователь с доступом может занять любой свободный слот.
---
## 14. Обновление проекта
```bash
cd ~/docker/stand
# Получаем изменения
git pull
# Пересобираем API (если менялся main.py или шаблоны)
docker compose up -d --build api maintenance
# Пересобираем рантаймы (если менялись rdp-proxy/, universal-runtime/, kiosk/)
docker build -t portal-rdp-proxy:latest ./rdp-proxy/
docker build -t portal-universal-runtime:latest ./universal-runtime/
docker build -t portal-kiosk:latest ./kiosk/
# После пересборки рантаймов — перезапустить слот-контейнеры
# Старые запущенные сессии продолжат работать, новые получат новый образ
docker compose down api maintenance
ENABLE_STARTUP_MAINTENANCE=1 docker compose up -d api maintenance
```
> **Важно**: `docker compose up -d api` **без** `--build` НЕ обновит код, если контейнер уже запущен.
> Всегда используйте `--build` после изменений в `app/`.
---
## 15. Описание всех переменных окружения
| Переменная | По умолчанию | Описание |
|------------|-------------|----------|
| `COMPOSE_PROJECT_NAME` | `stend_mont` | Префикс имён контейнеров Docker |
| `PUBLIC_HOST` | — | **Обязательно.** Домен сайта, напр. `stand.example.com` |
| `LETSENCRYPT_EMAIL` | — | Email для Let's Encrypt |
| `POSTGRES_DB` | `portal` | Имя базы данных |
| `POSTGRES_USER` | `portal` | Пользователь PostgreSQL |
| `POSTGRES_PASSWORD` | — | **Обязательно.** Пароль PostgreSQL |
| `SIGNING_KEY` | — | **Обязательно.** Секрет для подписи сессионных токенов (мин. 32 символа) |
| `ADMIN_USERNAME` | `admin` | Логин администратора |
| `ADMIN_PASSWORD` | — | **Обязательно.** Пароль администратора |
| `ADMIN_TTL_DAYS` | `3650` | Срок действия аккаунта admin (дни) |
| `SESSION_IDLE_SECONDS` | `7200` | Тайм-аут сессии по бездействию (секунды). Рекомендуется `300` (5 мин) |
| `UVICORN_WORKERS` | `6` | Количество воркеров uvicorn |
| `WEB_POOL_SIZE` | `20` | Максимальное число WEB-контейнеров в пуле |
| `WEB_POOL_BUFFER` | `2` | Сколько прогретых WEB-контейнеров держать в запасе |
| `PREWARM_POOL_SIZE` | `2` | Размер пула прогрева для VNC |
| `UNIVERSAL_POOL_SIZE` | `0` | Размер универсального пула |
| `MAX_ACTIVE_SERVICES_PER_USER` | `4` | Максимум одновременных сессий на одного пользователя |
| `ENABLE_STARTUP_MAINTENANCE` | `0` | `1` = при старте запустить/переинициализировать все пул-контейнеры |
| `TRAEFIK_INTERNAL_URL` | `http://traefik` | URL Traefik изнутри Docker-сети (не менять без нужды) |
| `LOG_LEVEL` | `INFO` | Уровень логирования: `DEBUG`, `INFO`, `WARNING`, `ERROR` |
| `LOG_SLOW_REQUEST_MS` | `2000` | Запросы дольше этого (мс) логируются как медленные |
| `GO_USER_LOCK_TIMEOUT_SECONDS` | `8` | Тайм-аут блокировки при запуске сессии пользователем |
| `GO_POOL_LOCK_TIMEOUT_SECONDS` | `20` | Тайм-аут блокировки при захвате слота пула |
| `POOL_DISPATCH_RETRIES` | `6` | Число попыток занять слот пула |
| `POOL_DISPATCH_REQUEST_TIMEOUT_SECONDS` | `2.0` | Тайм-аут одного запроса к пул-контейнеру |
| `POOL_DISPATCH_SLEEP_SECONDS` | `0.3` | Пауза между попытками диспетчеризации |
| `X11VNC_FLAGS` | `-wait 5 -defer 5 -threads` | Дополнительные флаги x11vnc |
| `WEB_RESOLUTION_MIN_WIDTH` | `1024` | Минимальная ширина разрешения для WEB-сессий |
| `WEB_RESOLUTION_MIN_HEIGHT` | `720` | Минимальная высота |
| `WEB_RESOLUTION_MAX_WIDTH` | `3840` | Максимальная ширина |
| `WEB_RESOLUTION_MAX_HEIGHT` | `2160` | Максимальная высота |
---
## 16. Частые проблемы и решения
### Сертификат не получается (Let's Encrypt)
**Симптом:** Браузер показывает ошибку TLS, в логах Traefik: `acme: error`.
**Причины и решения:**
1. DNS не указывает на сервер — проверьте A-запись: `nslookup your-domain.example.com`
2. Порт 80 или 443 закрыт фаерволом — откройте: `sudo ufw allow 80 && sudo ufw allow 443`
3. Файл `acme.json` не имеет прав 600: `chmod 600 traefik/letsencrypt/acme.json`
4. Слишком много запросов к LE — подождите час и попробуйте снова
---
### API не стартует (ошибка подключения к БД)
**Симптом:** В логах `api`: `connection refused` или `could not connect to server`.
```bash
# Проверьте что база запущена
docker compose ps db
docker compose logs db
# Убедитесь что переменные совпадают в .env
grep POSTGRES .env
```
Если база не успела подняться — подождите 5–10 секунд и перезапустите API:
```bash
docker compose restart api
```
---
### RDP-сессия не подключается (чёрный экран)
**Симптом:** noVNC открывается, но экран чёрный или появляется ошибка.
```bash
# Найдите имя контейнера слота
docker ps | grep rdpslot
# Смотрите логи внутри контейнера
docker exec portal-rdpslot-SLUG-N cat /tmp/xfreerdp.log
docker exec portal-rdpslot-SLUG-N cat /tmp/xvfb.log
docker exec portal-rdpslot-SLUG-N cat /tmp/x11vnc.log
```
Типичные ошибки:
| Ошибка | Причина | Решение |
|--------|---------|---------|
| `Server is already active for display :1` | Старый lock-файл Xvfb | Обновите `rdp-proxy/entrypoint.sh` (уже исправлено в v0.6.0) |
| `Authentication failure` | Неверный RDP логин/пароль | Проверьте слот в админке |
| `failed to open display :1` | Xvfb не запустился | Перезапустите контейнер слота |
| `Connection refused` к `:3389` | RDP-сервер недоступен | Проверьте `host:port` сервиса |
Перезапуск конкретного слота:
```bash
docker restart portal-rdpslot-SLUG-N
```
---
### Слот показывается свободным, но пользователь всё ещё в сессии
**Симптом:** В админке слот «Свободен», но пользователь зашёл в /view.
Это возникает если сессия осталась в статусе `ACTIVE` без обновления `last_access_at`.
Принудительно истечь сессию через PostgreSQL:
```bash
docker exec -it stend_mont-db-1 psql -U portal -d portal -c \
"UPDATE sessions SET status='EXPIRED' WHERE status='ACTIVE' AND service_id=<ID>;"
```
---
### Контейнеры пула не создаются при старте
**Симптом:** После запуска нет контейнеров `portal-rdpslot-*` или `portal-webpool-*`.
Убедитесь что стартовали с флагом:
```bash
ENABLE_STARTUP_MAINTENANCE=1 docker compose up -d api maintenance
```
Или добавьте в `.env`:
```dotenv
ENABLE_STARTUP_MAINTENANCE=1
```
и перезапустите:
```bash
docker compose up -d api maintenance
```
---
### Полная переустановка (сброс данных)
> **Внимание**: удалятся все пользователи, сессии, сервисы!
```bash
cd ~/docker/stand
docker compose down -v # -v удаляет volume с данными PostgreSQL
docker compose up -d --build
```
---
## Итоговый чеклист первого развёртывания
- [ ] Сервер с Ubuntu 22.04+ и публичным IP
- [ ] DNS A-запись `ваш-домен → IP сервера`
- [ ] Установлен Docker 24+ и Docker Compose v2
- [ ] Репозиторий склонирован
- [ ] Заполнен `.env` (PUBLIC_HOST, POSTGRES_PASSWORD, SIGNING_KEY, ADMIN_PASSWORD)
- [ ] Создан `traefik/letsencrypt/acme.json` с правами 600
- [ ] Собраны образы рантаймов (`portal-rdp-proxy`, `portal-universal-runtime`, `portal-kiosk`)
- [ ] Выполнен `docker compose up -d --build`
- [ ] Открывается `https://ваш-домен/admin` → вход в систему
- [ ] Созданы пользователи и сервисы
- [ ] Для RDP: добавлены слоты, перезапущен API с `ENABLE_STARTUP_MAINTENANCE=1`