Семь советов по добавлению второго сервера приложения

Источник: «7 Tips for Adding a Second Server to your App»
Добавление второго сервера к вашему приложению может стать отличным способом улучшить его производительность и/или повысить надёжность. Однако при добавлении второго сервера необходимо учитывать несколько моментов.

В этой статье мы обсудим ключевые моменты, которые необходимо учитывать при добавлении дополнительного сервера приложения. В качестве примера мы будем использовать Laravel, размещённый в Laravel Forge, но эти концепции могут быть применены к любому типу приложений, не ограничиваясь языком PHP.

Текущая инфраструктура

Для начала, дабы убедиться, что мы говорим на одном языке, вот схема текущей инфраструктуры. Приложение работает на сервере, созданном в Laravel Forge и запущенно на AWS.

1. Балансировщик нагрузки

Первое, что вам понадобится, — это балансировщик нагрузки. Это будет точка входа вашего приложения, то есть вы будете указывать DNS вашего домена на балансировщик нагрузки, а не непосредственно на сервер. Задача балансировщика нагрузки, как вы уже догадались, заключается в балансировке входящих запросов между всеми здоровыми и зарегистрированными серверами.

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

С этого момента каждый раз, когда мы будем упоминать App Server, это будет относиться к отдельному серверу, на котором запущено наше приложение Laravel.

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

Мы рекомендуем использовать балансировщик нагрузки приложения, обеспечивающий более надёжную функциональность в будущем, если она вам понадобится. Балансировщики нагрузки приложений могут направлять трафик на определённые серверы в зависимости от запрашиваемого URL и даже направлять запросы к нескольким приложениям. На данный момент мы будем балансировать трафик равномерно, используя метод "round robin".

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

2. База данных (MySQL), кэш и очередь (Redis)

В настоящее время на одном сервере работает наше приложение, локальные экземпляры MySQL и Redis. Что произойдёт, если второй сервер будет подключён к нашему балансировщику нагрузки?

Наличие нескольких источников данных для наших баз данных и слоёв кэширования может привести к возникновению всевозможных проблем. При использовании нескольких баз данных пользователь может быть зарегистрирован на одном сервере, но не на другом. При наличии одного экземпляра Redis на сервер вы можете быть зарегистрированы на App Server 1, но когда балансировщик нагрузки перенаправит вас на App Server 2, вам придётся регистрироваться снова, поскольку ваша сессия хранится в локальном экземпляре Redis.

Мы можем заставить App Server 2 или любые другие будущие серверы приложений, подключённые к нашему балансировщику нагрузки, подключаться к службам App Server 1, но что произойдёт, если App Server 1 будет отключён для обслуживания или неожиданно выйдет из строя? Одной из причин добавления второго сервера является повышение надёжности и масштабируемости, что не решает нашу проблему.

Идеальный сценарий, когда имеется несколько серверов приложений, заключается в том, чтобы внешние службы, такие как MySQL и Redis, работали в отдельной среде. Для этого мы можем использовать управляемые сервисы, например AWS RDS для баз данных и AWS Elasticache для Redis, или неуправляемые сервисы, то есть создать отдельный сервер для запуска этих сервисов самостоятельно. Управляемые сервисы обычно являются лучшим вариантом, если стоимость не является проблемой, поскольку вам не нужно беспокоиться об обновлении ОС и программного обеспечения, и они обычно имеют более высокий уровень безопасности.

Представим, что мы решили использовать управляемые сервисы для нашего приложения. Конфигурация Laravel будет выглядеть следующим образом:

-DB_HOST=localhost
+DB_HOST=app-database.a2rmat6p8bcx7.us-east-1.rds.amazonaws.com
-REDIS_HOST=localhost
+REDIS_HOST=app-redis.qexyfo.ng.0001.use2.cache.amazonaws.com

После того как все настроено, наша инфраструктура будет выглядеть следующим образом при подключении App Server к нашим сервисам.

Инфраструктура будет выглядеть следующим образом при подключении App Server'ов нашим сервисам MySQL и redis
Инфраструктура будет выглядеть следующим образом при подключении App Server'ов нашим сервисам MySQL и Redis

3. Загружаемый пользователем контент

Наше приложение позволяет пользователям загружать свою фотографию профиля, отображаемую после входа в систему. В текущей инфраструктуре изображения сохраняются во внутренней папке приложения и передаются оттуда. Теперь, когда у нас есть несколько App Server, это станет проблемой, поскольку изображения, загруженные на App Server 1, не будут присутствовать на втором сервере.

Есть несколько способов решить эту проблему. Один из них — создать общую папку между серверами (например, Amazon EFS). Если выберем этот вариант, нам придётся настроить кастомную файловую систему в Laravel, которая будет указывать на местоположение этой общей папки на наших серверах приложений. Несмотря на то, что этот вариант вполне приемлем, он требует определённых знаний по настройке дисков на серверах, и для каждого нового сервера вам придётся заново настраивать общую папку.

