Files
Stend_mont/DEPLOY.md

25 KiB
Raw Permalink Blame History

Инструкция по развёртыванию — МОНТ Инфраструктурный Полигон

Версия проекта: 0.6.0


Содержание

  1. Что это такое
  2. Архитектура
  3. Требования к серверу
  4. Установка зависимостей
  5. Клонирование репозитория
  6. Настройка переменных окружения
  7. Настройка Traefik
  8. Сборка Docker-образов рантаймов
  9. Первый запуск
  10. Инициализация базы данных
  11. Проверка работоспособности
  12. Настройка через админку
  13. Настройка RDP-слотов
  14. Обновление проекта
  15. Описание всех переменных окружения
  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).


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. Настройка через админку

  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: Запустите контейнеры слотов

# Перезапустите 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.

Причины и решения:

  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.

# Проверьте что база запущена
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