В этой статье мы расскажем о переносе одного из компонентов монолитного SAAS-сервиса, а именно тестового интернет-магазина, в контейнеры Docker. Статья будет полезна тем, кто только приступил к изучению Docker.  

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

Прежде всего, хотелось оценить возможность и трудоемкость переноса всего SAAS-сервиса интернет-магазинов в контейнеры. Предполагается, что такой перенос повлияет положительно на отказоустойчивость сервиса и масштабируемость при соответствующих изменениях в его архитектуре. После переноса появится возможность переработки технологии CI/CD с применением специально предназначенных для этого инструментов. Перенос в Docker создает предпосылки к переходу на микро-сервисную архитектуру.

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

И, конечно, использование контейнеров «модно и молодежно» и будет интересно программистам, занятым в сопровождении и развитии сервиса (рис. 1).

Рис. 1. Молодой программист в интерпретации нейросети Midjourney
Рис. 1. Молодой программист в интерпретации нейросети Midjourney

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

В интернете можно найти немало курсов и статей на эту тему. Но наш SAAS-сервис написан на Perl, и довольно давно — работы были начаты примерно 20 лет назад. Чтобы было понятно, о чем идет речь, мы показали на рис. 2 портрет основателя сервиса и автора этой статьи, созданный Midjourney.

Рис. 2. Автор статьи и основатель SAAS-сервиса интернет-магазинов по версии Midjourney
Рис. 2. Автор статьи и основатель SAAS-сервиса интернет-магазинов по версии Midjourney

Большинство курсов, книг и статей, посвященных Docker, приводят примеры переноса в контейнеры приложений, написанных на других, более популярных сегодня языках программирования — Python, Go, JavaScript (NodeJS) и других. 

В процессе переноса возникало множество вопросов. Мы читали документацию и различные статьи, искали ответы в интернете с помощью Google, YouTube, СhatGPT, а также различных групп Telegram.

Хотя СhatGPT не всегда подсказывал работающее решение, он сэкономил нам немало времени. Некоторые изображения для этой статьи были созданы при помощи нейросети Midjourney и фрагментов переписки с СhatGPT буквально за несколько минут.

Архитектура сервиса SAAS

Кратко расскажем о том, как устроена часть SAAS-сервиса, имеющая отношение к тестовым интернет-магазинам.

До переноса тестовые интернет-магазины создавались на виртуальных машинах Debian с установленной панелью управления ISPmanager или HestiaCP. Начальная подготовка такой виртуальной машины заключалась в установке OS Debian, одной из перечисленных выше панелей управления, а также в запуске скриптов начального развертывания среды SAAS-сервиса. 

С учетом необходимой дополнительной ручной работы на подготовку такой виртуальной машины у системного администратора уходит 1–2  дня. Исторически такие инструменты, как Ansible, не использовались.

При переносе тестового интернет-магазина в контейнеры ставилась задача максимального облегчения работы программистов. В идеале они должны самостоятельно создавать  и запускать на своих рабочих станциях нужные контейнеры, используя для работы с ПО те же самые инструменты, которыми пользовались раньше. 

Кроме того, нужно было минимизировать изменения в ПО сайтов, связанные с переходом к работе в контейнерах Docker. Из этих соображений контейнеры должны были предоставлять совместимую среду выполнения для скриптов интернет-магазинов.

Тестовый интернет-магазин состоит из трех сайтов (рис. 3):

  • сайт витрины;

  • административный сайт;

  • сайт конфигуратора

Рис. 3. Размещение сервисов тестового интернет-магазина на виртуальной машине
Рис. 3. Размещение сервисов тестового интернет-магазина на виртуальной машине

Все эти сайты работают с одной и той же базой данных Mariadb. При этом скрипты сайта витрины и административного сайта должны иметь доступ к файлам ядра ПО сервиса, которые хранятся в каталогах специально выделенного для этого пользователя core.

Для кеширования сайты используют memcached, а для ускорения поиска — Sphinx. Кроме того, с помощью Sphinx организован поиск в базе данных KLADR.

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

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

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

Сайты витрины и административный сайт работают вместе. Вначале мы разместили эти сайты в одном контейнере, чтобы для программистов все было похоже на работу с существующими виртуальными машинами. Однако потом было принято решение выделить для каждого из этих сайтов отдельный контейнер (рис. 4). 

Рис. 4. Контейнеры Docker для тестового интернет-магазина
Рис. 4. Контейнеры Docker для тестового интернет-магазина

В файле docker-compose.yml проекта были созданы соответствующие сервисы, названия которых показаны на рис. 4. 

