Битрикс: от модулей к сервисам

Приветствую всех не равнодушных!

Хочу поделиться с вами историей о том, как мы рефакторили код проекта на Битрикс24 под DDD архитектуру. Возможно кому-то это будет полезно, а возможно, и сам подчерпну что-то новое для себя.

Hidden text

Для тех, кто с ходу заявит — только не Битрикс, советую ознакомиться со статьей:

«Только не «Битрикс»!». Почему не стоит игнорировать изучение этого фреймворка

Часто от программистов PHP можно услышать: «О нет! Только не «Битрикс»!». Многие специалисты не хотя…

habr.com


Согласен, что это не лучшая платформа, однако, от ее популярности в СНГ никуда не денешься.

Также посыл текущей статьи в том, что оставаясь внутри нашей любимой битры, мы можем также развиваться в сторону более современных платформ.

Итак, имеется портал написанный давно на Битрикс24 коробочной версии, задача — упростить поддержку, избавиться от кучи портянок в кастомных модулях, привести код к единообразию, чтобы разработчики делали его в едином стиле, добавить возможность быстрого переключения между внешними сервисами одного типа.
Задача два — позволить разработчикам битрикса, развиваться, не оставаясь заложниками одной CRM

Шаг 1: контейнеризация

За основу была взята сборка

GitHub — bitrixdock/bitrixdock: BitrixDock — это готовое Docker окружение для Bitrix CMS: dart:

github.com

Клонируем ее в собственный репозиторий:

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

Запускаем сборку и любуемся:

7a07bbd00b49c1811690d25d5939be6f.png

Не забываем добавить исклчения для нашего проекта в .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 пользователя, который работает снаружи

06bd4a0d4331107dc62c7cd08ea53b32.png

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

 [
        'value' => [
            'type' => 'memcache',
            'memcache' => [
                'host' => 'memcached',
                'port' => '11211',
            ],
            'sid' => $_SERVER["DOCUMENT_ROOT"] . "#01"
        ],
    ],
]

Контейнер memcache работает, и работает очень быстро, по сравнению с дисковым кешем, что видно по запросам

dd1afb2ce998355c2f3e085834781011.png

Однако! Видим, что на macOS и Linux все прекрасно, но на windows машинах появляются тормоза, вплоть до 6 секунд при загрузке. Это крайне не приятно и сильно мешает разработке.

Путем поиска и умозаключений, было выявлено, что проблема заключается в работе wsl2, и когда очень много файлов, то начинаются заметные тормоза при медленных дисках родительской системы.

Решение 1: Закупить SSD и перенести проект на него.

Решение 2: Организовать облако и сделать площадки для разработчиков через kubernetes

Решение 3:

Комментатор: У нас бюджет только на одну виртуалку, как нам быть?

Я: Если мы запустим 2 контейнера на 1 виртуалке, то они поругаются за права владения портами 80 и 443

Комментатор: А как же нам быть?

Я: Выход есть, но, возможно не идеальный. Делаю.

  1. Подготавливаю виртуалку для работы множества пользователей

    1. создаем каждому сотруднику учетную запись на сервере

    2. заводим пользователя для управления контейнерами, к примеру web-master: web-master и его добавляем в группу docker.

    3. всех пользователей добавляем в одну общую группу, к этому web-master sudo usermod -g web-master anatoliy-pro, причем важно, чтобы первичная группа у всех пользователей была именно web-master, чтобы права на файлы не переписывались.

    4. разворачиваем директории с docker-compose проектами и делаем права на все файлы у докер-сборки 640 и владелец web-master: web-master, таким образом изменения для докера будут делаться под этим пользователем

    5. в контейнерах директории www. делаем с правами пользователя, под которым будет идти работа anatoliy-pro:web-master, на всякий случай после клонирования проекта в папку web, отключаем слежение за правами в git, чтобы они не улетели на прод cd ./www && git config core.fileMode false

    6. Редактируем наш 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: заключение

Знаю, что тема статьи Битрикс, а мы до него даже не дошли.

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

На эти настройки будет завязана вся работа, которую я опишу во второй части, если будет интерес к теме.

Habrahabr.ru прочитано 1452 раза