25 KiB
Инструкция по развёртыванию — МОНТ Инфраструктурный Полигон
Версия проекта: 0.6.0
Содержание
- Что это такое
- Архитектура
- Требования к серверу
- Установка зависимостей
- Клонирование репозитория
- Настройка переменных окружения
- Настройка Traefik
- Сборка Docker-образов рантаймов
- Первый запуск
- Инициализация базы данных
- Проверка работоспособности
- Настройка через админку
- Настройка RDP-слотов
- Обновление проекта
- Описание всех переменных окружения
- Частые проблемы и решения
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).
4. Установка зависимостей
# Обновляем систему
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
# Рабочий каталог — можно любой, например /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. Настройка переменных окружения
Скопируйте шаблон и отредактируйте:
cp .env.example .env
nano .env
Минимальный набор значений, которые нужно поменять:
# Домен, на котором будет работать портал
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.
7. Настройка Traefik
7.1 Файл traefik/traefik.yml
Откройте и проверьте email в секции certificatesResolvers:
nano traefik/traefik.yml
certificatesResolvers:
letsencrypt:
acme:
email: admin@example.com # ← ваш email
storage: /letsencrypt/acme.json
tlsChallenge: {}
Порты по умолчанию: HTTP :8288, HTTPS :2288 (нестандартные, прописаны в docker-compose.yml).
Если нужны стандартные 80/443:
nano docker-compose.yml
# Найдите секцию traefik -> ports и замените:
# - "0.0.0.0:8288:80" → - "80:80"
# - "0.0.0.0:2288:443" → - "443:443"
7.2 Создание файла хранилища сертификатов
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 замените:
- traefik.http.routers.portal.entrypoints=websecure
- traefik.http.routers.portal.tls=true
- traefik.http.routers.portal.tls.certresolver=letsencrypt
на:
- traefik.http.routers.portal.entrypoints=web
8. Сборка Docker-образов рантаймов
Рантаймы — это образы для WEB/VNC/RDP-сессий. Они не входят в основной docker compose up, их нужно собрать отдельно.
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 и другие пакеты).
Проверьте что образы появились:
docker images | grep portal
# Должно быть:
# portal-universal-runtime latest ...
# portal-rdp-proxy latest ...
# portal-kiosk latest ...
9. Первый запуск
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 создаёт все таблицы).
Если нужно создать таблицы вручную (на случай сбоя):
# Подключитесь к контейнеру PostgreSQL
docker exec -it stend_mont-db-1 psql -U portal -d portal
# Выполните скрипт схемы
\i /dev/stdin
# вставьте содержимое scripts/schema.sql и нажмите Ctrl+D
Или через файл:
docker exec -i stend_mont-db-1 psql -U portal -d portal < scripts/schema.sql
Таблица
rdp_slotsсоздаётся автоматически ORM, её нет вschema.sql— это нормально.
11. Проверка работоспособности
# Статус всех контейнеров
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. Настройка через админку
- Откройте
https://your-domain.example.com/admin - Войдите с данными
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: Запустите контейнеры слотов
# Перезапустите API с флагом инициализации
cd ~/docker/stand
ENABLE_STARTUP_MAINTENANCE=1 docker compose up -d api maintenance
После запуска появятся контейнеры вида portal-rdpslot-SLUG-N.
Проверка:
docker ps | grep rdpslot
Назначение прав на RDP-сервис
В разделе ACL назначьте нужным пользователям доступ к RDP-сервису (обычный чекбокс). Любой пользователь с доступом может занять любой свободный слот.
14. Обновление проекта
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.
Причины и решения:
- DNS не указывает на сервер — проверьте A-запись:
nslookup your-domain.example.com - Порт 80 или 443 закрыт фаерволом — откройте:
sudo ufw allow 80 && sudo ufw allow 443 - Файл
acme.jsonне имеет прав 600:chmod 600 traefik/letsencrypt/acme.json - Слишком много запросов к LE — подождите час и попробуйте снова
API не стартует (ошибка подключения к БД)
Симптом: В логах api: connection refused или could not connect to server.
# Проверьте что база запущена
docker compose ps db
docker compose logs db
# Убедитесь что переменные совпадают в .env
grep POSTGRES .env
Если база не успела подняться — подождите 5–10 секунд и перезапустите API:
docker compose restart api
RDP-сессия не подключается (чёрный экран)
Симптом: noVNC открывается, но экран чёрный или появляется ошибка.
# Найдите имя контейнера слота
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 сервиса |
Перезапуск конкретного слота:
docker restart portal-rdpslot-SLUG-N
Слот показывается свободным, но пользователь всё ещё в сессии
Симптом: В админке слот «Свободен», но пользователь зашёл в /view.
Это возникает если сессия осталась в статусе ACTIVE без обновления last_access_at.
Принудительно истечь сессию через PostgreSQL:
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-*.
Убедитесь что стартовали с флагом:
ENABLE_STARTUP_MAINTENANCE=1 docker compose up -d api maintenance
Или добавьте в .env:
ENABLE_STARTUP_MAINTENANCE=1
и перезапустите:
docker compose up -d api maintenance
Полная переустановка (сброс данных)
Внимание: удалятся все пользователи, сессии, сервисы!
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