Как видно из рисунка, сервис app реализует функциональность административного сайта, app_shop — сайта витрины интернет-магазина, а сервис app_config — сервис конфигуратора.

В рамках сервиса mariadb работает одноименная СУБД, а сервис memcached кэширует запросы к базе данных (и к некоторым другим сервисам) со стороны сайтов.

Для работы с базами данных в процессе отладки используются сервисы adminer и phpmyadmin. При этом программисты могут выбрать тот инструмент, к которому они больше привыкли.

И, наконец, сервис обратного прокси nginx-proxy необходим для обеспечения доступа ко всем трем сайтам с использованием портов 80 и 443. Дополнительно он позволяет подключать к сайтам сертификаты SSL, как бесплатные Let`s Encrypt, так и приобретенные для конкретных доменных имен.

При таком разделении на контейнеры с применением систем оркестрации Swarm или Kubernetes в рабочем окружении появится возможность запуска контейнеров на разных узлах кластера. Это даст возможность повышения отказоустойчивости и нагрузочной способности. 

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

Что пришлось изменить в проекте при переносе

Оказалось, что при соответствующей настройке контейнеров для запуска интернет-магазинов в Docker с целью разработки и отладки потребовалось не так много изменений. Фактически все свелось к правке строк конфигурации, имеющих отношение к соединению с базой данных и memcached.

В файле конфигурации сайтов в DSN базы данных и в подключении к сервису Memcached строки localhost и 127.0.0.1 заменили именами соответствующих контейнеров из файла docker-compose.yml, описанного ниже:

$this->{ db_dsn } = "DBI:mysql:gift_db:mariadb";
$this->{ mcached_servers } = ["memcached:11211"];

Аналогичное изменение было сделано и в файле конфигурации Sphinx:

source cities
{
  type                                    = mysql
  sql_host                                = mariadb
  sql_user                                = kladr
  sql_pass                                = *********
  sql_db                                  = kladr
  sql_port                                = 3306
  …
}

Больше никакой адаптации скриптов делать не пришлось. При переводе архитектуры монолита на микро-сервисы планируется внести множество изменений, однако это уже будет другая история.

Файл docker-compose.yml

Ниже мы представили файл docker-compose.yml, который запускает все необходимые нам контейнеры:

version: '3'

services:
  app:
    build:
      context: app
    env_file: .env
    ports:
      - 8077:80
    depends_on:
      - memcached
      - mariadb
    volumes:
      - mariadb-socket:/run/mysqld/
      - ./app/home:/home
    networks:
      - mynet

  app_config:
    build:
      context: app_config
    env_file: .env
    ports:
      - 8078:80
    depends_on:
      - memcached
      - mariadb
      - app
    volumes:
      - mariadb-socket:/run/mysqld/
      - ./app/home:/home
    networks:
      - mynet

  app_shop:
    build:
      context: app_shop
    env_file: .env
    ports:
      - 8079:80
    depends_on:
      - memcached
      - mariadb
      - app
    volumes:
      - mariadb-socket:/run/mysqld/
      - ./app/home:/home
    networks:
      - mynet

  memcached:
    image: memcached
    ports:
      - "11211:11211"
    restart: always
    networks:
      - mynet      

  mariadb:
    image: mariadb
    restart: always
    environment:
      MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
      CONFIG_DB: ${CONFIG_DB}
      CONFIG_USER: ${CONFIG_USER}
      CONFIG_DB_PASSWORD: ${CONFIG_DB_PASSWORD}
      GIFT_DB: ${GIFT_DB}
      GIFT_DB_USER: ${GIFT_DB_USER}
      GIFT_DB_PASSWORD: ${GIFT_DB_PASSWORD}
      KLADR_DB: ${KLADR_DB}
      KLADR_USER: ${KLADR_USER}
      KLADR_DB_PASSWORD: ${KLADR_DB_PASSWORD}
    volumes:
      - ../mariadb:/var/lib/mysql
      - mariadb-socket:/run/mysqld/
      - ./app/mariadb_conf:/etc/mysql/conf.d
      - ./app/initdb:/docker-entrypoint-initdb.d
    networks:
      - mynet
    user: "root"
    command: --init-file /docker-entrypoint-initdb.d/01-create-db.sh            

  adminer:
    image: adminer
    restart: always
    ports:
      - 8080:8080
    networks:
      - mynet      

  phpmyadmin:
    image: phpmyadmin
    restart: always
    ports:
      - 8087:80
    environment:
      - PMA_ARBITRARY=1
    depends_on:
      - mariadb
    networks:
      - mynet      

  nginx_proxy:
    image: 'jc21/nginx-proxy-manager:latest'
    restart: unless-stopped
    ports:
      - '80:80'
      - '81:81'
      - '443:443'
    volumes:
      - ./data:/data
      - ./letsencrypt:/etc/letsencrypt
    networks:
      - nginx_proxy_net

volumes:
  mariadb-socket:
networks:
  mynet:
  nginx_proxy_net:

Сервисы для сайтов интернет-магазина

Так как контейнеры сайтов требуют индивидуальной настройки, например установки утилит и модулей Perl, то для каждого из них подготовлен файл Dockerfile. Эти файлы будут рассмотрены ниже в статье. Что касается сервисов Mariadb, Memcached и других, то они создаются на базе готовых образов, полученных с Docker Hub.

Блоки сервисов app, app_shop и app_config, определенные в файле docker-compose.yml, очень похожи. У них отличаются только пути к каталогу для сборки Docker-образа и отображение порта 80 контейнера на порты хоста :

app:
  build:
    context: app
  env_file: .env
  ports:
    - 8077:80
  depends_on:
    - memcached
    - mariadb
  volumes:
    - mariadb-socket:/run/mysqld/
    - ./app/home:/home
  networks:
    - mynet

Для административного сайта app используется порт 8077, для сайта витрины app_shop — порт 8079, а для сайта конфигуратора app_config — порт 8078. Эти отображения заданы в соответствующих списках ports.

Скрипты сайтов исторически были написаны таким образом, что им нужен доступ к базе данных не только по сети через порт 3306, но и через сокет. Поэтому в разделе volumes указано, что в контейнере должен быть доступен сокет Mariadb, а также каталог app/home, содержащий файлы и скрипты трех сайтов:

volumes:
  - mariadb-socket:/run/mysqld/
  - ./app/home:/home

Доступность в контейнере каталога app/home, отображающегося на каталог /home контейнера, позволяет разработчикам менять файлы сайтов, не перезапуская контейнер. 

Список depends_on определяет, что сервис app должен быть запущен после сервисов memcached и mariadb:

depends_on:
  - memcached
  - mariadb

Следует учесть, что на запуск зависимых сервисов, например, mariadb, может потребоваться некоторое время. Порядок запуска не гарантирует, что сервис будет запущен после завершения инициализации сервисов, от которых он зависит.

Для связи сервисов app, app_shop, app_config, memcached и mariadb мы используем список сетей networks, в котором определена сеть mynet:

networks:
  - mynet 

Параметр env_file указывает файл .env, содержащий пароли доступа к базам данных, пользователям и другую конфиденциальную информацию, определенную в виде переменных среды:

env_file: .env

Вот как может выглядеть такой файл (пароли заменены символами *****):

GIFT_PASSWORD=*****
CORE_PASSWORD=*****
KLADR_PASSWORD=*****
CONFIG_PASSWORD=*****
GIFT_DB=gift
GIFT_DB_USER=gift
GIFT_DB_PASSWORD=*****
KLADR_DB=kladr
KLADR_USER=kladr
KLADR_DB_PASSWORD=*****
CONFIG_DB=config
CONFIG_USER=config
CONFIG_DB_PASSWORD=*****
MARIADB_ROOT_PASSWORD=*****

По соображениям безопасности файл .env добавлен в файл .gitignore и не сохраняется в проекте Gitlab.

Сервис memcached

Сервис memcached используется сайтами для снижения нагрузки на базу данных и для сокращения запросов к различным сервисам. Он определен в файле docker-compose.yml следующим образом:

memcached:
  image: memcached
  ports:
    - "11211:11211"
  restart: always
  networks:
    - mynet  

Здесь нет ничего необычного. Указан стандартный для memcached порт 11211, а также сеть mynet.

Сервис mariadb

Для сервиса mariadb мы использовали готовый одноименный образ:

mariadb:
  image: mariadb
  restart: always
  environment:
    MARIADB_ROOT_PASSWORD: ${MARIADB_ROOT_PASSWORD}
    CONFIG_DB: ${CONFIG_DB}
    CONFIG_USER: ${CONFIG_USER}
    CONFIG_DB_PASSWORD: ${CONFIG_DB_PASSWORD}
    GIFT_DB: ${GIFT_DB}
    GIFT_DB_USER: ${GIFT_DB_USER}
    GIFT_DB_PASSWORD: ${GIFT_DB_PASSWORD}
    KLADR_DB: ${KLADR_DB}
    KLADR_USER: ${KLADR_USER}
    KLADR_DB_PASSWORD: ${KLADR_DB_PASSWORD}
  volumes:
    - ../mariadb:/var/lib/mysql
    - mariadb-socket:/run/mysqld/
    - ./app/mariadb_conf:/etc/mysql/conf.d
    - ./app/initdb:/docker-entrypoint-initdb.d
  networks:
    - mynet
  user: "root"
  command: --init-file /docker-entrypoint-initdb.d/01-create-db.sh

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

Параметр volumes связывает локальный каталог mariadb, расположенный в каталоге пользователя, запускающего docker-compose, и каталог /var/lib/mysql контейнера, в котором находятся файлы баз данных.

Кроме того, этот параметр предоставляет доступ к базе данных через сокеты, позволяет управлять конфигурацией mariadb через каталог /etc/mysql/conf.d, а также задает каталог app/initdb для хранения скриптов инициализации баз данных:

volumes:
  - ../mariadb:/var/lib/mysql
  - mariadb-socket:/run/mysqld/
  - ./app/mariadb_conf:/etc/mysql/conf.d
  - ./app/initdb:/docker-entrypoint-initdb.d

Когда контейнер применяется для отладки (как в нашем случае), мы можем позволить себе хранить файлы баз данных в каталоге локального компьютера. Напомним, что если хранить их в контейнере, то каждый раз при перезапуске контейнера базы данных придется создавать заново.

Если же контейнер будет использован в рабочем окружении, то каталоги /var/lib/mysql и /etc/mysql/conf.d придется монтировать на сетевое дисковое устройство. Например, подойдет NFS, iSCSI или FC, в зависимости от ваших возможностей и требований к быстродействию.

В файле docker-compose.yml для создаваемой базы данных задается пароль пользователя root (параметр MARIADB_ROOT_PASSWORD). Таким же образом можно создать еще одну базу данных, указав ее пароль. 

В нашем случае нужны сразу три базы данных (для магазина, конфигуратора и KLADR). Для каждой базы требуется задать имя пользователя, пароль, кодировку, а также сортировку.

Для создания баз данных мы сделали отображение каталога /docker-entrypoint-initdb.d на локальный каталог app/initdb с файлом 01-create-db.sh:

#!/bin/bash
mysql -uroot -p${MARIADB_ROOT_PASSWORD} <<MYSQL_SCRIPT
CREATE DATABASE IF NOT EXISTS config CHARACTER SET cp1251 COLLATE cp1251_general_ci; 
CREATE USER IF NOT EXISTS 'config'@'%' IDENTIFIED BY '${CONFIG_DB_PASSWORD}'; 
GRANT ALL PRIVILEGES ON config.* TO 'config'@'%';
CREATE DATABASE IF NOT EXISTS kladr CHARACTER SET cp1251 COLLATE cp1251_general_ci; 
CREATE USER IF NOT EXISTS 'kladr'@'%' IDENTIFIED BY '${KLADR_DB_PASSWORD}'; 
GRANT ALL PRIVILEGES ON kladr.* TO 'kladr'@'%';
CREATE DATABASE IF NOT EXISTS gift CHARACTER SET cp1251 COLLATE cp1251_general_ci; 
CREATE USER IF NOT EXISTS 'gift'@'%' IDENTIFIED BY '${GIFT_DB_PASSWORD}'; 
GRANT ALL PRIVILEGES ON gift.* TO 'gift'@'%';
FLUSH PRIVILEGES;
MYSQL_SCRIPT

Этот файл должен быть доступен для выполнения (используйте chmod + x).

Поле инициализации базы данных файл 01-create-db.sh будет запущен, для чего в файле docker-compose.yml предусмотрены параметры user и command:

user: "root"
command: --init-file /docker-entrypoint-initdb.d/01-create-db.sh

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

Но как задать параметры для mariadb, работающей в контейнере?

Для этого мы создали файл db.cnf и записали его в локальный каталог app/initdb, отображаемый на каталог /etc/mysql/conf.d контейнера:

[mysqld]
skip-name-resolve = 1
key_buffer_size  = 256M
max_allowed_packet = 16M
…
innodb_buffer_pool_size = 256M
innodb_buffer_pool_instances = 1

Если вы привыкли оценивать необходимые изменения в параметрах работы Mariadb с помощью утилиты mysqltuner.pl, то сможете использовать ее и при работе этой СУБД в контейнере.

Прежде всего, определите с помощью команды docker ps имя или идентификатор контейнера mariadb:

$ docker ps | grep mariadb
88ea97e408f0 mariadb "docker-entrypoint.s…" 59 minutes ago Up 59 minutes 3306/tcp                                                                   gift-docker_mariadb_1

В данном случае имя контейнера — gift-docker_mariadb_1.

Далее подключитесь к контейнеру, запустив  bash:

$ docker exec -it gift-docker_mariadb_1 bash
root@88ea97e408f0:/#

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

Теперь загрузите программу mysqltuner.pl и запустите ее:

# apt update && apt install wget -y
# wget http://mysqltuner.pl/ -O mysqltuner.pl
# perl mysqltuner.pl --user root --pass 'passwordroot'

Укажите пароль root базы данных, заданный в параметре MARIADB_ROOT_PASSWORD файла .env.

В результате вы получите необходимые рекомендации. Внесите изменения в файл  app/initdb/db.cnf и перезапустите контейнер.

Если что-то пошло не так, посмотрите журнал контейнера mariadb:

$ docker logs gift-docker_mariadb_1

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

Сервисы adminer и phpmyadmin

Панели управления сервером, такие как ISPmanager и HestiaCP, устанавливают для работы с базами данных приложение phpMyAdmin. Что касается Adminer, то это приложение практически ни в чем не уступает phpMyAdmin, однако его образ занимает в памяти почти в два раза меньше места. 

В файле docker-compose.yml сервис adminer определен следующим образом:    

adminer:
  image: adminer
  restart: always
  ports:
    - 8080:8080
  networks:
    - mynet      

Для тех разработчиков, кто привык к phpMyAdmin, в файле docker-compose.yml подготовлено определение соответствующего сервиса:

phpmyadmin:
  image: phpmyadmin
  restart: always
  ports:
    - 8087:80
  environment:
    - PMA_ARBITRARY=1
  depends_on:
    - mariadb
  networks:
    - mynet

Параметр PMA_ARBITRARY позволяет phpMyAdmin подключаться к любому серверу СУБД, а не только к тем, что определены в его файлах конфигурации.

В рабочем окружении сервисы adminer и phpmyadmin можно запускать только для отладки.

Сервис nginx_proxy

В файле docker-compose.yml мы определили три контейнера с сайтами, доступными на портах 8077, 8078 и 8079. Однако нам нужно сделать так, чтобы эти сайты открывались по своим доменным именам на портах 80 и 443.

Воспользуемся для этого обратным прокси Nginx Proxy Manager на базе готового образа jc21/nginx-proxy-manager, добавив в файл docker-compose.yml сервис nginx_proxy:

nginx_proxy:
  image: 'jc21/nginx-proxy-manager:latest'
  restart: unless-stopped
  ports:
    - '80:80'
    - '81:81'
    - '443:443'
  volumes:
    - ./data:/data
    - ./letsencrypt:/etc/letsencrypt
  networks:
    - nginx_proxy_net

В каталоге data сервис будет хранить базу данных SQLite и файлы конфигурации nginx.

Проект Nginx Proxy Manager представлен на сайте https://nginxproxymanager.com/ (рис. 5).

Рис. 5. Сайт Nginx Proxy Manager
Рис. 5. Сайт Nginx Proxy Manager

Детальное описание этого сервиса выходит за рамки данной статьи, однако в интернете есть различные руководства, дополняющие описание, представленное на сайте. 

Отметим только, что с помощью Web-интерфейса Nginx Proxy Manager вы сможете легко настроить переадресацию для ваших доменных имен на нужные порты. Кроме этого, можно подключить к сайтам сертификаты SSL, а также настроить индивидуальные конфигурации Nginx для каждого из сайтов.

Сервис app

Для сборки контейнеров сайтов мы используем готовый образ debian:stable-slim. Этот образ выбран для лучшей совместимости, так как на виртуальных серверах разработчиков интернет-магазинов и на рабочих серверах установлена ОС Debian 11. 

В файлах Dockerfile каждого из трех упомянутых выше сайтов выполняется установка необходимых программ и модулей Perl, а также другие действия.

В рамках сервиса app работает административный сайт интернет-магазина. Ниже мы привели сокращенный файл Dockerfile для этого сервиса:

FROM debian:stable-slim
RUN useradd -ms /bin/bash gift && useradd -ms /bin/bash core && useradd -ms /bin/bash kladr && \
  DEBIAN_FRONTEND=noninteractive apt-get update && apt-get install -y \
  apt-utils \
  build-essential \
  net-tools \
  curl \
  wget \
  sudo \
  apache2 apache2-utils apache2-suexec-custom libapache2-mod-php \
  git \
  cron \
  liblib-abs-perl libcache-memcached-perl \
…
  libdbd-mysql-perl libmariadb-dev \
  libimage-magick-perl \
  imagemagick \
  libgd-perl \
  php-mysql php-curl php-gd php-json php-zip php && \
  cpan -i App::cpanminus && cpan Net::SMTP::SSL && \
  cpanm HTML::Entities Digest::SHA1 HTML::Template BSON::Time Class::Accessor LWP::UserAgent MIME::Lite Devel::SimpleTrace DBI \
… 
Authen::Htpasswd Apache::Htaccess && \
  apt clean && apt-get autoremove -y && rm -rf /var/lib/apt/lists/* && \
  rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/*

ADD https://cpan.metacpan.org/authors/id/B/BR/BROQ/Switch-Perlish-1.0.5.tar.gz /tmp/
RUN cd /tmp/ && tar -xzf Switch-Perlish-1.0.5.tar.gz && cd Switch-Perlish-1.0.5 && perl Makefile.PL && make install && make clean
…
WORKDIR /app
COPY ./data/itmatrix /app/itmatrix
WORKDIR /app/itmatrix
RUN tar xzf Installation.tar.gz && ./install.sh
WORKDIR /app
RUN rm -rf itmatrix 

COPY ./httpd_conf/apache2.conf /etc/apache2/apache2.conf
COPY ./httpd_conf/000-default.conf /etc/apache2/sites-available/000-default.conf
COPY ./httpd_conf/admin.gift.shop2you.ru.conf /etc/apache2/sites-available/admin.gift.shop2you.ru.conf
RUN ln -s /etc/apache2/sites-available/admin.gift.shop2you.ru.conf /etc/apache2/sites-enabled/

WORKDIR /etc/apache2/mods-enabled 
RUN ln -s ../mods-available/cgi.load cgi.load 
COPY ./httpd_conf/suexec /etc/apache2/suexec/www-data
RUN a2enmod rewrite ssl suexec auth_digest && apachectl configtest

RUN mkdir /usr/local/sphinx/
COPY ./data/sphinx-3.5.1/bin /usr/local/sphinx
COPY ./data/sphinx-3.5.1/bin /usr/bin

RUN crontab -u gift -l | { cat; echo "* * * * * /usr/bin/perl /home/gift/data/www/admin.gift.shop2you.ru/cgi-bin/dbatch.pl"; } | crontab -u gift -
RUN cron

ADD start.sh ./start.sh
RUN chmod +x ./start.sh
EXPOSE 80
CMD ["./start.sh"]

В команде RUN файле Dockerfile сервиса app создаются пользователи для сайтов, устанавливаются утилиты и модули Perl. В частности, устанавливается Apache и все необходимое для него, а также PHP.

Что касается модулей Perl, то они устанавливаются пятью разными способами — из пакетов Debian, с помощью утилит cpan и cpanm, путем скачивания и распаковки архива с сайта cpan.metacpan.org, а также из приватного архива Installation.tar.gz. Некоторые модули устанавливаются без ошибок только из пакетов Debian, а для некоторых возможна установка только из дистрибутивов, загруженных с сайта cpan.metacpan.org (при этом нужна определенная версия модулей).

После завершения установки модулей Perl команда RUN копирует в каталог /etc/apache2/ файлы конфигурации для сайтов и добавляет ссылку на конфигурацию административного сайта в каталог /etc/apache2/sites-enabled/.

На следующем этапе к конфигурации Apache добавляются необходимые модули, а также настраивается файл /etc/apache2/suexec/www-data.

В ваших проектах только что перечисленные действия, скорее всего, будут другими. Они зависят от деталей реализации сайтов.

Для ускорения поиска сайты используют Nginx. В Dockerfile выполняется копирование предварительно загруженных бинарных файлов этого поискового сервера. Также выполняется настройка пакетного задания и запуск cron.

На финальной стадии Dockerfile с помощью команды CMD запускает пакетный файл start.sh.

Перед запуском Apache в файле start.sh устанавливаются пароли пользователей, заданные в файле .env, меняются владельцы файлов в каталогах сайтов и настраивается доступ к каталогу /home/core/, содержащему модули Perl ядра интернет-магазина. Кроме этого, выполняется запуск Sphinx для поиска по базе данных KLADR:

#!/bin/bash
echo "gift:${GIFT_PASSWORD}" | chpasswd
echo "core:${CORE_PASSWORD}" | chpasswd
echo "kladr:${KLDAR_PASSWORD}" | chpasswd
chown -R gift:gift /home/gift
chown -R kladr:kladr /home/kladr
chown -R core:core /home/core
usermod -a -G core gift && chmod -R g+rx /home/core/
/usr/bin/indexer --config /home/kladr/data/kladr/sphinx/sphinx_linux.conf --all
/usr/bin/searchd --config /home/kladr/data/kladr/sphinx/sphinx_linux.conf
apache2ctl -D FOREGROUND

Сервис app_shop

Файл Dockerfile сервиса app_shop аналогичен только что рассмотренному файлу сервиса app. Отличия заключаются в блоках копирования конфигурации Apache:

COPY ./httpd_conf/gift.shop2you.ru.conf /etc/apache2/sites-available/gift.shop2you.ru.conf
RUN ln -s /etc/apache2/sites-available/gift.shop2you.ru.conf /etc/apache2/sites-enabled/

Кроме этого, в этом файле несколько другой состав устанавливаемых модулей Perl.

Содержимое файла start.sh показано ниже:

#!/bin/bash
echo "gift:${GIFT_PASSWORD}" | chpasswd
chown -R gift:gift /home/gift
/usr/bin/indexer --config /home/kladr/data/kladr/sphinx/sphinx_linux.conf --all
/usr/bin/searchd --config /home/kladr/data/kladr/sphinx/sphinx_linux.conf
apache2ctl -D FOREGROUND

Здесь перед запуском Apache устанавливается пароль пользователя gift, устанавливается владелец файлов каталога /home/gift, а также запускается Sphinx для поиска в базе данных KLADR.

Сервис app_config

Для работы сайта конфигурации был подготовлен сервис app_config. В его Dockerfile копируются файлы конфигурации Apache этого сайта:

COPY ./httpd_conf/config.itmatrix.ru.conf /etc/apache2/sites-available/config.itmatrix.ru.conf
RUN ln -s /etc/apache2/sites-available/config.itmatrix.ru.conf /etc/apache2/sites-enabled/

В остальном Dockerfile аналогичен сайту витрины интернет-магазина.

Отладка

В процессе переноса монолита в контейнеры возникала необходимость отладки файлов Dockerfile, конфигурации Apache, Mariadb, а также Sphinx. 

Как уже говорилось в статье, первоначально все три сайта работали в одном контейнере. Это упростило подготовку файла Dockerfile, так как все изменения, например добавление модулей Perl и настройку конфигурации Apache, можно было делать в одном месте.

Затем было принято решение перенести все эти сайты в отдельные контейнеры. Основная причина такого переноса — подготовка для системы оркестрации, такой как Swarm или Kubernetes. Размещение сайтов в отдельных контейнерах необходимо для обеспечения возможности масштабирования. Например, при возрастании нагрузки на сайт витрины интернет-магазина можно будет запустить для витрины дополнительные контейнеры.

Ошибки в docker-compose.yml и Dockerfile

Ошибки в файлах docker-compose.yml и Dockerfile можно увидеть при запуске. Для упрощения запуска подготовлен скрипт start.sh:

#!/bin/bash
sudo chown -R $USER:$USER $HOME/gift-docker
docker-compose up -d –build

Команда chown здесь необходима из-за того, что при инициализации сервиса app происходит смена владельцев для каталогов из app/home.

Остановку контейнеров можно сделать скриптом stop.sh:

#!/bin/bash
docker-compose down

После того как контейнеры запустились, вы можете просмотреть их журналы с помощью команды docker logs:

docker logs gift-docker_app_1

В качестве параметра этой команде нужно передать имя контейнера. Имена всех работающих контейнеров легко определить с помощью команды “docker ps”.

Ошибки в конфигурации Apache

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

Чтобы посмотреть содержимое этих журналов, подключитесь к контейнеру следующим образом:

docker exec -it gift-docker_app_1 bash

Перейдите в каталог /var/log/apache2/. Там находятся все нужные вам журналы:

access.log
error.log
other_vhosts_access.log

Вы можете посмотреть содержимое журналов, например, с помощью команды tail:

# tail -f error.log

После обнаружения ошибки в файле конфигурации Apache перезапустите контейнер. Также в этот журнал могут попадать ошибки, связанные с работой скриптов ваших сайтов.

Ошибки в конфигурации Mariadb

Если сервис mariadb не запускается или с ним возникают проблемы, можно посмотреть журнал Mariadb следующим образом:

docker logs gift-docker_mariadb_1

Эта команда поможет вам при настройке файла конфигурации Mariadb.

Итоги

В результате проведенных работ были достигнуты все поставленные цели, а также намечен план дальнейших действий по переводу SAAS-сервис интернет-магазинов в Docker.

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

Отталкиваясь от достигнутых результатов, запланированы работы по разворачиванию тестового интернет-магазина сначала в Swarm, а затем в Kubernetes. На первом этапе будет подключено внешнее сетевое хранилище для размещения баз данных и файлов сайтов.

Если все работы завершатся успехом, планируется переход на микро-сервисную архитектуру. Это, однако, потребует внесения масштабных изменений в архитектуру, а также в исходные коды проекта.

Автор статьи: Александр Фролов.


НЛО прилетело и оставило здесь промокод для читателей нашего блога:

— 15% на все тарифы VDS (кроме тарифа Прогрев) — HABRFIRSTVDS.

Комментарии (9)


  1. Iron_Butterfly
    00.00.0000 00:00
    -1

    Правильное название панели ispmanager, а не ISPmanager: https://www.ispmanager.ru/materials


    1. AlexandreFrolov
      00.00.0000 00:00

      Панель меняла свое название, вот, например, тут она называлась ISPmanager:

      https://habr.com/ru/company/ruvds/blog/472044/
      https://www.isplicense.ru/services/ispsystem/ispmanager4/
      https://www.reg.ru/hosting/ispmanager?utm_source=google.com&utm_medium=organic&utm_campaign=google.com&utm_referrer=google.com

      Но при использовании Docker и систем оркестрации, таких как Swarm и Kubernetes, а также инструмента Ansible панели управления серверами не устанавливаются на узлы кластера. Просто незачем.

      Я читал, что в ispmanager будут встроены средства управления контейнерами Docker:

      https://docs.ispmanager.ru/ispmanager6-lite/docker

      Но у меня пока нет ясности, как они будут соотноситься (если будут) с системами оркестрации.

      Установить Docker на сервер и запустить docker-compose нетрудно и без панели. Но системы оркестрации позволяют реализовать основные преимущества контейнеров, такие как отказоустойчивость и горизонтальное масштабирование. Вот тут как раз интересно, что сможет предложить ispmanager.


      1. Iron_Butterfly
        00.00.0000 00:00

        С мая 2022 года, когда компания стала самостоятельной, она называется ispmanager. Старое название ISPmanager неверное.

        Поддержка докера уже реализована. Там все на уровне управления контейнерами, образами. Монтирование томов, директорий, маппинг портов. Все из интерфейса. Все, что чаще всего востребовано обычными пользователями.


        1. AlexandreFrolov
          00.00.0000 00:00

          Правильно ли я понял, что контейнеры работают только в рамках одного хоста, где установлена панель, там же находится репозиторий, и никакие средстве оркестрации не используются, а интеграция со Swarm и Kubernetes не планируется?

          В этом случае, на мой взгляд, теряется основное преимущество контейнеров — реализация отказоустойчивости и масштабирования для высоконагруженных проектов.

          Что же касается монтирования томов и маппинга портов, то на мой взгляд, это куда удобнее делать через файл docker-compose.yml. Этот файл потом можно скачать из registry (GitHib, Harbor и т.п.) и развернуть перечисленные в нем контейнеры на любом хосте одной командой.

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


          1. Iron_Butterfly
            00.00.0000 00:00

            Да, все верно вы поняли.

            Реальные кейсы - например, поднятие контейнера с Redis для кэширования WordPress сайта. Или поднятие контейнера с MongoDB опять же для нужд сайта.

            Докер файлом или docker-compose.yml никто не мешает пользоваться в командной строке хоста.


      1. Iron_Butterfly
        00.00.0000 00:00

        Кстати, фича реквест за интеграцию с Kubernetis вот тут имеется, можете поддержать: https://www.ispmanager.ru/community/feature-request-7


        1. AlexandreFrolov
          00.00.0000 00:00
          +1

          Да, поддержал


  1. cadmi
    00.00.0000 00:00

    После того как контейнеры запустились, вы можете просмотреть их журналы с помощью команды docker logs:

    docker logs gift-docker_app_1

    В качестве параметра этой команде нужно передать имя контейнера. Имена всех работающих контейнеров легко определить с помощью команды “docker ps”.

    Господи, у вас же compose, к чему все эти приседания с ps и суффиксами _1 ?

    Ну и набирайте сразу docker compose logs -f app (если cli свежий, а если старьё, то docker-compose logs -f app)


    1. AlexandreFrolov
      00.00.0000 00:00

      Let’s look at the logs using the docker compose logs -f command. You’ll see the logs from each of the services interleaved into a single stream.

      Правильно ли я понимаю, что логи от всех контейнеров попадут в один поток? А нужно ли так? Все же там несколько контейнеров.