Продукты
VK Cloud

Запускаем WordPress c MySQL в Kubernetes

19 ноября 2024 г.
_blog_head_102.png

Едва ли в мире есть более популярный движок для создания блогов и небольших сайтов, чем WordPress — около 26% всех веб-сайтов в интернете созданы на его основе. Многие хостинги включают его в список своих сервисов, а официальный docker-образ был скачан более 10 миллионов раз и является одним из 25 самых популярных образов на Docker Hub. Запустив его не просто в докере, а в Kubernetes, мы можем создать масштабируемую платформу для блога или веб-сайта: при росте нагрузки мы просто масштабируем количество реплик приложения.


Планируем архитектуру

Теория: stateless- и stateful-приложения

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

Stateless-приложения. Для примера возьмем два приложения — «корзину» в интернет-магазине и наш блог на WordPress. Когда мы добавляем товары в корзину (но не переходим к оплате заказа), она сохраняет все данные в cookies на нашей стороне. По завершении сессии приложение сбрасывает все данные и не хранит ничего на сервере. Когда мы снова заходим в интернет-магазин, информация о товарах считывается из нашей системы и снова отображается в корзине. Чтобы работать с покупателем, корзине не нужно получать никакую информацию с бэкенда — роль хранилища выполняет система покупателя. Такие приложения называются stateless, поскольку они не хранят информацию о своем состоянии.

Stateful-приложения. Теперь посмотрим на наш блог. Когда мы создаем новый пост или оставляем комментарий, нужно чтобы любой пользователь в любое время мог получить к ним доступ. Поэтому все данные должны храниться на стороне сервера. Когда мы переходим на ту или иную страницу, движок блога загружает имеющуюся информацию из базы данных, а если мы что-то меняем на странице — записывает изменения на сервер. Такие приложения, которые должны хранить информацию о своем состоянии для корректной работы, называются stateful. WordPress относится именно к таким приложениям.

Как будем хранить данные?

Kubernetes поддерживает оба типа приложений, но принцип их запуска различен. Stateless-приложения не требуют выделения хранилища, но наш блог — stateful, и для него нужно выделить директорию для записи и чтения данных о состоянии. В нашей инсталляции WordPress мы будем хранить данные приложения в базе данных, конкретно — в MySQL. Для запуска WordPress и MySQL потребуется выделить две директории: одну для системных данных WordPress, другую для данных сайта и информации о пользователях, хранимых в MySQL.

С этим разобрались. Но где хранить все эти данные? Рассмотрим два варианта.

  • WordPress и MySQL могут хранить всю информацию внутри контейнеров, в которых они запущены. Это обеспечит сохранность данных на какое-то время, но при перезапуске пода (pod) все данные будут утеряны (по эффекту перезапустить под — это всё равно что удалить все данные на сервере). Этот вариант подходит, если вы хотите быстро протестировать работу приложений, но его точно не стоит использовать для работы в production.
  • Можно хранить данные приложений вне контейнера, используя внешнее хранилище на сервере. В этом случае перезапуск пода никак не повлияет на сохранность данных и, кроме того, мы сможем делать бэкапы содержимого. Именно этот вариант мы и выберем.

Что сделать, чтобы использовать внешнее хранилище?

Чтобы работать с хранилищем в Kubernetes и, в частности, смонтировать в него WordPress и MySQL, нам необходимо сделать две вещи:
  1. настроить утилиту для управления хранилищем на сервере хранилища,
  2. обеспечить взаимодействие между этой утилитой и приложениями в Kubernetes.
Kubernetes поддерживает множество утилит для работы с хранилищем: Ceph, GlusterFS, iSCSI, NFS и др. Список всех поддерживаемых типов можно найти в официальной документации Kubernetes. Для простоты в данной статье мы будем использовать NFS.

