Приветствую всех не равнодушных!
Хочу поделиться с вами историей о том, как мы рефакторили код проекта на Битрикс24 под DDD архитектуру. Возможно кому-то это будет полезно, а возможно, и сам подчерпну что-то новое для себя.
Hidden text
Для тех, кто с ходу заявит - только не Битрикс, советую ознакомиться со статьей:
Согласен, что это не лучшая платформа, однако, от ее популярности в СНГ никуда не денешься.
Также посыл текущей статьи в том, что оставаясь внутри нашей любимой битры, мы можем также развиваться в сторону более современных платформ.
Итак, имеется портал написанный давно на Битрикс24 коробочной версии, задача - упростить поддержку, избавиться от кучи портянок в кастомных модулях, привести код к единообразию, чтобы разработчики делали его в едином стиле, добавить возможность быстрого переключения между внешними сервисами одного типа.
Задача два - позволить разработчикам битрикса, развиваться, не оставаясь заложниками одной CRM
Шаг 1: контейнеризация
За основу была взята сборка
Клонируем ее в собственный репозиторий:
mkdir my_project
cd my_project
git clone https://github.com/bitrixdock/bitrixdock ./
git remote set-url origin https://my_gitlab/repo_docker
git push
Далее встает вопрос о том, что продуктовая ветка должна быть обезопасена, как минимум - нестандартными портами и на ней не должно быть adminer
Hidden text
На деле, админер вообще лучше не использовать, но это полемика.
Для того, чтобы конфигурировать можно было не через docker-compose.yml и всем его раскидывать, удобнее будет сделать ветки master, stage, developer, на которых уже можно делать конфигурацию для конкретной среды и она будет обновляться путем git pull
Далее будет приводиться пример, будто все в одной ветке на одной сборке, вы же у себя сами решайте, что вам на каких окружениях требуется:
Добавляем сервисы для сбора логов и хранения сессий.
Дабы обойти санкции на поставку ELK, воспользуемся готовой сборкой не от официалов:
rabbitmq:
image: rabbitmq:3-management
# container_name: rabbitmq
hostname: rabbitmq
volumes_from:
- source
ports:
- '${INTERFACE}:${RABBIT_PORT1:-15672}:15672'
- '${INTERFACE}:${RABBIT_PORT2:-5672}:5672'
restart: always
environment:
RABBITMQ_DEFAULT_USER: ${RABBIT_USER}
RABBITMQ_DEFAULT_PASS: ${RABBIT_PASS}
RABBITMQ_SERVER_ADDITIONAL_ERL_ARGS: -rabbit log_levels [{connection,error},{default,error}] disk_free_limit 2147483648
TZ: Europe/Moscow
stdin_open: true
tty: true
networks:
- bitrixdock
elk:
image: sebp/elk
environment:
node.name: elk
ES_JAVA_OPTS: -Xms512m -Xmx512m
# Bootstrap password.
# Used to initialize the keystore during the initial startup of
# Elasticsearch. Ignored on subsequent runs.
ELASTIC_PASSWORD: ${ELASTIC_PASSWORD:-}
# Use single node discovery in order to disable production mode and avoid bootstrap checks.
# see: https://www.elastic.co/guide/en/elasticsearch/reference/current/bootstrap-checks.html
discovery.type: single-node
LS_JAVA_OPTS: -Xms256m -Xmx256m
LOGSTASH_INTERNAL_PASSWORD: ${LOGSTASH_INTERNAL_PASSWORD:-}
# mem_limit: 4g
volumes:
- ./elk/logstash/20-input.conf:/etc/logstash/conf.d/20-input.conf
- ./elk/logstash/30-output.conf:/etc/logstash/conf.d/30-output.conf
ports:
- "${KIBANA_PORT:-5601}:5601"
- "${ELASTIC_PORT:-9200}:9200"
- "${LOGSTASH_PORT:-5044}:5044"
networks:
- bitrixdock
Как видите container_name: rabbitmq
закомментировано, для того, чтобы docker-compose сам назначал имя ${COMPOSE_PROJECT_NAME}_serviceName,
таким образом, если у вас будет несколько проектов с таким docker-compose.yml, то сервисы не будут стучаться в чужие сборки по имени контейнера.
Шаг 2: настройка
Для настройки логирования в ЕЛК, выберем схему php monolog -> rabbitMQ -> logStash -> elasticSearch -> kibana
Про то, как настроить монолог и отправить сообщения в очередь, мы поговорим в разделе настройки проекта, а сейчас покажу - как забирать эти сообщения из очереди:
./elk/logstash/20-input.conf
input {
rabbitmq {
host => "rabbitmq" # имя контейнера
port => 5672
queue => "elk" # имя очереди
durable => true
passive => true
exchange => "logs" # имя обмена
user => "rabbit-user"
password => "rabbit-password"
}
}
Форматируем сообщения:
./elk/logstash/30-output.conf
output {
elasticsearch {
hosts => ["localhost"]
manage_template => false
index => "%{[@type]}-%{[@source]}-%{+YYYY.MM.dd}"
}
}
Logstash забирает сообщения из очереди logs и передает их в elasticsearch, через kibana мы можем их просматривать и создавать дашборды. Все будет доступно по localhost:SERVICE_PORT из списка
- "${KIBANA_PORT:-5601}:5601"
- "${ELASTIC_PORT:-9200}:9200"
- "${LOGSTASH_PORT:-5044}:5044"
Пробрасываем общие директории через отдельный сервис, который запускается, шарит общие папки между всеми контейнерами и отключается:
source:
image: alpine:latest
# container_name: source
volumes:
- ./logs/${WEB_SERVER_TYPE}:/var/log/${WEB_SERVER_TYPE}
- ./logs/php:/var/log/php
- ./logs/db:/var/log/mysql
- ./logs/memcached:/var/log/memcached
- db:/var/lib/mysql
- cache:/var/lib/memcached
- ${SITE_PATH}:/var/www/bitrix
- /etc/localtime:/etc/localtime/:ro
- ./rabbitmq/data:/var/lib/rabbitmq
networks:
- bitrixdock
Запускаем сборку и любуемся:
Не забываем добавить исклчения для нашего проекта в .gitignore, чтобы проект внутри контейнера мог жить на отдельной gitlab площадке от контейнера и их коммиты не пересекались
.idea
.vscode
.fleet
logs
.env
www/* # Проект
rabbitmq/data
Шаг 3: решение проблем
Проблема 1: в контейнеры, директория с проектом ./www пробрасывается по пути: /var/www/bitrix/ и все наши внешние изменения, сразу изменяются и в контейнерах. Но возникает следующее: Пользователь, изменяя файлы снаружи, внутри перезаписывает права на файл с id внешнего пользователя. Таким образом, если мы зайдем внутрь контейнера docker-exec -u www-data php bash
, перейдем в директорию проекта cd /var/www/bitrix/
и посмотрим права на файлы, то увидим, что все файлы, которые менялись изнутри, с владельцем www-data:www-data а измененный снаружи - id_user:id_group и это приводит к тому, что на linux системах контейнеры теряют доступ на чтение этих файлов (чего не происходит на windows и macOS), но мы же приверженцы прав на файлы 640 и не хотим все делать 777?
Решение: добавляем в наш .env переменные с id пользователя, который работает снаружи
UDP: На примере выше, настройки mysql оставлены по-умолчанию, тк считаю плохой практикой держать БД с проектом на одной машине, я этот сервис отключаю, но оставил как пример или для локальной разработки.
Далее меняем для сервисов php и web_server запись в docker-compose.yml на подобную, для пробрасывания этих переменных окружения.
php:
build:
context: ./php/${PHP_VERSION}
args:
UID: ${UID:-1000}
GID: ${GID:-1000}
# container_name: php
Меняем пользователей, под которым работают сервисы в их Dockerfile RUN usermod -u ${UID} www-data
Hidden text
FROM phpdockerio/php:8.2-fpm
LABEL org.opencontainers.image.source="https://github.com/bitrixdock/bitrixdock"
RUN apt-get update \
&& apt-get -y --no-install-recommends install \
libc-client-dev libkrb5-dev libssl-dev \
php8.2-gd \
php8.2-imagick \
php8.2-intl \
php8.2-interbase \
php8.2-mbstring \
php8.2-mcrypt \
php8.2-memcache \
php8.2-memcached \
php8.2-mysql \
php8.2-opcache \
php8.2-soap \
php8.2-zip \
php8.2-imap \
php8.2-curl \
&& apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* /usr/share/doc/*
# Получение переменных из docker-compose.yml
ARG UID
ARG GID
COPY ./php.ini /etc/php/8.2/fpm/conf.d/90-php.ini
COPY ./php.ini /etc/php/8.2/cli/conf.d/90-php.ini
# Настройка imagick
COPY ./imagick/policy.xml /etc/ImageMagick-6/policy.xml
# Смена пользователя www-data на пользователя извне
RUN usermod -u ${UID} www-data
RUN echo 'User: ' ${UID};
WORKDIR "/var/www/bitrix"
EXPOSE 9000
Вуаля! В контейнерах изменения пишутся под тем же пользователем, что и снаружи, таким образом 640 нас начинает устраивать.
Проблема 2: при работе с проектом, желательно вынести кеширование и сессии в memcache, чтобы не нагружать файловую систему, внесем соответствующие настройки.
Hidden text
<?php
return [
/*****
* Cache в Memcache.
*****/
'cache' => [
'value' => [
'type' => 'memcache',
'memcache' => [
'host' => 'memcached',
'port' => '11211',
],
'sid' => $_SERVER["DOCUMENT_ROOT"] . "#01"
],
],
]
<?php
const BX_SECURITY_SESSION_MEMCACHE_HOST = 'memcached';
const BX_SECURITY_SESSION_MEMCACHE_PORT = 11211;
Контейнер memcache работает, и работает очень быстро, по сравнению с дисковым кешем, что видно по запросам
Однако! Видим, что на macOS и Linux все прекрасно, но на windows машинах появляются тормоза, вплоть до 6 секунд при загрузке. Это крайне не приятно и сильно мешает разработке.
Путем поиска и умозаключений, было выявлено, что проблема заключается в работе wsl2, и когда очень много файлов, то начинаются заметные тормоза при медленных дисках родительской системы.
Решение 1: Закупить SSD и перенести проект на него.
Решение 2: Организовать облако и сделать площадки для разработчиков через kubernetes
Решение 3:
Комментатор: У нас бюджет только на одну виртуалку, как нам быть?
Я: Если мы запустим 2 контейнера на 1 виртуалке, то они поругаются за права владения портами 80 и 443
Комментатор: А как же нам быть?
Я: Выход есть, но, возможно не идеальный. Делаю.
-
Подготавливаю виртуалку для работы множества пользователей
создаем каждому сотруднику учетную запись на сервере
заводим пользователя для управления контейнерами, к примеру web-master:web-master и его добавляем в группу docker.
всех пользователей добавляем в одну общую группу, к этому web-master
sudo usermod -g web-master anatoliy-pro,
причем важно, чтобы первичная группа у всех пользователей была именно web-master, чтобы права на файлы не переписывались.разворачиваем директории с docker-compose проектами и делаем права на все файлы у докер-сборки 640 и владелец web-master:web-master, таким образом изменения для докера будут делаться под этим пользователем
в контейнерах директории www. делаем с правами пользователя, под которым будет идти работа
anatoliy-pro:web-master
, на всякий случай после клонирования проекта в папку web, отключаем слежение за правами в git, чтобы они не улетели на продcd ./www && git config core.fileMode false
-
Редактируем наш docker-compose.yml на ветке developer, чтобы каждой площадке дать свои собственные порты
Hidden text
.env
#Порты для площадки
PORT_80=your_port
PORT_443=your_port
В сервисах меняем порты на переменные
docker-compose.yml
ports:
- '${INTERFACE}:${PORT_80:-80}:80'
- '${INTERFACE}:${PORT_443:-443}:443'
Таким образом, проблема будет решена и по адресу http://server:your_port будет открывать конкретная площадка и никто не будет драться за одинаковые порты.
Часть 4: заключение
Знаю, что тема статьи Битрикс, а мы до него даже не дошли.
Тем не менее, на мой взгляд, настройка окружения под битрикс, является не менее важной частью самой работы над ним и его оптимизации.
На эти настройки будет завязана вся работа, которую я опишу во второй части, если будет интерес к теме.