В этой статье мы расскажем о переносе одного из компонентов монолитного 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.