Для работы с хранилищем Kubernetes использует абстракции PersistentVolume и PersistentVolumeClaim. Что они из себя представляют? Объект PersistentVolume (PV) управляет постоянным хранилищем в кластере. Он поддерживает различные типы внешнего хранилища, например NFS, GlusterFS, Ceph и многие другие. PersistentVolumeClaim (PVC) — это запрос на использование хранилища для конкретного приложения. В упрощенном виде их взаимодействие можно изобразить следующим образом: приложение обращается к PersistentVolumeClaim, который запрашивает хранилище у PersistentVolume, который, в свою очередь, монтирует данные приложений в директорию на сервере хранилища. Чтобы не создавать PersistentVolume вручную, мы установим nfs-client-provisioner — приложение, которое автоматически создает PV по требованию PVC.

Как поды обращаются к физическому хранилищу

pod-scheme.png

Для работы подов с хранилищами в архитектуре Kubernetes выделена абстракция Volume. Под обращается к нему как хранилищу без учёта того, как это хранилище реализовано. С помощью volume решается две проблемы:

  • сохранение данных при падении контейнера (пода),
  • возможность для двух и более подов работать с общими файлами.
PersistentVolume (PV) — это Volume для работы подов с физическим хранилищем через API. PV представляет собой объект в Kubernetes, который содержит в себе информацию, как монтировать физическое хранилище и его метаданные.

PersistentVolumeClaim (PVC) — это запрос на создание PV и предоставление Storage.


План развёртывания

В процессе подготовки NFS и установки WordPress и MySQL мы выполним следующие шаги:
  1. Установка утилиты NFS для управления хранилищем.
  2. Установка nfs-client-provisioner для автоматического выделения хранилища в Kubernetes.
  3. Создание PersistentVolumeClaim для запроса места в хранилище.
  4. Создание Secret для MySQL для доступа к MySQL.
  5. Запуск MySQL.
  6. Запуск WordPress.

Подготовка NFS

Для работы с NFS необходимо настроить NFS-сервер на отдельной машине и NFS-клиенты на машинах с Kubernetes.

Зайдем на машину-сервер, которую будем использовать для хранения данных, установим на нее файервол и откроем порты TCP:111, UDP: 111, TCP:2049, UDP:2049. В нашем примере мы используем CenOS 7.5. Для этого выполним команды:

sudo yum install firewalld -y sudo systemctl start firewalld sudo systemctl enable firewalld sudo firewall-cmd --zone=public --add-port=111/tcp --permanent sudo firewall-cmd --zone=public --add-port=111/udp --permanent sudo firewall-cmd --zone=public --add-port=2049/tcp --permanent sudo firewall-cmd --zone=public --add-port=2049/udp --permanent sudo firewall-cmd --reload

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

После этого установим NFS-utils для работы с хранилищем:

sudo yum install nfs-utils

Создадим директорию для монтирования данных на диск (/nfs) и запустим nfs-server:

sudo mkdir -p /nfs sudo chmod -R 777 /nfs sudo systemctl enable rpcbind sudo systemctl enable nfs-server sudo systemctl enable nfs-lock sudo systemctl enable nfs-idmap sudo systemctl start rpcbind sudo systemctl start nfs-server sudo systemctl start nfs-lock sudo systemctl start nfs-idmap

Теперь добавим директорию NFS в частную подсеть Kubernetes:

sudo vi /etc/exports

Добавим следующую строку в файл и сохраним его:

/nfs X.X.X.X/X(rw,sync,no_root_squash)
где X.X.X.X/X — адрес подсети, например — 172.31.32.0/24.

Теперь применим конфигурацию, выполнив команду:

exportfs -a

Перезапустим nfs:

sudo systemctl restart nfs-server

Зайдем на остальные машины, установим утилиты для работы с nfs и запустим сервисы:

sudo yum install nfs-utils systemctl enable rpcbind systemctl enable nfs-server systemctl enable nfs-lock systemctl enable nfs-idmap systemctl start rpcbind systemctl start nfs-server systemctl start nfs-lock systemctl start nfs-idmap

Готово!


Установка nfs-client-provisioner

Чтобы не монтировать PersistentVolume к хранилищу вручную для каждого PVC, установим nfs-client provisioner на каждой машине-клиенте. Для установки будем использовать Helm. Выполним команду:
helm install --set nfs.server=Y.Y.Y.Y --set nfs.path=/nfs stable/nfs-client-provisioner
где Y.Y.Y.Y — внутренний адрес NFS-сервера, например — 172.31.32.1.

