Я работаю в компании Initlab. Мы специализируемся на разработке и поддержке Drupal проектов. У нас есть продукт для быстрого создания Ecommerce решений, основанный на Drupal. В 2019 году мы начали решать задачу построения масштабируемой и отказоустойчивой инфраструктуры для нашего продукта.
В качестве платформы для инфраструктуры был выбран Kubernetes. В этой статье разберем особенности Drupal и подходы, с помощью которых можно запускать Drupal в кластере Kubernetes.
Часть статьи является переводом статьи Джеффа Герлинга. Со своей стороны добавили более подробное описание вариантов запуска баз данных и отказоустойчивого хранилища в кластере.
Drupal и Docker
Drupal и Docker могут работать вместе и хорошим примером тут служат локальные среды разработки, построенные вокруг Docker, такие как Docksal, Ddev, Lando и DrupalVM. Но локальные среды разработки, в которых кодовая база Drupal в основном монтируется, как том в контейнер Docker, сильно отличаются от рабочей среды, где цель состоит в том, чтобы «вместить» как можно большую часть кода в stateless контейнер.
В Kubernetes все, или почти все приложения вынуждены работать, как 12-факторное приложение и при развертывании Drupal в Kubernetes необходимо это учитывать.
Что такое 12 Factor Apps?
На базовом уровне 12-факторное приложение — это приложение, которое легко развертывается в масштабируемой, широкодоступной и облачной среде.
Многим приложениям нужны базы данных для учетных записей пользователей, общая файловая система для загруженных или сгенерированных файлов, совместно используемых несколькими экземплярами, а также другие элементы, способные затруднить развертывание приложений масштабируемым и гибким способом.
Но 12-факторное приложение — это приложение, которое облегчает развертывание и управление в масштабируемой среде:
- Сохраняет всю свою конфигурацию в декларативном формате, так что нет никаких ручных или неавтоматизированных шагов установки.
- Не слишком сильно связано с одним типом операционной системы или со сложностями настройки алгоритмического языка и среды выполнения.
- Может масштабироваться от одного до нескольких реплик с минимальным (в идеале нулевым) вмешательством.
- Практически одинаково как в производственной, так и в непроизводственной среде.
- 12-факторному приложению могут потребоваться базы данных, общие файловые системы и другие элементы, но в идеале оно может работать так, что сводит к минимуму затраты при работе с этими элементами.
Условия размещения Drupal в Kubernetes
Чтобы превратить приложение или веб-сайт на Drupal в более «безликое» 12-факторное приложение, необходимо соблюсти пять главных условий:
- База данных должна быть постоянной. Данные должны сохраняться между запусками контейнеров.
- Каталог файлов Drupal должен быть постоянным, масштабируемым и общим для всех веб-контейнеров.
- Установка Drupal и агрегация CSS и JS должны иметь возможность сохранять данные в каталогах с файлами Drupal-сайта.
- Настройки Drupal settings.php и другие важные настройки должны настраиваться установкой значений переменных среды.
- Задание cron в Drupal следует запускать через Drush, чтобы иметь возможность запускать его наиболее эффективным и масштабируемым образом.
Выполнение условий размещения Drupal в Kubernetes
1. База данных
Существует несколько способов решить задачу сохранения данных при перезапуске контейнеров в Kubernetes. Эту задачу необходимо решить любому веб-сервису, использующему базу данных. Рассмотрим эти варианты.
Управляемый сервис баз данных
Самый простой и наиболее часто применяемый способ использование сервиса управляемых баз данных: AWS RDS Aurora или Cloud SQL в Google Cloud. В этом случае, мы можем подключить свой сайт на Drupal напрямую к управляемой базе данных.
Недостатком данного подхода является то, что он может быть дороже, чем решение с самостоятельным управлением, и не всегда применим из-за особенностей инфраструктуры. Преимущество управляемых баз данных в том что это надежное решение и не требуется тратить ресурсы на администрирование.
Docker образ mysql
Использовать базы данных внутри кластера Kubernetes можно, организовав отдельный контейнер для данных с помощью Docker. Это решение по стоимости дешевле, сохраняет переносимость и хорошо работает с небольшими сайтами и приложениями.
Но если необходимо иметь высокодоступные базы данных, реплицируемые на запись/чтение данных внутри Kubernetes, потребуется сделать гораздо больше работы.
MySQL Operator
Проект MySQL Operator предназначен для упрощения процесса размещения базы в кластере. Не работает в последних версиях kubernetes, начиная с 1.16. Кроме того, в каждой версии Kubernetes могут вноситься значительные изменения в API, из-за чего проект требуется адаптировать к новым версиям, что разработчики не всегда успевают делать.
Percona XtraDB Cluster Operator
Альтернатива MySQL Operator — это решение Percona XtraDB Cluster Operator. Percona XtraDB Cluster объединяет в себе Percona Server для MySQL, работающий с механизмом хранения XtraDB, и Percona XtraBackup с библиотекой Galera, для обеспечения синхронной multi-master репликации.
Балансировка нагрузки выполняется с помощью демона ProxySQL, который принимает входящий трафик от клиентов MySQL и перенаправляет его на серверы MySQL. ProxySQL эффективно управляет рабочей нагрузкой базы данных по сравнению с другими балансировщиками нагрузки, которые не поддерживают SQL.
Helm chart для MariaDB
Более простым для запуска базы данных в кластере Kubernetes может быть использование Helm chart для MariaDB, которое позволяет развернуть в Kubernetes реплицированный кластер MariaDB. В данном кластере развертывается один master, а остальные — slave контейнеры, с которых возможно чтение данных. В отличии от multi-master репликации, при отказе master контейнера будет остановке сервиса, на время запуска нового «мастера».
PostgreSQL
Если рассматривать СУБД PostgreSQL, то существует проект Stolon — облачный менеджер для обеспечения высокой доступности кластера PostgreSQL.
При деплое баз данных под MySQL или PostgreSQL, в Kubernetes, нужно где-то сохранять файлы с фактическими данных. Наиболее распространенный способ — подключить Kubernetes PV (Persistent Volume) к контейнеру MySQL.
Итак, придется решить проблему запуска постоянной базы данных в кластере Kubernetes, если требуется использовать полностью облачную среду.
2. Каталог файлов Drupal
Drupal должен иметь возможность записи в файловую систему внутри кодовой базы в нескольких случаях:
- Когда во время установки Drupal перезаписывается файл settings.php.
- Когда во время установки Drupal не проходит предварительную проверку установщика, если каталог сайта (например sites/default) не доступен для записи.
- Если при создании кеша тем Drupal требуется доступ на запись в каталог публичных файлов, чтобы он мог хранить сгенерированные файлы Twig и PHP.
- Если используется агрегация CSS и JS, Drupal требуется доступ на запись в каталог публичных файлов, чтобы он мог хранить сгенерированные файлы js и css.
Существует несколько вариантов решения этих задач.
NFS
Самое простое решение всех этих задач, позволяющее Drupal масштабировать и запускать более одного экземпляра Drupal — это использовать NFS для папки файлов Drupal (например sites/default/files). Однако легче — не значит лучше. У NFS есть свои особенности:
- NFS имеет проблемы с производительностью блокировок файлов при нагрузках.
- NFS может быть медленным для определенных типов доступа к файлам.
Облачное хранилище совместимое с S3
Можно использовать сервис хранения типа Amazon S3 в качестве общедоступной файловой системы для своего Drupal сайта, что снизит затраты на поддержку хранилища и повысит его надежность. Для интеграции S3 хранилища с drupal можно использовать, например, модуль S3FS.
Распределенные файловые системы
Также можно использовать распределенные файловые системы, такие как Gluster или Ceph. Однако тут увеличивается сложность поддержки таких решений, которые можно решить в Kubernetes с помощью проекта rook.
Проект Rook
Rook "превращает" распределенные системы хранения в самоуправляемые, самомасштабируемые, самовосстанавливающиеся сервисы хранения. Он автоматизирует задачи администратора хранилища: развертывание, загрузку, настройку, предоставление, масштабирование, обновление, миграцию, аварийное восстановление, мониторинг и управление ресурсами.
Rook использует возможности платформы Kubernetes для предоставления своих услуг: облачное управление контейнерами, планирование и оркестрация.
Rook управляет несколькими решениями для хранения, предоставляя общую платформу для всех из них. Rook гарантирует, что все они будут хорошо работать в Kubernetes:
- Ceph
- EdgeFS
- CockroachDB
- Cassandra
- NFS
- Yugabyte DB
3. Установка Drupal
Сделать файл settings.php доступным для записи во время установки более сложная задача, особенно если вам нужно установить Drupal, а затем развернуть новый образ контейнера ( что произойдет со всеми записанными значениями settings.php? Они будут удалены при перезапуске контейнера! ). Поэтому решение данной задачи получило отдельный пункт описания особенностей запуска Drupal в Kubernetes.
Пользователи могут устанавливать Drupal через веб-интерфейс мастера установки или автоматически через Drush. В любом случае Drupal во время установки, если еще не установлены все правильные переменные в settings.php, добавляет некоторые дополнительные конфигурации к файлу sites/default/settings.php или создает этот файл, если его не существует.
Следующие параметры должны быть предварительно установлены в settings.php, чтобы Drupal автоматически пропускал экран установки и не требовал записи дополнительных конфигураций в файл settings.php:
// Config sync directory.
$config_directories['sync'] = '../config/sync';
// Hash salt.
$settings['hash_salt'] = getenv('DRUPAL_HASH_SALT');
// Disallow access to update.php by anonymous users.
$settings['update_free_access'] = FALSE;
// Other helpful settings.
$settings['container_yamls'][] = $app_root . '/' . $site_path . '/services.yml';
// Database connection.
$databases['default']['default'] = [
'database' => getenv('DRUPAL_DATABASE_NAME'),
'username' => getenv('DRUPAL_DATABASE_USERNAME'),
'password' => getenv('DRUPAL_DATABASE_PASSWORD'),
'prefix' => '',
'host' => getenv('DRUPAL_DATABASE_HOST'),
'port' => getenv('DRUPAL_DATABASE_PORT'),
'namespace' => 'Drupal\Core\Database\Driver\mysql',
'driver' => 'mysql',
]
В данном примере используются переменные окружения (например: DRUPAL_HASH_SALT) с функцией PHP getenv() вместо "жестко" заданных значений в файле. Это позволяет контролировать настройки, которые Drupal использует как при установки, так и для новых контейнеров, которые запускаются после установки Drupal.
В файле Docker Compose, чтобы передать переменные, указываются директива environment для контейнера web/Drupal следующим образом:
services:
drupal:
image: drupal:latest
container_name: drupal
environment:
DRUPAL_DATABASE_HOST: 'mysql'
[...]
DRUPAL_HASH_SALT: 'fe918c992fb1bcfa01f32303c8b21f3d0a0'
А в Kubernetes, когда создаете Drupal Deployment, передать переменные среды можно с помощью envFrom и ConfigMap (предпочтительно), также можно напрямую передавать переменные среды в спецификации контейнера:
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: drupal
[...]
spec:
template:
[...]
spec:
containers:
- image: {{ drupal_docker_image }}
name: drupal
envFrom:
- configMapRef:
name: drupal-config
env:
- name: DRUPAL_DATABASE_PASSWORD
valueFrom:
secretKeyRef:
name: mysql-pass
key: drupal-mysql-pass
Приведенный выше фрагмент манифеста предполагает, что уже в kubernetes есть ConfigMap с именем drupal-config содержащей все DRUPAL_* переменные, кроме пароля к базе данных, а также секрет, названный mysql-pass, с паролем в ключе drupal-mysql-pass.
На этом этапе, если создаете новую среду с помощью Docker Compose локально или в кластере Kubernetes, можете перейти к установщику пользовательского интерфейса или использовать Drush. При этом не требуется вводить учетные данные базы данных, поскольку эти и другие необходимые переменные уже предварительно настроены.
4. Конфигурация Drupal, управляемая средой
Также может быть ряд других параметров конфигурации, которыми требуется управлять, используя переменные среды. Drupal еще не имеет механизма для этого, поэтому необходимо встроить такие настройки в код, используя getenv().
5. Выполнение заданий Cron Drupal в Kubernetes
Чтобы отслеживать любые проблемы с cron отдельно от обычного веб-трафика и быть уверенным, что выполнение заданий cron не мешает обычным посещениям страниц следует запускать cron через внешний процесс вместо того, чтобы запускать его при посещениях интерфейсной страницы (именно так Drupal по умолчанию запускает cron).
Внутри Kubernetes не нужно настраивать crontab ни на один из узлов Kubernetes, работающих с сайтами Drupal. И хотя может быть внешняя система, например Jenkins, запускающая задания cron на Drupal сайте, гораздо проще просто запускать Cron Drupal как Kubernetes CronJob, в идеале в том же пространстве имен Kubernetes, что и Drupal.
Самый надежный способ запуска Drupal cron — через Drush, но запуск отдельного контейнера Drush через CronJob означает, что CronJob должен запланировать сложный контейнер, работающий как минимум на PHP и Drush. Поды CronJob должны быть максимально легкими, чтобы их можно было планировать на любом системном узле и запускать очень быстро (даже если ваш контейнер Drupal еще не был загружен на этот конкретный узел Kubernetes).
Cron Drupal поддерживает запуск путем посещения URL-адреса, и это самый простой вариант для работы с Kubernetes. Пример настройки CronJob:
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
name: drupal-cron
namespace: my-drupal-site
spec:
schedule: "*/1 * * * *"
concurrencyPolicy: Forbid
jobTemplate:
spec:
template:
spec:
containers:
- name: drupal-cron
image: byrnedo/alpine-curl:0.1
args:
- -s
- http://www.my-drupal-site.com/cron/cron-url-token
restartPolicy: OnFailure
URL drupal_cron_url зависит от сайта и можно его найти, посетив страницу /admin/config/system/cron. Убедитесь, что в настройках cron для «Run cron every» установлено значение «Never», чтобы cron запускался только через CronJob Kubernetes.
Можно использовать byrnedo/alpine-curl образ докера, который является чрезвычайно легким — всего 5 или 6 МБ — так как он основан на Alpine Linux. Большинство других контейнеров, которые есть на базе Ubuntu или Debian, имеют размер, по крайней мере, 30-40 МБ (так что они будут использовать гораздо больше времени, чтобы загрузить первый раз, когда CronJob запускается на новом узле).
Заключение
Мы рассмотрели особенности размещения Drupal проектов в Kubernetes. В продолжении этой статьи детально рассмотрим как мы запустили наш продукт в кластере Kubernetes.