Монолитный Docker Compose-стек, разворачивающий всю инфраструктуру компании ЕвроТранс (логистика / автобусные перевозки / продажа билетов) на одной ВМ. Всё в одной bridge-сети network-evrotrans-stable-default (подсеть 172.19.0.0/16), наружу торчит только nginx (80/443) и openvpn (1194).
Публичный сайт (monofront3-main-site), личный кабинет (monofront3-main-personal) и ERP-фронт sakai3. Старые фронты erp3/evro3/frontv3 собраны, но отключены (replicas: 0).
php8 (8.1.17) — «новый» контур (api3, sales, partnerstickets). php5 (5.6.31, EOL) — «старый» контур (api, erp, erp2). Два параллельных поколения приложения.
Контуры ET (mysql) и PT (mysqlpt) на MariaDB 10.8.3, плюс mongo 4.4.30 для php8. У каждого MySQL-контура своя phpMyAdmin.
Единый HTTP/HTTPS-вход с TLS (Let's Encrypt), реверс-прокси и FastCGI ко всем сайтам. VPN-доступ в инфраструктуру. Админки — только через прокси + basicauth.
Cron-конвейер: бэкапы, очистка, PHP CLI (бизнес-логика api3) и wget-скрейперы внешних билетных/вокзальных систем. autoheal перезапускает unhealthy-контейнеры.
Локальный postfix отключён (replicas: 0); php8 отправляет почту через внешний SMTP-релей 10.150.0.20:25 (сервер evrotrans-mailer).
Плюс инфраструктурные службы хоста: мониторинг (Zabbix agent2), резервное копирование/защита (Acronis Cyber Protect), systemd-resolved.
| Параметр | Значение |
|---|---|
| Платформа | Yandex Cloud + Acronis Cyber Protect (бэкап/защита) |
| ОС | Ubuntu 20.04.6 LTS (Focal) — 🟠 стандартная поддержка истекла (апр 2025), нужен план апгрейда / ESM |
| Ядро | 5.4.0-216-generic |
| CPU | 8 ядер |
| RAM | 7.9 GiB (занято ~5.0, свободно ~1.4, buff/cache ~1.5) |
| Swap | 8.0 GiB, занято ~3.4 GiB 🟠 (признак нехватки RAM) |
| Диск | /dev/vda1 48 GiB, занято 36 GiB (75%), свободно 13 GiB — единый раздел / |
| Load avg | 1.64 / 1.76 / 1.93 (на 8 ядрах — комфортно, ~20%) |
| Uptime | 97 дней |
| Docker | 24.0.2, Compose v2.18.1 |
| Стек на диске | 11 GiB. Топ папок: mysql 3.4G · web 2.6G · api3 1.3G · mongo 946M · pti3 466M · sales3 344M · openvpn 317M |
mysql занимает 2.3 из 4 ГБ лимита и перезапускался 3 раза (остальные контейнеры — 0). На 8 ядрах CPU не является узким местом.Прочие службы на хосте (вне Docker, из ss -tulpn):
4800848080:10050 — мониторинг уже частично развёрнут ✅80, 443, 1194; systemd-resolved на :53Наружу опубликованы только два сервиса:
| Порт хоста | Сервис | Назначение |
|---|---|---|
80, 443 | nginx | Единый HTTP/HTTPS-вход, реверс-прокси и FastCGI ко всем сайтам/сервисам |
1194/tcp+udp | openvpn | VPN-доступ в инфраструктуру |
Внешние IP (из nginx-конфигов): 89.111.137.188, 31.44.10.106.
flowchart TB
NET(["Интернет"]):::ext
VPNC(["VPN-клиент"]):::ext
subgraph BRIDGE["Docker bridge · network-evrotrans-stable-default · 172.19.0.0/16 (18 контейнеров)"]
direction TB
NGINX["nginx :80/443 · 172.19.0.5
реверс-прокси + FastCGI
89.111.137.188 / 31.44.10.106"]:::edge
OVPN["openvpn :1194 · 172.19.0.14"]:::edge
PHP8["php8 · 172.19.0.19"]:::hot
CRON["crontab
network_mode: host"]:::back
end
SMTP(["SMTP-релей 10.150.0.20:25
(evrotrans-mailer)"]):::ext
INTEG(["Внешние интеграции:
JWT · Telegram · SMS
Минтранс FTP (ПДП/ОНСИ/МР/АКТ) · GitHub"]):::ext
EXTSYS(["Внешние билетные / вокзальные системы"]):::ext
BKP(["Бэкап-сервер (ENV_BACKUP_SERVER_*)"]):::ext
NET -->|":80/443"| NGINX
VPNC -->|":1194"| OVPN
PHP8 -->|"mail"| SMTP
PHP8 -->|"интеграции"| INTEG
CRON -->|"PHP CLI + wget"| EXTSYS
CRON -->|"dump / rsync"| BKP
classDef edge fill:#11202e,stroke:#4ea1ff,color:#dbe4ee
classDef back fill:#241a0c,stroke:#ffb454,color:#dbe4ee
classDef hot fill:#2a1416,stroke:#ff5c5c,color:#ffd9d9
classDef ext fill:#12161c,stroke:#5a6675,color:#cfd8e2
DNS-зоны: основная evrotrans.net (+ поддомены) и eprojeckt.ru (app/erp/p/trl/trlavs.eprojeckt.ru — каталоги в ./web, конфиги частью в .bak).
flowchart TB NET(["Интернет"]):::ext VPNC(["VPN-клиент"]):::ext SMTP(["Внешний SMTP-релей
10.150.0.20:25"]):::ext EXTAPI(["Внешние API · вокзалы
Минтранс FTP"]):::ext BKP(["Бэкап-сервер
ENV_BACKUP_SERVER_*"]):::ext subgraph HOST["ВМ vm-evrotrans (Yandex Cloud) — сеть 172.19.0.0/16"] direction TB subgraph EDGE["ГРАНИЦА"] direction TB NGINX["nginx :80/443
172.19.0.5"]:::edge CERT["certbot (TLS)
172.19.0.15"]:::edge BA["basicauth
172.19.0.3"]:::edge OVPN["openvpn :1194
172.19.0.14"]:::edge end subgraph FRONT["ФРОНТЫ · Node :3000"] direction TB SITE["monofront3-main-site
172.19.0.7"]:::front PERS["monofront3-main-personal
172.19.0.8"]:::front SAKAI["sakai3
172.19.0.6"]:::front OLDF["erp3 · evro3 · frontv3
(replicas:0)"]:::off end subgraph BACK["PHP-бэкенды :9000"] direction TB PHP8["php8 (8.1.17)
172.19.0.19 · 231% CPU"]:::hot PHP5["php5 (5.6.31, EOL)
172.19.0.18"]:::back end subgraph DATA["БАЗЫ ДАННЫХ"] direction TB MYSQL[("mysql (ET) · mariadb
172.19.0.13")]:::db MYSQLPT[("mysqlpt (PT) · mariadb
172.19.0.12")]:::db MONGO[("mongo :27017
172.19.0.9")]:::db POSTFIX["postfix (выкл. replicas:0)"]:::off end subgraph ADMIN["АДМИН / МОНИТОРИНГ"] direction TB PORT["portainer
172.19.0.10"]:::svc DOZ["dozzle
172.19.0.4"]:::svc MEXP["mongo-express
172.19.0.11"]:::svc PMA["phpmyadmin
172.19.0.17"]:::svc PMAPT["phpmyadminpt
172.19.0.16"]:::svc end subgraph OPS["ФОНОВО"] direction TB CRON["crontab
host-скрипты + PHP CLI + wget"]:::back AH["autoheal
172.19.0.2"]:::svc end DSOCK(["docker.sock"]):::ext end NET -->|":80/443"| NGINX VPNC -->|":1194"| OVPN CERT --- NGINX BA --- NGINX NGINX -->|"proxy_pass"| SITE NGINX -->|"proxy_pass"| PERS NGINX -->|"proxy_pass"| SAKAI NGINX -->|"FastCGI :9000"| PHP8 NGINX -->|"FastCGI :9000"| PHP5 NGINX -->|"proxy_pass"| PORT NGINX -->|"proxy_pass"| DOZ NGINX -->|"proxy_pass"| MEXP NGINX -->|"proxy_pass"| PMA NGINX -->|"proxy_pass"| PMAPT PHP8 --> MYSQL PHP8 --> MYSQLPT PHP8 --> MONGO PHP5 --> MYSQL PHP5 --> MYSQLPT PMA --> MYSQL PMAPT --> MYSQLPT MEXP --> MONGO PHP8 -->|"mail"| SMTP CRON --> PHP8 CRON -->|"PHP CLI + wget"| EXTAPI CRON -->|"dump / rsync"| BKP AH --> DSOCK classDef edge fill:#11202e,stroke:#4ea1ff,color:#dbe4ee classDef front fill:#0f2417,stroke:#56d364,color:#dbe4ee classDef back fill:#241a0c,stroke:#ffb454,color:#dbe4ee classDef hot fill:#2a1416,stroke:#ff5c5c,color:#ffd9d9 classDef db fill:#1d1430,stroke:#bc8cff,color:#e9def9 classDef svc fill:#10242b,stroke:#3fb6c9,color:#cfeef5 classDef ext fill:#12161c,stroke:#5a6675,color:#cfd8e2 classDef off fill:#171b21,stroke:#3a444f,color:#7d8896
Все сервисы стартуют декларативно с restart: always. Порядок старта: БД (healthy) → PHP (healthy) → nginx.
| Контейнер | IP (172.19.0.x) | CPU | RAM | Образ |
|---|---|---|---|---|
| nginx | .5 | 2.9% | 44 MiB | nginx:1.24.0-alpine |
| php8 | .19 | 231% ⚠️ | 313 MiB | ddan9/php8 (PHP 8.1.17) |
| php5 | .18 | 0% | 71 MiB | ddan9/php5 (PHP 5.6.31) |
| mysql (ET) | .13 | 5.7% | 2.34 GiB / 4 GiB | mariadb:10.8.3 |
| mysqlpt (PT) | .12 | 0% | 17 MiB / 2 GiB | mariadb:10.8.3 |
| mongo | .9 | 0.3% | 458 MiB | mongo:4.4.30 |
| monofront3-main-site | .7 | 1.7% | 395 / 512 MiB | (build, pm2-runtime) |
| monofront3-main-personal | .8 | 1.5% | 242 / 512 MiB | (build, pm2-runtime) |
| sakai3 | .6 | 0% | 79 MiB | (build, node .output) |
| openvpn | .14 | 0.1% | 60 MiB | ddan9/openvpn-asu:2.14.1 |
| portainer | .10 | 0% | 13 MiB | portainer:1.24.1-alpine |
| dozzle | .4 | 5.3% | 55 MiB | amir20/dozzle:v8.14.8 |
| mongoexpress | .11 | 0% | 27 MiB | mongo-express:1.0.2 |
| phpmyadmin | .17 | 0% | 79 MiB | phpmyadmin:5.1 |
| phpmyadminpt | .16 | 0% | 27 MiB | phpmyadmin:5.1 |
| certbot | .15 | 0% | 4 MiB | ddan9/certbot |
| basicauth | .3 | 0% | 1 MiB | ddan9/basicauth |
| autoheal | .2 | 0% | 12 MiB | willfarrell/autoheal:1.2.0 |
cron_php_api3_1min.sh, а не постоянная перегрузка. Стабильность не под угрозой, но скрипт стоит оптимизировать.crontab запущен в network_mode: host, поэтому в списке сети (172.19.0.x) его нет.Активные конфиги в ./nginx/conf.d/*.conf (рядом — десятки .bak-версий, в работе не участвуют). Точная маршрутизация (по fastcgi_pass/proxy_pass):
| Конфиг / домен | Назначение | Бэкенд |
|---|---|---|
evrotrans.net (+ *.evrotrans.net, default_server) | главный сайт | динамический proxy_pass http://$service (по поддомену) |
www.evrotrans.net | главный сайт | (как выше) |
api.evrotrans.net | публичный API | php5:9000 + проксирование на api3.evrotrans.net/sales/ и /sales-api-rb/ |
api3.evrotrans.net (/web) | API v3 | php8:9000 |
sales.evrotrans.net (/web) | продажи | php8:9000 + проксирование на api3/sales/ |
partnerstickets.evrotrans.net (/web) | партнёрские билеты | php8:9000 |
erp.evrotrans.net | ERP | php5:9000 (legacy) |
erp2.evrotrans.net | ERP v2 | php5:9000 (legacy) |
erp3.evrotrans.net | ERP-фронт | динамический proxy_pass http://$service (sakai3:3000 закомментирован) |
pma / pma1.evrotrans.net | админки БД | → phpmyadmin:80 / phpmyadminpt:80 |
mde.evrotrans.net | mongo-express | → mongoexpress:8081 |
doz.evrotrans.net | dozzle | → dozzle:8080 |
ptr.evrotrans.net | portainer | → portainer:9000 |
vpn.evrotrans.net | OpenVPN admin | → https://openvpn:943 |
api3, sales, partnerstickets) работает на php8 (8.1), а «старый» (api, erp, erp2) — на php5 (5.6, EOL). Это два параллельных поколения приложения.$service, вычисляемую из поддомена (~^(?<subdomain>.+)\.evrotrans\.net). Удобно, но без явного списка — опечатка в DNS/поддомене может увести трафик «в никуда».Глобальные настройки nginx: worker_processes auto, worker_connections 1024, gzip on (level 6), keepalive_timeout 65.
limit_req/limit_conn отсутствуют) — нет защиты от всплесков/перебора. В старых .bak-конфигах встречаются прокси на несуществующие сервисы (caddy:3000, evro3:3000) — легаси../web, монтируется в php5/php8/nginx как /var/www)Отдельные document-root на каждый сайт: api.evrotrans.net, api3.evrotrans.net, erp.evrotrans.net, erp2.evrotrans.net, evrotrans.net, p.evrotrans.net, partnerstickets.evrotrans.net, sales.evrotrans.net, test.evrotrans.net, vpn.evrotrans.net, а также зоны *.eprojeckt.ru (app/eprojeckt/erp/p/trl/trlavs). Плюс служебные: html, inc, inc2, inctrl, inctrl2, deps, тестовые testmail*.php, MELAI.csv.
| Сервис | Версия | Назначение |
|---|---|---|
| php8 | PHP 8.1.17 (NTS, OPcache) | Основной бэкенд. Модули: mongodb, imagick, intl, gd, mysqli/pdo_mysql, pgsql, ldap, soap, snmp, mcrypt, bcmath, gmp, zip, xsl… |
| php5 | PHP 5.6.31 EOL | Legacy-бэкенд для старого кода (api, erp, erp2) |
Общие тома кода (монтируются и в nginx, и в оба PHP): ./api3, ./pti3, ./sales3, ./web. Структура backend-приложений (api3, pti3, sales3) единообразна: Dockerfile, dirs/, logs/, publish/, site/.
.env / compose)mongodb://mongo:27017), две MySQL-БД (ET и PT)JWT_ERP_*, JWT_SITE_*)TG_BOT_KEY), SMS (SMS_API_KEY)10.150.0.20:25 + postfix-кредыMINTRANS_FTP_{PDP,ONSI,MR,ACT}_{URL,PORT})| Сервис | Статус | Назначение |
|---|---|---|
| monofront3-main-site | ✅ healthy лимит 512M | Публичный сайт (монорепо apps/main-site), запуск через pm2-runtime |
| monofront3-main-personal | ✅ healthy лимит 512M | Личный кабинет (apps/main-personal), pm2-runtime |
| sakai3 | ✅ healthy | ERP-фронт SakaiERP (node .output/server) |
| erp3 / evro3 / frontv3 | ⛔ replicas: 0 | Старые фронты (собраны, отключены) |
Каждый фронт: Dockerfile + deploy/ + dist/ (готовая сборка монтируется в контейнер).
| Сервис | Образ | Контур | Переменные |
|---|---|---|---|
| mysql | mariadb:10.8.3 | ET (основной) | ENV_MYSQL_ET_* |
| mysqlpt | mariadb:10.8.3 | PT (отдельный) | ENV_MYSQL_ETPT_* |
| mongo | mongo:4.4.30 | NoSQL (для php8) | ENV_MONGO_* |
Каждый контур MySQL имеет свою phpMyAdmin. У обеих БД есть _DATABASE_IGNORE (исключения из бэкапа/репликации). Данные: ./mysql/database, ./mysqlpt/database, ./mongo/database.
| Сервис | Роль |
|---|---|
| certbot | Автообновление TLS (Let's Encrypt), ключи в ./certbot/keys, ACME через /.well-known/acme-challenge/ |
| basicauth | Генерация htpasswd для защиты админок (./basicauth/htaccess) |
| autoheal | Перезапуск контейнеров с label autoheal=true при unhealthy (интервал 60с) |
| crontab | Планировщик (network_mode: host), конфиг ./crontab/config/root — см. §11 |
| postfix | SMTP-почта — выключен (replicas: 0); php8 шлёт через 10.150.0.20:25 |
| openvpn | VPN-шлюз (admin/root/user аккаунты в .env) |
Мониторинг-панели за nginx+basicauth: portainer (Docker), dozzle (логи), mongo-express, две phpMyAdmin.
./crontab/config/root)Запуск через обёртки cron_admin.sh (от админа r3ddan9) и cron_user.sh (от evrotrans).
flowchart TB CRON["crontab (host)
config: ./crontab/config/root"]:::back ADMIN["cron_admin.sh (r3ddan9)
БЭКАПЫ И ОЧИСТКА (host/*.sh)"]:::back USER["cron_user.sh (evrotrans)"]:::back CRON --> ADMIN CRON --> USER subgraph BK["Бэкапы и очистка"] direction TB BCFG["config — 1/10/20 числа · 06:00"]:::back BDB["database — каждый час (58 * * * *)"]:::back BWEB["web — еженедельно (1/7/14/21/28 · 06:00)"]:::back BCLN["cleaner_* + logs_cleaner"]:::back end ADMIN --> BCFG ADMIN --> BDB ADMIN --> BWEB ADMIN --> BCLN BCLN -->|"rsync"| BKPSRV(["Бэкап-сервер
ENV_BACKUP_SERVER_*"]):::ext subgraph STK["СТЕК"] direction TB SCRT["certbot_renew (сб 01:00)"]:::edge SNGX["restart nginx (сб 03:35)"]:::edge SMYS["mysql_courtship (7 число)"]:::db end USER --> SCRT USER --> SNGX USER --> SMYS subgraph PHPCLI["PHP CLI · crontab-scripts-php"] direction TB P1["api3_1min ⚠️ КАЖДУЮ МИНУТУ"]:::back P2["api3_3min · 30min · 1w (пн 04:45)"]:::back end USER --> P1 USER --> P2 P1 -->|"нагрузка"| PHP8["php8
172.19.0.19 · 231% CPU"]:::hot P2 --> PHP8 subgraph WGET["WGET-скрейперы · crontab-scripts-wget/x_*"] direction TB WAPI["api / api3 (sms · telegram · actions)"]:::back WPART["партнёрские билеты (evrotrans · rosbilet)"]:::back WVED["ведомости (avgust · kpasru)"]:::back WVOK["вокзалы: artmark-mosmetro · melitopol
region-bilet · rostov · probilets
(интервалы 1 мин … 3 ч)"]:::back WCLN["x_cron-cleaner.sh — каждые 12 ч"]:::back end USER --> WAPI USER --> WPART USER --> WVED USER --> WVOK USER --> WCLN WAPI -->|"HTTP"| EXTSYS(["Внешние билетные /
вокзальные системы"]):::ext WPART -->|"HTTP"| EXTSYS WVED -->|"HTTP"| EXTSYS WVOK -->|"HTTP"| EXTSYS classDef edge fill:#11202e,stroke:#4ea1ff,color:#dbe4ee classDef back fill:#241a0c,stroke:#ffb454,color:#dbe4ee classDef hot fill:#2a1416,stroke:#ff5c5c,color:#ffd9d9 classDef db fill:#1d1430,stroke:#bc8cff,color:#e9def9 classDef ext fill:#12161c,stroke:#5a6675,color:#cfd8e2
Бэкапы (./host/cron_home_backuper_*.sh) → на внешний сервер (ENV_BACKUP_SERVER_*): config — 1/10/20 числа в 06:00; database — каждый час (58 * * * *); web — раз в неделю (1/7/14/21/28 в 06:00).
Очистка (cron_backup_cleaner_*, cron_home_logs_cleaner): почасовая чистка БД-бэкапов, еженедельная чистка config/web/логов. Стек: cron_mysql_courtship (7 числа), cron_certbot_renew (сб 01:00), рестарт nginx по субботам 03:35.
PHP CLI (crontab-scripts-php/scripts/) — бизнес-логика api3: cron_php_api3_1min.sh — каждую минуту ⚠️ (вероятный источник нагрузки на php8); _3min, _30min, _1w (понедельник 04:45).
WGET-скрейперы (crontab-scripts-wget/scripts/x_*.sh) — опрос внешних билетных/вокзальных систем по HTTP: x_api-evrotrans-*, x_api3-evrotrans-{sms,telegram,actions}-*; билеты партнёров: x_partners-tickets-{evrotrans,rosbilet}-*; ведомости: x_vedomosti-{avgust,kpasru}-*; вокзалы (vokzali): artmark-mosmetro, melitopol, region-bilet, rostov, probilets — с интервалами от 1 мин до 3 ч; x_cron-cleaner.sh — каждые 12 ч.
./mongo/database, ./mysql/database, ./mysqlpt/database./api3, ./pti3, ./sales3, веб-корни ./web./<front>/dist./certbot/{config,keys,logs}./nginx/conf.d, логи ./nginx/logs./php{5,8}/config (fpm+php), ./php{5,8}/ssmtp, логи ./php{5,8}/logs./backup/{config,database,web} (локальная стадия перед отправкой)./host/*.sh (backup/restart/relink/certbot/basicauth), плюс .bak-версии./crontab-scripts-php, ./crontab-scripts-wgetno-new-privileges, apparmor=docker-default, privileged: false..env (включая GITHUB_TOKEN, JWT, FTP Минтранс, SMS, Telegram) — не коммитить, доступ к серверу только по VPN/SSH..bak/.conf.bak.N-файлов и резервных compose (docker-compose.yml.{bak,Vbackup,___}) — стоит навести порядок, чтобы не путать активную конфигурацию.Из комментариев в compose видно недавнюю модернизацию:
1.12.1 → 1.24.0-alpine, healthcheck → nginx -t.cap_add: SYS_PTRACE, healthcheck упрощён до php -v, устаревшие links заменены на depends_on: condition: service_healthy.cron_php_api3_1min.sh — источник всплесков CPU; профилировать (включён SYS_PTRACE), при возможности разнести по времени.api/erp/erp2 — план миграции на php8.limit_req для публичных API..bak/легаси-конфигов nginx и резервных compose-файлов.cd /home/evrotrans/stable docker compose ps # статус сервисов docker stats --no-stream # нагрузка docker compose up -d # поднять/обновить стек docker compose logs -f php8 nginx # логи docker compose build sakai3 && docker compose up -d sakai3 # пересборка фронта docker compose restart php8 # перезапуск сервиса docker exec stable-php8-1 php -v # версия PHP # хелперы хоста: bash ./host/bash_restart_phps.sh # рестарт PHP-контейнеров bash ./host/cron_certbot_renew.sh # обновить сертификаты