Обычно мы предпочитаем использовать облачные сервисы хранения объектов, такие как Amazon S3 или Digital Ocean Spaces. Laravel позволяет легко работать с этими сервисами, если вы используете опции File Storage. В этом случае вам нужно будет только настроить диск файловой системы на использование S3 и загрузить все предыдущее содержимое, загруженное пользователями, в бакет.

-FILESYSTEM_DISK=local
+FILESYSTEM_DISK=s3

AWS_ACCESS_KEY_ID=your-key
AWS_SECRET_ACCESS_KEY=your-secret-access-key
AWS_DEFAULT_REGION=us-east-1
AWS_BUCKET=your-bucket-name

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

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

Если в будущем ваше приложение вырастет, вы можете настроить AWS Cloudfront, который действует как CDN поверх вашего S3 бакета, обслуживая содержимое бакета быстрее для ваших пользователей и часто дешевле, чем S3.

4. Очереди

В шаге 2 мы установили централизованный Redis сервер — технологию, которую мы использовали для управления очередями приложений. Эта технология также подойдёт для наших приложений с балансировкой нагрузки, но есть несколько хороших вариантов, которые стоит рассмотреть.

Если вы продолжаете обрабатывать очереди на своих серверах приложений, используя централизованный экземпляр Redis, никаких изменений делать не нужно. Задания будут приниматься тем сервером, на котором есть worker для обработки задания.

Другой вариант — использовать сервис вроде AWS SQS, который может снизить нагрузку на ваш экземпляр Redis по мере роста приложения, переложив эту работу на другой сервис.

5. Команды по расписанию

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

В Laravel есть встроенный способ обработки этого сценария, чтобы ваши запланированные команды выполнялись только на одном сервере с помощью цепочки методов onOneServer().

$schedule->command('report:generate')
->daily()
->onOneServer();

Этот метод требует использования централизованного кэширующего сервера, поэтому шаг 2 очень важен для его реализации.

6. Развёртывание

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

Мы все ещё можем развернуть наши приложения, используя наш предыдущий подход, но теперь нам нужно убедиться, что мы не забыли нажать кнопку развёртывания на обоих серверах. Если мы забудем, то на наших серверах будут работать разные версии приложения, что может привести к большим проблемам.

При наличии нескольких серверов, вероятно, пришло время поднять стратегию развёртывания. Существуют очень хорошие инструменты и сервисы для развёртывания, такие как Laravel Envoyer или PHP Deployer. Эти инструменты и сервисы позволяют автоматизировать процесс развёртывания на нескольких серверах, что позволяет исключить человеческий фактор из уравнения.

Если мы хотим углубиться в процесс развёртывания, то, поскольку у нас теперь есть 2 сервера приложений, одним из преимуществ является то, что мы можем временно удалить один из серверов из балансировщика нагрузки, и он перестанет принимать запросы. Это позволяет нам проводить развёртывание с нулевым временем простоя, когда мы удаляем первый сервер из балансировщика нагрузки, развёртываем новый код, помещаем его обратно в балансировщик нагрузки, удаляем второй и повторяем тот же процесс. Как только работа на сервере 2 будет завершена, оба сервера получат новый код и будут подключены к балансировщику нагрузки. Чтобы добиться этого, мы можем использовать инструменты вроде AWS CodeDeploy, но их настройка сложнее, чем в предыдущих вариантах.

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

Развёртывание — очень важный процесс наших приложений, если можно автоматизировать его с помощью Github Actions или любого другого CI/CD-сервиса, мы значительно улучшаем процесс.
Развёртывание — очень важный процесс наших приложений, поэтому если мы можем автоматизировать его с помощью Github Actions или любого другого CI/CD-сервиса, мы значительно улучшаем процесс.

7. Сеть и безопасность

Дополнительное преимущество использования балансировщика нагрузки заключается в том, что наши серверы больше не являются точкой входа на наши веб-сайты. Это означает, что наши серверы могут быть доступны только изнутри и/или ограничены определёнными IP-адресами (нашими IP-адресами, IP-адресами балансировщика нагрузки и т. д.). Это значительно повышает безопасность серверов, поскольку они недоступны напрямую. То же самое можно (и нужно) сделать для кластеров баз данных и кэша.

Для этого мы разрешим трафик на порт 22 только с наших собственных IP-адресов (чтобы мы могли подключаться к серверу по SSH) и разрешим трафик на порт 80 с балансировщика нагрузки, чтобы он мог отправлять запросы на сервер. Те же правила применимы к кластерам баз данных и кэша.

Заключительные соображения

При добавлении дополнительных серверов в инфраструктуру необходимо учитывать множество моментов. Это усложняет инфраструктуру и рабочие процессы, но при этом повышает надёжность и масштабируемость приложения, а также улучшает общую безопасность.

Если рассматривать эти рекомендации с самого начала процесса, то они просты в исполнении и могут оказать значительное влияние на улучшение вашего приложения.

Дополнительные материалы

Предыдущая Статья

Основы TypeScript: Неявные и Явные типы, Утверждения типов

Следующая Статья

Основы TypeScript: Создание типов, перечислений и интерфейсов