Сделаем nfs типом хранилища (StorageClass) по умолчанию:

kubectl patch storageclass nfs-client -p '{"metadata": {"annotations":{"storageclass.kubernetes.io/is-default-class":"true"}}}'


Создаем PersistentVolumeClaim

Чтобы хранить данные WordPress и MySQL в NFS, необходимо запросить часть хранилища (в данном случае по 1 GiB) для каждого приложения через PVC. Благодаря запущенному nfs-provisioner PV будут предоставлены автоматически. Создадим файлы pvc-wordpress.yaml и pvc-mysql.yaml со следующим содержанием:

pvc-wordpress.yaml:

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: wp-pv-claim labels: app: wordpress spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi

и pvc-mysql.yaml:

apiVersion: v1 kind: PersistentVolumeClaim metadata: name: claim-db-wordpress-one labels: app: wordpress-one spec: accessModes: - ReadWriteMany resources: requests: storage: 1Gi

Теперь создадим объекты PVC:

kubectl create -f pvc-wordpress.yaml kubectl create -f pvc-mysql.yaml

Готово, теперь можно переходить к запуску MySQL и WordPress.


Создаем secret для MySQL

Secret — объект, который хранит какую-либо конфиденциальную информацию типа паролей или ключей. Данные, хранимые в Secret, должны быть закодированы по стандарту base64. Сейчас мы создадим секрет c паролем для пользователя admin в MySQL, зададим создание случайного пароля:

openssl rand -base64 32 | base64

На выходе получим зашифрованный пароль: QlFhZzlEOWF6c3JoMTU4Rjh2U3FDVUdHNE9KSm4xMVBtVDV1Rno1Szkvbz0K

Теперь нужно создать secret.yaml для MySQL и WordPress, на который будет ссылаться MYSQL_ROOT_PASSWORD в environment деплойментов:

apiVersion: v1 kind: Secret metadata: name: mysql-pass type: Opaque data: password: QlFhZzlEOWF6c3JoMTU4Rjh2U3FDVUdHNE9KSm4xMVBtVDV1Rno1Szkvbz0K

Выполним команду для создания секрета из файла:

kubectl create -f secret.yaml


Запускаем MySQL

Теперь создадим mysql-deploy.yaml, в котором опишем параметры запуска деплоймента и сервиса для MySQL:
apiVersion: v1 kind: Service metadata: name: wordpress-db-one labels: app: wordpress spec: ports: - port: 3306 selector: app: wordpress tier: mysql clusterIP: None --- apiVersion: apps/v1 kind: Deployment metadata: name: wordpress-db-one labels: app: wordpress spec: selector: matchLabels: app: wordpress tier: mysql strategy: type: Recreate template: metadata: labels: app: wordpress tier: mysql spec: containers: - image: mysql:5.7 name: mysql args: - "--ignore-db-dir=lost+found" env: - name: MYSQL_ROOT_PASSWORD valueFrom: secretKeyRef: name: mysql-pass key: password ports: - containerPort: 3306 name: mysql volumeMounts: - name: db-storage mountPath: /var/lib/mysql volumes: - name: db-storage persistentVolumeClaim: claimName: claim-db-wordpress-one

Файл состоит из 2 файлов конфигураций: Service открывает порт 3306 для всех контейнеров, у которых прописаны лейблы app:wordpress и tier:mysql. Часть Deployment описывает параметры создания деплоймента и specs контейнера с MySQL:

  • используется образ mysql:5.7
  • указываются лейблы app:wordpress и tier:frontend, прописанные в Service для WordPress.
  • в качестве переменной MYSQL_ROOT_PASSWORD используется password из созданного секрета.
  • открывается порт 3306
  • Директория /var/lib/mysql внутри контейнера монтируется в Volume через PVC с названием claim-db-wordpress-one.
Создадим деплоймент и сервис из файла:
kubectl create -f mysql-deploy.yaml

Запускаем WordPress

