Сервер приложений
В работе: на данный момент реализован основной функционал, в процессе последующей разработки будут добавляться новые возможности и меняться уже существующие.
Техническая информация
Из‑за возросшей нагрузки и прочих факторов мы были вынуждены перейти с shared‑хостинга на VDS‑хостинг. Это означает, что стабильность приложений теперь целиком лежит на нас.
Для удобства поиска необходимой информации:
В качестве архитектуры сервера был выбран следующий подход:
Веб‑сервер: Traefik
Traefik — это reverse proxy и load balancer, который автоматически обнаруживает сервисы в Docker и настраивает маршрутизацию по доменам без ручной конфигурации.
У Traefik настроена автоматическая выдача SSL‑сертификатов Let's Encrypt; он прослушивает все Docker‑контейнеры и занимается роутингом по поддоменам. Как это работает:
Traefik подхватывает этот контейнер и указанный URL, после чего поднимает поддомен.
Когда приходит запрос на поддомен, Traefik принимает его и проксирует в нужный Docker‑контейнер.
Traefik автоматически подхватывает контейнеры и их метки, а также выпускает SSL через Let's Encrypt без ручной настройки.
Поднимается Docker‑контейнер, в файл Compose для него указываются метки:
labels: traefik.enable=true traefik.http.routers.${{ env.APP_NAME }}-http.rule=Host(\`${{ env.APP_NAME }}.${{ env.DOMAIN }}\`)" traefik.http.routers.${{ env.APP_NAME }}-http.entrypoints=web" traefik.http.routers.${{ env.APP_NAME }}-http.middlewares=${{ env.APP_NAME }}-redirect" traefik.http.middlewares.${{ env.APP_NAME }}-redirect.redirectscheme.scheme=https" traefik.http.routers.${{ env.APP_NAME }}-https.rule=Host(\`${{ env.APP_NAME }}.${{ env.DOMAIN }}\`)" traefik.http.routers.${{ env.APP_NAME }}-https.entrypoints=websecure" traefik.http.routers.${{ env.APP_NAME }}-https.tls=true" traefik.http.routers.${{ env.APP_NAME }}-https.tls.certresolver=letsencrypt" traefik.http.services.${{ env.APP_NAME }}.loadbalancer.server.port=80" traefik.docker.network=traefik" networks: - traefik # внешняя сеть - app_net # внутренняя сеть сервисов контейнера
Traefik подхватывает этот контейнер и указанный URL, после чего поднимает поддомен.
Когда приходит запрос на поддомен, Traefik принимает его и проксирует в нужный Docker‑контейнер.
CP (панель управления сервером): aaPanel
aaPanel — это бесплатная веб‑панель управления сервером, упрощающая установку LAMP/LNMP, администрирование сайтов/БД/FTP, резервное копирование и базовую безопасность.
Основные вкладки CP:
Главная — основная статистика по серверу.
Docker — информация по всем контейнерам и управление ими.
Мониторинг — сведения о нагрузке.
Контейнеры: Docker
Docker‑контейнер — это изолированный процесс с собственным файловым окружением, собранным из образа, который включает приложение и его зависимости. Контейнеры запускаются быстро, воспроизводимы и переносимы между серверами, при этом потребляют меньше ресурсов, чем виртуальные машины.
Каждое приложение работает в Docker‑контейнере. Файлы docker-compose.yml и Dockerfile генерируются автоматически при каждом пуше на Gitlab. Ниже — основные команды для работы с контейнерами Docker:
Основные команды Docker
Список контейнеров (запущенные):
docker ps
Список всех контейнеров (включая остановленные):
docker ps -a
Остановка / запуск / перезапуск контейнера:
docker stop myapp docker start myapp docker restart myapp
Удаление контейнера:
docker rm myapp docker rm -f myapp
Просмотр логов:
docker logs myapp docker logs -f myapp
Открыть shell внутри контейнера:
docker exec -it myapp bash
Выполнить команду внутри контейнера:
docker exec myapp php -v
Информация о контейнере / порты / процессы:
docker inspect myapp docker port myapp docker top myapp
Копирование файлов между хостом и контейнером:
docker cp myapp:/path/in/container ./local_dir docker cp ./local_file myapp:/path/in/container
Образы и сборка
Список образов / загрузка / удаление:
docker images docker pull nginx:latest docker rmi nginx:latest
Сборка образа из Dockerfile в текущей папке:
docker build -t myapp:latest .
Ресурсы и очистка
Мониторинг ресурсов контейнеров:
docker stats
Очистка неиспользуемых ресурсов:
docker system prune -f
Docker Compose
Запуск/остановка сервисов:
docker compose up -d docker compose down
Логи сервиса / команда внутри сервиса:
docker compose logs -f app docker compose exec app bash
Пересборка и перезапуск:
docker compose up -d --build
CI/CD и работа с автодеплоем приложений
CI/CD — это практика автоматизации сборки и тестирования кода (CI) и его доставки/развёртывания в среду (CD) при каждом изменении, чтобы быстрее и надёжнее выпускать обновления.
Для автоматизации деплоя в организации создан репозиторий под названием .github; в нём находятся два скрипта:

В папке .github находится workflow для CI/CD: файл deploy.yml отвечает за автоматическое развёртывание и настройку контейнеров и репозитория.
В папке local_deploy находятся два файла для поднятия архитектуры приложения, подобной серверной, у себя на локальной машине для разработки.
local_deploy
В разработке; некоторый функционал может работать некорректно.
Файлы, находящиеся в папке local_deploy в репозитории .github, предназначены для разработки приложений на локальной машине.
В сущности, данный скрипт открывает SSH‑туннель до контейнера сервера, который поднимает этот туннель на поддомене домена karos58.ru. Как это работает:
Скрипт формирует файлы docker-compose и Dockerfile для приложения.
Скрипт поднимает контейнеры на локальной машине.
Скрипт генерирует SSH‑туннель и ключ; ключ отправляется на микросервис контейнера tunnel, который регистрирует ключи в файле .sshd.
После этого устанавливается связь с контейнером tunnel‑endpoint, который, в свою очередь, устанавливает связь с nginx и сопоставляет порты и поддомены.
Приложение поднимается на поддомене karos58.ru с префиксом app‑.
Практическая информация
Принцип создания и поднятия Bitrix‑приложения на сервере
Разберём поэтапно, как загрузить приложение, предназначенное для публикации в Маркет или других целей.
Первая загрузка
Для корректной необходимо перейти на gitlab компании и создать репозиторий в группе apps.


После чего дождаться автоматического коммита от DevOps-api и начать работу.
Последующие коммиты
При внесение изменений в репозиторий необходимо лишь сделать пуш на ветку main, после чего, в течение 5 минут(проверить можно по статусу выполнения пайплайна) изменения появятся на сервере.
Код развертывается:

Код развернут:

Работа с базой данных
Подключение к БД осуществляется через хост db, обязательно создавайте пользователя и базу данных, через pma, перед началом работы.
Работа с cron
Для управления cron-задачами на контейнере, необходимо перейти по пути https://<название-приложения>.end-point.ru/admin/cron . После ввода авторизационных данных, откроется страница такого вида:

В данном интерфейсе, можно: добавить, изменить и включить/отключить cron-задачи.
Стоит помнить, что путь до файлов приложения на сервере, и в контенере - различаются. В случае контейнера, путь до корня папки приложения будет выглядеть так: /var/www/html/ . Именно такой путь необходимо указывать, если вы хотите исполнять какой-либо файл внутри директории приложения.
{% hint style="info" %} Важно!
По умолчанию, в любом приложение будет присутствовать стандартная cron-задача, для удаления CRest-логов. Не стоит пытаться удалить эту задачу.
{% endhint %}
Работа с Redis
Для работы с redis необходимо изменить значения в файле deploy.options.yml(если файл отсутствует - попросите администратора сервера обновить пайплайны в проекте, либо сделайте это сами: DevOps-panel Документация). В файле необходимо найти данный блок:
redis: enabled: false
И заменить параметр enabled c false на true. После этого, redis будет доступен в контейнере приложения, на:
Хост: localhost;
Порт: 6379.
Для работы с redis, рекомендуется использовать одну из официальных библиотек.
Локальный деплой (для разработки)
Для разработки приложений на локальной машине можно использовать файлы, находящиеся в папке local_deploy в репозитории gitlab/local_deploy.
Windows:
Для начало работы, необходимо скачать с Gitlab компании репозиторий gitlab/local-deploy.
Как пользоваться:
Устанавливаем Docker Desktop(полностью, включая wsl).
Добавляем актуальные скрипты (dev.ps1 и dev.cmd) из папки
**local_deploy/windows**в корень папки, которую хотим поднять локально.Открываем терминал и выполняем команду:
.\dev.cmd up
Ожидаем выполнения скрипта; приложение будет доступно по адресу, указанному в выводе скрипта в терминале.
Локальные URL: HTTPS: https://172.21.16.1:8081 HTTP: http://172.21.16.1:8080 Регистрация SSH-ключа на сервере... Автоматическая регистрация ключа... Ключ зарегистрирован SSH туннель активен (PID: 19956, порт 10768): URL приложения (туннель): https://dev-app-kurator.karos58.ru
Все команды для работы со скриптом можно узнать, введя в терминале:
.\dev.cmd help
Доступные команды
Поднять приложение:
.\dev.cmd up
Остановить приложение:
.\dev.cmd down
Посмотреть логи контейнеров:
.\dev.cmd logs
Зайти в контейнер (доступ к терминалу):
.\dev.cmd sh
Узнать URL приложения:
.\dev.cmd url
Узнать пароли:
.\dev.cmd passwords
Доступные опции запуска деплоя
Опции указываются после команды up:
.\dev.cmd up -<опция>
Список опций:
-db # Поднятие приложения вместе с базой данных -swagger # Поднятие приложения вместе с Swagger (в разработке)
Linux:
Как это работает:
Устанавливаем docker(гайд)
Добавляем актуальные скрипты (dev.sh) из папки
**local_deploy/Linux**в корень папки, которую хотим поднять локально.Открываем терминал и выполняем команду:
./dev.sh up
Ожидаем выполнения скрипта; приложение будет доступно по адресу, указанному в выводе скрипта в терминале.
Локальные URL: HTTPS: https://172.21.16.1:8081 HTTP: http://172.21.16.1:8080 Регистрация SSH-ключа на сервере... Автоматическая регистрация ключа... Ключ зарегистрирован SSH туннель активен (PID: 19956, порт 10768): URL приложения (туннель): https://dev-app-kurator.karos58.ru
Все команды для работы со скриптом можно узнать, введя в терминале:
./dev.sh help
Доступные команды
Поднять приложение:
./dev.sh up
Остановить приложение:
./dev.sh down
Посмотреть логи контейнеров:
./dev.sh logs
Зайти в контейнер (доступ к терминалу):
./dev.sh sh
Узнать URL приложения:
./dev.sh url
Узнать пароли:
./dev.sh passwords
Доступные опции запуска деплоя
Опции указываются после команды up:
./dev.sh up -<опция>
Список опций:
-db # Поднятие приложения вместе с базой данных -swagger # Поднятие приложения вместе с Swagger (в разработке)
Важные моменты
Все временные файлы нужно добавлять в .gitignore, чтобы они не попадали в репозиторий.
Все файлы настроек и конфигураций также добавлять в .gitignore.
Нельзя менять исходный код приложения напрямую на сервере — все изменения вносятся через коммиты на Gitlab.
Все подключения к БД должны происходить через хост db, а не localhost.
Url путь до файлов приложения выглядить вот так https://<название_приложения>.end-point.ru/index.php.
Все логи в реальном времени выбрасываются из контейнера в корневую директорию приложения.
Важные моменты при переносе приложений со старого сервера
При переносе приложений стоит помнить про основные вещи, описанные ранее, разберем основные еще подробнее:
Теперь каждое приложение имеет иной путь, а именно <название-приложения>.end-point.ru/index.php, если ваша страничка или хэндлер находится в какой-либо папке то путь будет выглятдеть так <название-приложения>.end-point.ru/...(путь до файла)/handler.php (пример: calls-manager.end-point.ru/templates/index.php).
Все подключения к БД теперь происходят через host db, а не localhost
Про docker-compose.yml и Dockerfile
При первом пуше в репозиторий в директории автоматически создаются docker-compose.yml и Dockerfile. Их настройки можно посмотреть в файле deploy.yml в репозитории .github.
Важно: эти файлы генерируются автоматически — не редактируйте их вручную, без крайней необходимости. Разберём основные параметры этих файлов:
Сервис приложения
app: build: . container_name: testik command: ["bash","-lc","umask 000; /usr/sbin/cron || /usr/sbin/cron -f &>/dev/null & disown; exec /usr/sbin/apache2ctl -D FOREGROUND"] restart: unless-stopped environment: - APP_ENV=production - APP_NAME=testik - APP_HOST=testik.end-point.ru - DOMAIN=end-point.ru - DB_HOST=db - DB_PORT=**** - DB_NAME=************** - DB_USER=************** - DB_PASS=****************** depends_on: - db networks: - traefik - app_net expose: - "80" volumes: - ./:/var/www/html - cron_spool:/var/spool/cron/crontabs - cron_d:/etc/cron.d labels: - traefik.enable=true - traefik.http.routers.testik-http.rule=Host(`testik.end-point.ru`) - traefik.http.routers.testik-http.entrypoints=web - traefik.http.routers.testik-http.middlewares=testik-redirect - traefik.http.middlewares.testik-redirect.redirectscheme.scheme=https - traefik.http.routers.testik-https.rule=Host(`testik.end-point.ru`) - traefik.http.routers.testik-https.entrypoints=websecure - traefik.http.routers.testik-https.tls=true - traefik.http.routers.testik-https.tls.certresolver=letsencrypt - traefik.http.services.testik.loadbalancer.server.port=80 - traefik.docker.network=traefik healthcheck: test: ["CMD-SHELL","curl -s -o /dev/null -w '%{http_code}' http://localhost/index.php | grep -E -q '^(2|3|4)[0-9]{2}'"] interval: 20s timeout: 5s retries: 10 start_period: 15s **У этого сервиса есть важные теги и переменные:** ```yaml restart: unless-stopped # контейнер перезапускается, если упал.
environment # ключевые переменные окружения приложения: APP_ENV, APP_NAME, APP_HOST, DOMAIN, DB_HOST, DB_PORT и др.
networks # сети, к которым подключён сервис (traefik, app_net).
expose # порт, на котором отвечает приложение внутри сети (80).
volumes # монтирование кода и cron‑томов (сохраняются между рестартами).
labels # метки для Traefik (роутинг HTTP/HTTPS, certresolver, порт сервиса и т. п.).
Важные теги и переменные:
restart: unless-stopped # автоматический перезапуск при падении.
environment # параметры доступа к БД.
volumes # том с данными БД (сохраняется между рестартами).
networks # внутренняя сеть app_net.
Советы и правила по разработке приложений
Обязательно:
Добавлять конфиги создаваемые CRest в .gitignore
Добавлять все логи в .gitignore
Добавлять все config, settings, .env в .gitignore и создавать их в ручную на сервере
Рекомендуется:
Хранить все уязвимые данные в файле .env
Составлять url пути до хэндлеров, с помощью глобальных переменных и динамических путей.
Подключать все классы через composer
Служебная информация
В это части будет изложена вся информация о архитектуре сервера и работе с ней.
Структура сервера
Основные сервисы и их compose-файлы находятся в директории /opt.
[image:https://868780284-files.gitbook.io/~/files/v0/b/gitbook-x-prod.appspot.com/o/spaces%2FjFHukYpBaAeXQWXY9mJj%2Fuploads%2F1dFLuIrkgw2OqquZaS8e%2Fimage.png?alt=media&token=f54491f9-248e-467c-8eaf-edeaf89954d4:::0,0,100,100:::center]
Для каждого нового сервиса необходимо создать новую директорию в **/opt** с понятным и читаемым названием. Допускается, в случае необходимости, пробрасывать volume контейнера или какие-либо иные данные из директории **/opt**, в другие каталоги Linux.
Существующие сервисы и работа с ними
Информация в этом блоке может меняться, в ходе разработки.
apps
Директория, в которой находятся все приложения компании, именно от сюда они монтируются на docker-контейнеры.
db
Центральный и главный сервер бд, вместе с ним, также поднимается и pma:
version: "3.8" services: db: image: mysql:8.0 container_name: db restart: unless-stopped environment: - MYSQL_ROOT_PASSWORD=************ - MYSQL_DATABASE=******* - MYSQL_USER=******** - MYSQL_PASSWORD=*********** command: ["—character-set-server=utf8mb4","—collation-server=utf8mb4_unicode_ci","—skip-name-resolve","—default-authentication-plugin=mysql_native_password"] healthcheck: test: ["CMD-SHELL","mysqladmin ping -h 127.0.0.1 -uroot -p$MYSQL_ROOT_PASSWORD —silent || exit 1"] interval: 5s timeout: 5s retries: 120 start_period: 30s volumes: - db_data:/var/lib/mysql networks: - db phpmyadmin: image: phpmyadmin:5-apache container_name: pma restart: unless-stopped depends_on: - db environment: - PMA_HOST=db - PMA_PORT=3306 - PMA_ARBITRARY=0 - UPLOAD_LIMIT=256M - APACHE_PORT=80 ports: - 29000:80 networks: - db networks: db: name: db volumes: db_data:
services.db
Контейнер MySQL 8.0. Указаны пароли и название базы. Настроены параметры кодировки и авторизации. Хелсчек проверяет, что MySQL запустился. Данные базы хранятся в volume db_data.
services.phpmyadmin
Веб-панель для управления MySQL. Подключается к сервису db. Установлено ограничение на размер загружаемых файлов.
networks.db
Отдельная сеть, в которой работают MySQL и phpMyAdmin.
volumes.db_data
Папка для постоянного хранения данных MySQL.
github-runner
Директория с рабочими файлами self-hosted github-runner. Необходимая информация о нем находится в официальной документации. Раннер - один из немногих компонентов установленный на host-машину, установка runner на докер сопряжена с некоторыми трудностями, потому было принято такое решение.
Основные команды раннера:
Запуск раннера
cd /opt/github-runner ./run.sh
Запуск как сервис
./svc.sh install ./svc.sh start ./svc.sh status
Остановка сервиса
./svc.sh stop
Удаление сервиса
./svc.sh uninstall
Удаление регистрации раннера
cd /opt/github-runner ./config.sh remove
Очистка workspace (если что-то зависло)
rm -rf /opt/github-runner/_work/* rm -rf /opt/github-runner/_diag/*
sites
Контейнер для сайтов сделанных на wp и прочих cms. На данный момент на нем находится два сайта.
version: "3.9" services: wp-sites: build: context: . dockerfile: Dockerfile container_name: wp-sites restart: always environment: - TZ=Europe/Moscow - MYSQL_ROOT_PASSWORD=**** - FORCE_SSL_ADMIN=true volumes: - ./sites:/var/www/html - db_data:/var/lib/mysql labels: - "traefik.enable=true" - "traefik.docker.network=traefik" # HTTP роутер для geologics.ru - "traefik.http.routers.geologics-http.rule=(Host(`geologics.ru`) || Host(`www.geologics.ru`))" - "traefik.http.routers.geologics-http.entrypoints=web" - "traefik.http.routers.geologics-http.middlewares=geologics-redirect" - "traefik.http.middlewares.geologics-redirect.redirectscheme.scheme=https" - "traefik.http.middlewares.geologics-redirect.redirectscheme.permanent=true" # HTTPS роутер для geologics.ru - "traefik.http.routers.geologics-https.rule=(Host(`geologics.ru`) || Host(`www.geologics.ru`))" - "traefik.http.routers.geologics-https.entrypoints=websecure" - "traefik.http.routers.geologics-https.tls=true" - "traefik.http.routers.geologics-https.tls.certresolver=letsencrypt" - "traefik.http.routers.geologics-https.service=wp-sites" # HTTP роутер для end-point.ru - "traefik.http.routers.end-point-http.rule=(Host(`end-point.ru`) || Host(`www.end-point.ru`))" - "traefik.http.routers.end-point-http.entrypoints=web" - "traefik.http.routers.end-point-http.middlewares=end-point-redirect" - "traefik.http.middlewares.end-point-redirect.redirectscheme.scheme=https" - "traefik.http.middlewares.end-point-redirect.redirectscheme.permanent=true" # HTTPS роутер для end-point.ru - "traefik.http.routers.end-point-https.rule=(Host(`end-point.ru`) || Host(`www.end-point.ru`))" - "traefik.http.routers.end-point-https.entrypoints=websecure" - "traefik.http.routers.end-point-https.tls=true" - "traefik.http.routers.end-point-https.tls.certresolver=letsencrypt" - "traefik.http.routers.end-point-https.service=wp-sites" - "traefik.http.services.wp-sites.loadbalancer.server.port=80" networks: - traefik volumes: db_data: networks: traefik: external: true
services.wp-sites
Контейнер с WordPress, собирается из локального Dockerfile. Установлены переменные окружения: часовой пояс, root-пароль MySQL и принудительное использование SSL для админки.
Монтируются локальные папки с сайтами (./sites) и volume для данных MySQL (db_data).
Через labels настроена маршрутизация и поддержка HTTPS с TLS (Let's Encrypt).
volumes.db_data
Хранение данных MySQL, чтобы база сохранялась при перезапуске контейнера.
networks.traefik
Используется внешняя сеть Traefik для маршрутизации и балансировки нагрузки.
Для установки новых сайтов необходимо положить исходники сайта в директорию sites, перетянуть дамп сайта в бд контейнера, добавить labels по примеру, остальных сайтов и поднять контейнер.
traefik
Основной реверс-прокси сервера, на него приходят все запросы, которые он потом распределяет по контейнерам. Traefik был выбран в пользу классического nginx, за счет его превосходной работы с контейнерной архитектурой.
Compose:
services: traefik: image: traefik:v2.10 container_name: traefik restart: unless-stopped command: - —configFile=/etc/traefik/traefik.yml ports: - "80:80" - "443:443" - "8080:8080" # dashboard/API volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - /opt/traefik/traefik.yml:/etc/traefik/traefik.yml:ro - /opt/traefik/dynamic:/dynamic:ro - /opt/traefik/acme.json:/acme.json - /opt/traefik/logs:/var/log networks: - traefik networks: traefik: external: true
Config(traefik.yml):
global: checkNewVersion: false sendAnonymousUsage: false api: dashboard: true insecure: true entryPoints: web: address: ":80" http: redirections: entrypoint: to: websecure scheme: https websecure: address: ":443" providers: docker: endpoint: "unix:///var/run/docker.sock" exposedByDefault: false network: traefik watch: true file: directory: /dynamic watch: true certificatesResolvers: letsencrypt: acme: email: support@gkyw.ru storage: /acme.json keyType: EC256 httpChallenge: entryPoint: web log: level: DEBUG filePath: "/var/log/traefik.log" accessLog: filePath: "/var/log/access.log" metrics: prometheus: addEntryPointsLabels: true addServicesLabels: true
services.traefik
Контейнер Traefik v2.10 для маршрутизации и управления HTTPS. Настроен с указанием конфигурационного файла (traefik.yml), проброшенными портами 80 (HTTP), 443 (HTTPS) и 8080 (dashboard/API).
Монтируются: Docker сокет для автоматического обнаружения контейнеров, конфигурационные файлы, динамические правила, файл хранения сертификатов и лог-файлы. Подключён к внешней сети traefik.
traefik.yml (конфигурация)
global — отключена проверка новых версий и сбор анонимной статистики.
api — включён dashboard и API (insecure).
entryPoints — HTTP перенаправляется на HTTPS, настроен HTTPS-порт.
providers — Docker (отслеживание контейнеров на сети
traefik) и файловый (динамические правила из папки/dynamic).certificatesResolvers — автоматическое получение TLS-сертификатов через Let's Encrypt с HTTP-challenge.
log и accessLog — хранение логов в указанных файлах, уровень DEBUG.
metrics — Prometheus-метрики с привязкой к точкам входа и сервисам.
networks.traefik
Используется внешняя сеть Traefik для маршрутизации контейнеров.
В сущности, это стандартная настройка traefik, работа с traefik очень проста и позволяет снять с себя, очень много головной боли. Всю необходимую информацию о traefik, можно найти в официальной документации.
Работа с основными инструментами сервера и их настройка
Многие сервисы, описанные выше, также имеют интерфейс, в котором требуется настройка, разберем каждый:
Backrest:
Удобный и простой инструмент для бэкапов, на данный момент, бэкапы хранятся на самом сервере и дублируются на офисный сервер.
После захода в админку backerst, можно увидеть такую страницу

В backrest есть две сущности:
Репозитории - место хранения резервных копий. может быть локальным, на сетевом диске или в облачном хранилище. содержит все бэкапы, логи и метаданные для восстановления.
План - набор настроек для создания резервной копии конкретных данных. определяет, что и куда бэкапить, частоту, а также типы резервных копий (полные, инкрементальные, дифференциальные).
На сервере настроены бэкапы для всех основных данных. Подробнее о backrest можно узнать в официальной документации.
aaPanel:
Основная CP сервера, основная задача это отслеживания нагрузки и управление контейнерами docker. После перехода в админку, можно увидеть такую страницу:

Тут есть все необходимые метрики для просмотра нагрузки и прочего, также есть вкладка для управления контейнерами сервера под названием Docker.

По большому счету, основная цель CP, вести статистику нагрузки на сервер, пользоваться через нее админскими функциями не очень удобно и практично.
Docker и CI/CD
Основной функционал сервера - это хостинг приложений для маркетплейса. Все приложения лежат на гитхабе компании. Для всех репозиториев приложений создан workflow файл с пайплайном для автоматической доставки исходного кода на сервер, с последующим деплоем. На данный момент, сама доставка реализована через работу с work_tree пришедшего коммита, данный функционал был реализован из-за невозможности, в текущее время, настроить secrets на репозиториях github компании. Для работы с пайплайном используется поднятый на сервере self-hosted github-runner, методы работы с которым, были описаны выше. После доставки исходного кода на сервер, создаются/обновляются или остаются прежним docker-compose и docker файлы. Примерное наполнение этих файлов:
docker-compose:
services: app: build: . container_name: userfieldshistory_widget command: ["/usr/local/bin/bootstrap-docroot.sh"] restart: unless-stopped cap_add: - SYS_PTRACE - SYS_ADMIN environment: - TZ=Europe/Moscow - APP_ENV=production - APP_NAME=userfieldshistory-widget - APP_HOST=userfieldshistory-widget.end-point.ru - DOMAIN=end-point.ru - DB_HOST=db - DB_PORT=3306 networks: - traefik - db expose: - "80" volumes: - ./:/var/www/html - cron_spool:/var/spool/cron/crontabs - cron_d:/etc/cron.d labels: - traefik.enable=true - traefik.http.routers.userfieldshistory-widget-http.rule=Host(`userfieldshistory-widget.end-point.ru`) - traefik.http.routers.userfieldshistory-widget-http.entrypoints=web - traefik.http.routers.userfieldshistory-widget-http.middlewares=userfieldshistory-widget-redirect - traefik.http.middlewares.userfieldshistory-widget-redirect.redirectscheme.scheme=https - traefik.http.routers.userfieldshistory-widget-https.rule=Host(`userfieldshistory-widget.end-point.ru`) - traefik.http.routers.userfieldshistory-widget-https.entrypoints=websecure - traefik.http.routers.userfieldshistory-widget-https.tls=true - traefik.http.routers.userfieldshistory-widget-https.tls.certresolver=letsencrypt - traefik.http.services.userfieldshistory-widget.loadbalancer.server.port=80 - traefik.docker.network=traefik networks: traefik: external: true db: external: true volumes: cron_spool: cron_d:
services.app Контейнер приложения userfieldshistory-widget, собирается из локального Dockerfile. запускается с командой bootstrap-docroot.sh. установлены переменные окружения для часового пояса, окружения приложения, имени, хоста и подключения к базе данных. контейнер подключен к сетям traefik и db, порт 80 выставлен через expose. смонтированы локальные каталоги с кодом и cron-таблицами. через labels настроена маршрутизация и поддержка HTTPS с TLS (Let's Encrypt) для Trae
volumes.cron_spool / volumes.cron_d Постоянное хранение cron-заданий и конфигураций.
networks.traefik/networks.db
Подключение к сети traefik и к сети базы данных.
Dockerfile:
Copy
FROM php:8.1-apache ARG CACHE_BUST=initial RUN echo "cache-bust=$CACHE_BUST" >/dev/null ENV TZ=Europe/Moscow ENV APACHE_RUN_USER=www-data ENV APACHE_RUN_GROUP=www-data RUN ln -snf /usr/share/zoneinfo/{%formula content="$TZ /etc/localtime && echo $" /%}TZ > /etc/timezone RUN echo "umask 000" >> /etc/bash.bashrc && echo "umask 000" >> /etc/profile RUN apt-get update && apt-get install -y \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ libzip-dev \ libicu-dev \ libonig-dev \ libxml2-dev \ default-mysql-client \ unzip \ git \ curl \ nano \ cron \ tzdata \ && docker-php-ext-configure gd —with-freetype —with-jpeg \ && docker-php-ext-install -j$(nproc) \ gd \ mysqli \ pdo_mysql \ zip \ intl \ mbstring \ opcache \ xml \ soap \ pcntl \ && rm -rf /var/lib/apt/lists/* RUN echo "date.timezone = Europe/Moscow" > /usr/local/etc/php/conf.d/timezone.ini RUN a2enmod rewrite headers ssl RUN printf "ServerName localhost\n" > /etc/apache2/conf-available/servername.conf && a2enconf servername RUN echo "umask 000" >> /etc/apache2/envvars RUN printf '%s\n' \ '#!/usr/bin/env bash' \ 'set -e' \ 'umask 000' \ 'chmod -R 777 /var/www/html 2>/dev/null || true' \ 'DOCROOT=/var/www/html' \ 'CANDIDATES="/var/www/html/public /var/www/html/web /var/www/html/dist /var/www/html/app"' \ 'FOUND=""' \ 'for d in $CANDIDATES; do' \ ' if [ -f "$d/index.php" ]; then' \ ' FOUND="$d"' \ ' break' \ ' fi' \ 'done' \ 'if [ -z "$FOUND" ]; then' \ ' CAND=$(find "/var/www/html" -maxdepth 3 -type f -name index.php 2>/dev/null | head -n1)' \ ' if [ -n "$CAND" ]; then' \ ' FOUND={%formula content="$(dirname \"$" /%}CAND")' \ ' fi' \ 'fi' \ '[ -z "$FOUND" ] && FOUND="/var/www/html"' \ 'if [ "$FOUND" != "/var/www/html" ]; then' \ ' if [ ! -f "/var/www/html/index.php" ] || grep -q "AUTO-GENERATED BOOTSTRAP" "/var/www/html/index.php" 2>/dev/null; then' \ ' cat > "/var/www/html/index.php" <<EOPHP' \ '<?php /* AUTO-GENERATED BOOTSTRAP */' \ '{%formula content="$target = __DIR__ . substr(\"$" /%}FOUND", strlen("/var/www/html")) . "/index.php";' \ 'if (!is_file($target)) { http_response_code(500); echo "Index not found"; exit; }' \ 'require $target;' \ 'EOPHP' \ ' chmod 777 "/var/www/html/index.php" 2>/dev/null || true' \ ' chown www-data:www-data "/var/www/html/index.php" 2>/dev/null || true' \ ' fi' \ 'fi' \ 'service cron start >/dev/null 2>&1 || /usr/sbin/cron >/dev/null 2>&1 || true' \ 'exec apache2-foreground' \ > /usr/local/bin/bootstrap-docroot.sh RUN chmod +x /usr/local/bin/bootstrap-docroot.sh COPY —from=composer:latest /usr/bin/composer /usr/bin/composer CMD ["/usr/local/bin/bootstrap-docroot.sh"]
Установка зависимостей устанавливаются необходимые пакеты для PHP (gd, mysqli, pdo_mysql, zip, intl, mbstring, opcache, xml, soap, pcntl), клиент MySQL, утилиты (git, curl, nano, unzip, cron) и настройки таймзоны.
Настройка PHP и Apache
задаётся
date.timezone(Europe/Moscow)включаются модули Apache:
rewrite,headers,sslконфигурируется
ServerNamelocalhostустанавливается
umask 000для корректных прав доступа
После создания файлов выполняются команды для поднятия контейнера, дампа autoload composer-a и тп. В конечном итоге, упрощенно, архитектуру сервера можно изобразить вот-так:

Рекомендации по обслуживанию сервера
Для начала, стоит выделить основные проблемы, которые могут случиться. Для проведения работ на сервере необходимо получить к нему доступ под ssh, под пользователем root.
1. Израсходовано место на диске или закончились доступные иноды.
Профилактика и информирование
На сервере существует скрипт, который каждый час проверяет объем свободного пространства на диске, и в случае, если остаток памяти приблизится к опасным значениям — сообщит об этом в специальный чат Битрикс. Также, для надежности, стоит раз в две недели или чаще проверять остаток памяти и инодов вручную:
df -ih /— общий остаток инодовdf -h— остаток доступной памяти(смотреть на диск /dev/vda2/)du --inodes -s /opt/apps/ | sort -nr | head -n 10— получение топ-10 каталогов по использованию инодов
Очистка памяти
Если вдруг, было обнаружено, что на сервере осталось критически мало места, то необходимо провести расследование «Какое из приложений записало очень много логов». Ниже представлены шаги проведения расследования:
Узнаем остаток свободного места на диске /dev/vda2/. Команда:
df -h. Если свободное место еще осталось, то почти наверняка закончились свободные инодыПроверяем общий остаток инодов. Команда:
df -ih /. Далее, есть два варианта:
Закончились свободные иноды — в случае, если вывод команды
df -ih /указывает на то, что иноды закончились, то с огромной вероятностью, причина находится в директории/opt/apps. Потому стоит выполнить командуdu --inodes -s /opt/apps/ | sort -nr | head -n 10, обнаружив приложение, которое заняло большую часть инодов — необходимо почистить его директорию логов, в большинстве случаев все иноды забиваются именно ими, но если очистка логов не помогла, то стоит поискать иную директорию, достоверно ответственный за оккупацию инодов.Закончилось свободное место на диске — если было подтверждено, что свободных инодов на данный момент в достатке, то это означает, что сервер забился не количеством файлов, а их размером. В таком случае, необходимо выполнить команду
du -h /opt/apps | sort -rh | head -n 10. В выводе команды будет список с директориями которые занимают больше всего места.
В заключение можно выполнить полезную команду для очистки неиспользуемых images и volumes докера. Перед выполнение команды, ОБЯЗАТЕЛЬНО, необходимо проверить, что ни один из необходимых контейнеров не выключен. Команда для просмотра активных контейнеров: docker ps Команда для очистки: docker system prune -a
2. CI/CD и проблемы с развертыванием
У каждого активного приложения в репозтории находятся пайплайн для доставки и развертывания приложения на сервер. Разберем основные аспекты стандартного пайплайна:
Подготовка приложения и копирование кода
APP_NAME="${CI_PROJECT_NAME#app-}" CONTAINER_NAME={%formula content="$(echo \"$" /%}{APP_NAME}" | tr '-' '_') APP_DIR="{%formula content="${APPS_DIR}/$" /%}{CONTAINER_NAME}" if [ -f "${ENV_COMMON}" ]; then set -a; source "${ENV_COMMON}"; set +a fi ENV_APP="/opt/.apps-env/${CONTAINER_NAME}.env" if [ -f "${ENV_APP}" ]; then set -a; source "${ENV_APP}"; set +a fi mkdir -p "${APP_DIR}" rsync -a —delete \ —exclude='.git/' \ —exclude='vendor/' \ —exclude='node_modules/' \ —exclude='.env' \ "{%formula content="${CI_PROJECT_DIR}/\" \"$" /%}{APP_DIR}/"
Что здесь происходит:
Из имени проекта вычисляется имя приложения и контейнера.
Подгружаются общие и индивидуальные переменные окружения.
Создаётся директория приложения на сервере.
Код копируется из GitLab Runner в директорию приложения.
При копировании исключаются определенные файлы(см. на флаг —exclude).
Фактически этот блок отвечает за подготовку рабочей директории для дальнейшего деплоя.
Проверка настроек и подключение Redis
OPTIONS_FILE="{%formula content="${APP_DIR}/$" /%}{DEPLOY_OPTIONS_FILE}" REDIS_ENABLED=0 if [ -f "${OPTIONS_FILE}" ]; then if { grep -A40 '^redis:' "${OPTIONS_FILE}" 2>/dev/null || true; } \ | grep -qE '^\s*enabled:\s*(true|yes|1)\b'; then REDIS_ENABLED=1 fi fi
Что здесь происходит:
Читается файл
deploy.options.yml.Из него определяется, нужно ли поднимать Redis.
Если Redis включён, дальше в
docker-compose.ymlавтоматически добавится дополнительный сервис.
Автоматическая генерация Docker-образа
FROM php:8.1-apache RUN apt-get update && apt-get install -y \ libfreetype6-dev \ libjpeg62-turbo-dev \ libpng-dev \ libzip-dev \ libicu-dev \ default-mysql-client \ unzip git curl cron \ && docker-php-ext-install \ gd mysqli pdo_mysql zip intl mbstring opcache xml soap pcntl \ && pecl install redis \ && docker-php-ext-enable redis COPY —from=composer:latest /usr/bin/composer /usr/bin/composer CMD ["docker-entrypoint.sh"]
Что здесь происходит:
Создаётся Docker-образ на базе PHP 8.1 и Apache.
Устанавливаются необходимые системные библиотеки.
Устанавливаются основные PHP-расширения.
Добавляется Composer.
Устанавливается поддержка Redis.
Это основа всей среды выполнения приложения. Если необходимо добавить развертывание npm или чего-либо — добавлять логику стоит именно сюда
Генерация docker-compose и публикация приложения
services: app: build: . container_name: ${CONTAINER_NAME} restart: unless-stopped environment: - APP_ENV=production - APP_NAME=${APP_NAME} - APP_HOST=${APP_HOST} - DB_HOST=db - DB_PORT=3306 labels: - traefik.enable=true - traefik.http.routers.{%formula content="${APP_NAME}-http.rule=Host(`$" /%}{APP_HOST}`) - traefik.http.routers.{%formula content="${APP_NAME}-https.rule=Host(`$" /%}{APP_HOST}`) - traefik.http.routers.${APP_NAME}-https.tls=true - traefik.http.routers.${APP_NAME}-https.tls.certresolver=letsencrypt
Что здесь происходит:
Создаётся контейнер приложения.
Передаются переменные окружения.
Настраиваются labels для Traefik.
Привязывается домен приложения.
Автоматически выпускается SSL-сертификат Let's Encrypt.
Именно этот блок делает приложение доступным по адресу вида:
https://my-app.end-point.ru
Запуск контейнеров и контроль успешности деплоя
docker compose up -d —build —remove-orphans for i in $(seq 1 30); do [ "{%formula content="$(docker inspect -f '{{.State.Running}}' \"$" /%}{CONTAINER_NAME}")" = "true" ] && break if [ "${i}" -eq 30 ]; then docker logs "${CONTAINER_NAME}" —tail 30 exit 1 fi sleep 2 done
Что здесь происходит:
Собирается новый Docker-образ.
Запускаются контейнеры.
Выполняется проверка, что контейнер действительно стартовал.
Если запуск не удался, выводятся последние логи и пайплайн завершается ошибкой.
Это защита от ситуации, когда деплой прошёл "зелёным", а приложение фактически не работает.
Финальная настройка приложения
if docker exec "${CONTAINER_NAME}" \ test -f /var/www/html/composer.json; then docker exec -w /var/www/html -u root "${CONTAINER_NAME}" \ composer install —no-dev —optimize-autoloader docker exec -w /var/www/html -u root "${CONTAINER_NAME}" \ composer dump-autoload —no-dev —optimize fi docker exec -u root "${CONTAINER_NAME}" bash -lc ' apache2ctl -t apache2ctl -k graceful '
Что здесь происходит:
После запуска контейнера устанавливаются Composer-зависимости.
Генерируется оптимизированный autoload.
Проверяется конфигурация Apache.
Выполняется перезагрузка Apache без остановки контейнера.
Основные проблемы
Разобрав пайплайн, стоит перейти к основным проблемам, которые могут случится при его выполнение.
Проблема с правами — возникает, если при выполнение rsync какой-то файл имеет неправильный права:
Подготовка деплоя для support-dev rsync: [generator] delete_file: rmdir(handlers/log/endpoint.bitrix24.ru/requestHandlers) failed: Permission denied (13) rsync: [generator] delete_file: rmdir(handlers/log/endpoint.bitrix24.ru/botHandlers) failed: Permission denied (13) rsync: [generator] delete_file: rmdir(handlers/log/endpoint.bitrix24.ru/botCommands) failed: Permission denied (13) rsync: [generator] delete_file: rmdir(handlers/log/endpoint.bitrix24.ru) failed: Permission denied (13) cannot delete non-empty directory: handlers/log/endpoint.bitrix24.ru/requestHandlers cannot delete non-empty directory: handlers/log/endpoint.bitrix24.ru/botCommands cannot delete non-empty directory: handlers/log/endpoint.bitrix24.ru cannot delete non-empty directory: handlers/log rsync error: some files/attrs were not transferred (see previous errors) (code 23) at main.c(1338) [sender=3.2.7] Cleaning up project directory and file based variables00:00 ERROR: Job failed: exit status 1
Данную проблему можно исправить разобравшись с правами файлов, на которые жалуется rsync.
Проблема с пакетами — возникает, если docker не смог подтянуть какой-либо пакет. В качестве экстренной меры можно попробовать сменить версию пакета или удалить его вовсе, если это не критично.
3. Бэкапы
Если, все же, что-то пошло не так, то на сервере делается бэкап. Кроме того, бэкапы стоит проверять каждую неделю. Бэкапы делаются службой restik, для управления бэкапами на сервере установлен веб-интерфейс Backrest.
Проверка бэкапов
Для проверки, необходимо перейти на страницу Backrest и просмотреть все репозитории и планы на предмет ошибок

На сервере делается ряд бэкапов, имеющие как отдельный план, так и отдельный репозиторий:
apps — бэкап всех важных файлов приложений, за исключением логов и исходного кода (только клиенткие конфиги и прочие файлы конфигураций).
db — центральная база данных сервера.
legacy — бэкап старых версий приложений, план настроен по примеру apps.
legacy-db — бэкап центральной базы данных старых версий приложений.
service — бэкап сервисов сервера.
Планы и репозитории с флагом -cloud это дубликаты, которые отправляют собранные снимки на наш офисный сервер.
Восстановление
Для восстановления, необходимо перейти в Backrest, во вкладку plans, после чего, выбрать необходимый план и бэкап. Далее, необходимо раскрыть Snapshot Browser и выбрать в нем то, что необходимо восстановить. При наведение на директорию раскрывается выпадающий список, в котором необходимо нажать на Restore to path и выбрать необходимый путь для восстановления

Внимательно изучите содержимое снимка, перед его восстановлением