docs: add full deployment guide in Russian

This commit is contained in:
2026-04-28 07:08:05 +00:00
parent d0ff949828
commit b951f6c68e
+601
View File
@@ -0,0 +1,601 @@
# Инструкция по развёртыванию — МОНТ Инфраструктурный Полигон
Версия проекта: **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`