Процесс запуска WordPress аналогичен описанному выше. Создадим wordpress-deploy.yaml:
apiVersion: v1 kind: Service metadata: name: wordpress labels: app: wordpress spec: ports: - port: 80 selector: app: wordpress tier: frontend type: LoadBalancer --- apiVersion: apps/v1 kind: Deployment metadata: name: wordpress labels: app: wordpress spec: selector: matchLabels: app: wordpress tier: frontend strategy: type: Recreate template: metadata: labels: app: wordpress tier: frontend spec: containers: - image: wordpress:4.8-apache # latest: wordpress:4.9-php7.2-apache name: wordpress env: - name: WORDPRESS_DB_HOST value: wordpress-db-one # This one is from Service - name: WORDPRESS_DB_PASSWORD valueFrom: secretKeyRef: name: mysql-pass key: password ports: - containerPort: 80 name: wordpress volumeMounts: - name: wordpress-persistent-storage mountPath: /var/www/html volumes: - name: wordpress-persistent-storage persistentVolumeClaim: claimName: wp-pv-claim

Как и в предыдущем случае, файл состоит из 2 частей. Service пробрасывает 80-й порт контейнера на внешний IP:порт машины для всех контейнеров с лейблами app:wordpress и tier:frontend. Deployment содержит следующие specs контейнера WordPress:

  • запускаемый образ с DockerHub - wordpress:4.8-apache
  • указаны лейблы app:wordpress и tier:frontend, используемые в Service.
  • указаны переменные WORDPRESS_DB_HOST (внутренний hostname MySQL) и MYSQL_ROOT_PASSWORD, использующий значение из созданного секрета
  • открыт 80-й порт.
  • Директория /var/www/html внутри контейнера монтируется в Volume через PVC с названием wp-pv-claim.
Создадим Deployment и Service из файла wordpress-deploy.yaml:

kubectl create -f wordpress-deploy.yaml

Убедимся, что все работает:

kubectl get po

kubectl-get-po-300x70.png


Откроем WordPress

Теперь откроем WordPress в браузере. Для этого получим порт приложения, выполнив команду kubectl get svc:

kubectl-get-svc-300x36.png

Перейдем по адресу: внешний_IP:порт_сервиса (в нашем случае 32095).

wp-lang.png

Перед нами появился экран выбора языка — WordPress готов к работе.


Проверка

Теперь убедимся, что мы верно настроили работу WordPress и MySQL с хранилищем. В панели управления вордпресса укажем название блога («WordPress в Kubernetes») и перейдем на главную страницу по адресу внешний IP:порт_сервиса. На странице появилось название блога.

wp-blog.jpg

Теперь перезапустим поды WordPress и MySQL и проверим, сохранились ли данные приложений.

kubectl get po

kubectl-get-po-2.png

Удалим поды и подождем, пока будут созданы новые:

kubectl delete po название_пода1 название_пода2

kubectl-delete-po-300x21.png

Снова зайдем на главную страницу WordPress.

wp-blog.jpg

Картинка та же, так что при удалении и восстановлении пода всё сохранилось
После перезапуска подов оба приложения взяли сохраненные данные из NFS-хранилища — значит, мы все настроили правильно!

Заключение

С Kubernetes мы можем быстро развернуть WordPress с MySQL и смонтировать их данные в постоянное хранилище. Мы использовали NFS из-за простоты настройки, однако для более надежного production рекомендуется использовать более сложные распределенные системы хранения, например GlusterFS или Ceph.

Тем не менее, NFS отлично подходит для небольших проектов. Если в какой-то момент что-то пойдет не так и WordPress или MySQL «упадет» или перезапустится, мы не потеряем контент благодаря тому, что они хранятся не внутри контейнеров, а во внешнем хранилище. Главное — не забывать регулярно делать бэкапы.

Теги: разработка, kubernetes, базы данных, mysql

Почитать по теме

_blog_head_74.png
26 ноября

Что такое методология DevOps: подробное руководство о том, как построить работу IT-отдела

_blog_head_197.png
26 ноября

Чем на самом деле занимается DevOps-инженер и зачем вам его нанимать

40+ готовых сервисов