Высокодоступный балансировщик нагрузки NGINX в Google Cloud с использованием Pacemaker, Corosync и статических IP-адресов в CentOS 7
С появлением виртуализированной облачной инфраструктуры все больше и больше организаций переходят в облако. Это дает множество преимуществ, но значительными факторами являются практически неограниченная масштабируемость, высокая доступность, снижение затрат и повышение производительности.
Большинство масштабируемых и высокодоступных приложений работают за балансировщиком нагрузки. Google Cloud Platform, который является одним из ведущих поставщиков облачных услуг, обеспечивает несколько типов балансировки нагрузки в качестве управляемой услуги для ваших нужд. Если вы хотите узнать об этом больше, посетите https://cloud.google.com/load-balancing/docs/choosing-load-balancer.
Google Cloud также предоставляет отличную блок-схему для выбора балансировщиков нагрузки в соответствии с вашими конкретными требованиями.

Хотя вам предлагается выбирать между доступными балансировщиками нагрузки, могут быть сценарии, в которых эти балансировщики нагрузки могут не соответствовать вашим потребностям. Например, давайте рассмотрим ситуацию, если вы используете один балансировщик нагрузки, такой как NGINX или HAProxy, в своей локальной среде, где вы обрабатываете несколько типов запросов на разных портах. Если вы хотите перейти в облако, вместо использования одного экземпляра NGINX вам придется создать несколько балансировщиков нагрузки, что может оказаться для вас дорогостоящим делом, особенно если у вас минимальная нагрузка. Кроме того, большинство организаций, мигрирующих в облако, хотят сделать что-то подобное для подобной миграции и не хотят менять архитектуру своих приложений для соответствия облаку. В этой ситуации у вас может не быть возможности переключиться на балансировку нагрузки Google Cloud.
Вы всегда можете настроить NGINX или HA Proxy на экземпляре Google Compute Engine. Однако проблема в том, что экземпляр Google Compute Engine является зональным ресурсом. Следовательно, он не выдержит зонального нарушения. Что ж, чтобы решить эту проблему, вам необходимо иметь резервные балансировщики нагрузки в каждой зоне, чтобы вы могли переключиться на эти экземпляры в случае сбоя в работе зоны. В локальной среде вы можете использовать такие инструменты, как keepalived, который использует протокол VRRP для автоматизации аварийного переключения. Однако, поскольку Google Cloud использует полностью виртуализированную инфраструктуру, VRRP там не работает, и нам нужно искать альтернативные способы сделать это.
Решение
Google Cloud Platform предоставляет виртуальные статические IP-адреса, которые вы можете зарезервировать заранее. Эти IP-адреса могут быть внутренними или внешними. Вам необходимо присвоить внутренним IP-адресам псевдонимы существующему виртуальному адаптеру Ethernet, в то время как внешние IP-адреса присоединяются непосредственно к вычислительному экземпляру в качестве ресурса. Вы можете использовать этот принцип для автоматизации переключения между первичным и вторичным узлами, написав для этого специальные сценарии. Уже есть несколько хорошо написанных и проверенных решений, таких как this, которые мы воспользуемся сегодня.
Инфраструктурный дизайн
У нас есть два экземпляра Google Compute Engine, работающих в двух разных зонах. На обоих установлен NGINX, который действует как балансировщик нагрузки. Мы также устанавливаем Pacemaker и Corosync, которые помогают в управлении кластером.
Corosync - это утилита, которая позволяет кластеризовать несколько серверов, и они взаимодействуют друг с другом с помощью UDP. Они действуют как уровень обмена сообщениями между одноранговыми узлами.
«Pacemaker» - это диспетчер ресурсов кластера, который управляет агентами ресурсов в кластере, например выбирает ресурс-лидер и отслеживает его, а когда лидер больше не отвечает, выбирает другого агента в качестве лидера.
Агент ресурсов - это сценарий, который отвечает на запрос Pacemaker о запуске, мониторинге и остановке. Это ядро, которое соединяет кластер и Google Cloud API.
- Corosync и Pacemaker запускают и создают кластер.
- Pacemaker выбирает один ресурс в качестве лидера и выполняет команду запуска агента ресурса. Агент ресурсов отвечает на запрос запуска и запускает nginx.
- После запуска NGINX агент ресурсов вызывает API-интерфейсы Google Cloud, чтобы назначить статический IP-адрес серверу, на котором он работает.
- Затем «Pacemaker» наблюдает за агентом ресурсов, чтобы узнать, работает ли NGINX на сервере и отвечает на запросы проверки работоспособности.
- Если во время мониторинга не было получено ответа, кардиостимулятор резервного экземпляра обнаруживает его и понимает, что он должен взять на себя ответственность.
- «Pacemaker» в резервном экземпляре выбирает агента ресурсов резервного экземпляра в качестве ведущего, и он становится активным. Агент ресурсов отвечает на запрос запуска и запускает nginx.
- После запуска NGINX агент ресурсов вызывает API-интерфейсы Google Cloud для назначения статических IP-адресов резервному серверу.
- Затем «Pacemaker» наблюдает за агентом ресурсов, чтобы узнать, работает ли NGINX на сервере и отвечает на запросы проверки работоспособности.

Скрипты агента ресурсов
Скрипты агента ресурсов составляют основу интеграции Pacemaker с Google Cloud Platform. Он отвечает за вызов соответствующих API-интерфейсов Google Cloud для отслеживания и назначения виртуальных IP-адресов запрашивающим их экземплярам.
Демонстрация
Теперь я продемонстрирую, как это можно сделать в Google Cloud с помощью двух экземпляров Google Compute Engine, работающих под управлением NGINX. Мы будем использовать Terraform для раскрутки наших серверов, а затем запустим несколько скриптов для настройки решения.
Для запуска вам необходимо установить Terraform CLI и настроить учетную запись службы.
Прочтите Настройка облачной среды и Установка Terraform для получения более подробной информации о том, как это сделать.
Развитие вашей инфраструктуры
Теперь мы развернем инфраструктуру, необходимую для запуска установки. Когда вы применяете Terraform, он запускает два экземпляра GCE с установленным NGINX хост-сервером бастиона, внутренним статическим виртуальным IP-адресом, внешним статическим виртуальным IP-адресом и облачным NAT для исходящего интернет-трафика. Он также настроит правила брандмауэра для открытия портов 22 и 80 для указанных вами диапазонов источников и портов UDP 5404–5406 для Corosync для связи между узлами.
Выполните следующее, чтобы Terraform развил вашу инфраструктуру.
git clone https://github.com/bharatmicrosystems/gcp-failoverd.git cd gcp-failoverd/ cp terraform.tfvars.example terraform.tfvars
Измените файл terraform.tfvars и замените переменные соответствующими значениями для project, region и source_ranges.
Скопируйте загруженный ключ JSON в gcp-failoverd/ и переименуйте его в credentials.json.
Выполните следующие
terraform init terraform plan terraform apply
Настройка высокой доступности
Теперь мы должны установить высокую доступность между балансировщиками нагрузки.
Войдите в Bastion Host
Подключитесь по SSH к хосту-бастиону из вашей локальной системы, выполнив команду gcloud compute ssh.
gcloud compute ssh bastion --zone europe-west2-a
Он должен открыть сеанс SSH с хостом-бастионом. Внутри хоста-бастиона клонируйте репозиторий gcp-failoverd.
Репозиторий содержит сценарии, которые уже написаны для вас и протестированы на платформе Google Cloud Platform. Мы рассмотрим сценарии и узнаем, что они делают.
git clone https://github.com/bharatmicrosystems/gcp-failoverd.git cd gcp-failoverd/ cp -a scripts/ exec/
Быстрый старт
Уже существует автоматизированный скрипт, который настроит для вас решение, а также запустит автоматические дымовые тесты. Вы можете использовать это для быстрой обработки.
Скрипт имеет следующее использование
sh -x setup-gcp-failoverd.sh -i [internal_vip_name] -e [external_vip_name] -l [load_balancers] -c [cluster_name] -h [health_check_endpoint] Where: internal_vip_name : name of the static internal ip which needs to be attached to the active instance external_vip_name : name of the static external ip which needs to be attached to the active instance load_balancers : comma-separated list of load balancers part of the cluster health_check_endpoint : The endpoint that the agent needs to monitor to declare your service healthy. If you are serving the health endpoint on localhost:80/healthz, then write :80/healthz
Чтобы использовать автоматизированные сценарии для нашей демонстрации, запустите следующее:
cd exec/ sh -x setup-gcp-failoverd.sh -i nginx-internal-vip -e nginx-external-vip -l nginx-instance01,nginx-instance02 -c nginx-cluster -h :80/
Трудный путь
Установите кардиостимулятор и PCS
«Pacemaker» помогает в управлении кластером и поэтому включает corosync в качестве зависимости. PCS - это инструмент командной строки, который помогает взаимодействовать с кардиостимулятором. Мы установим их оба во всех экземплярах балансировщика нагрузки Nginx. Используйте gcloud compute ssh [instance_hostname] --internal-ip для входа в виртуальные машины. Вам нужно выбрать пароль, убедитесь, что вы используете один и тот же пароль на всех виртуальных машинах.
PASSWORD=[YOUR_CLUSTER_PASSWORD] sudo yum install -y pacemaker pcs sudo usermod --password $(openssl passwd -1 $PASSWORD) hacluster sudo systemctl enable pcsd.service sudo systemctl start pcsd.service sudo systemctl restart pcsd.service
Настроить кластер
Войдите только в первичный экземпляр nginx-instance01
PASSWORD=[YOUR_CLUSTER_PASSWORD] CLUSTER_NAME=nginx-instance sudo pcs cluster auth nginx_instance01 nginx_instance02 -u hacluster -p $PASSWORD sudo pcs cluster setup --name $CLUSTER_NAME nginx_instance01 nginx_instance02 sudo pcs cluster start --all sudo pcs status corosync sudo pcs cluster status sudo pcs property set stonith-enabled=false
Выход
+ sudo pcs cluster auth nginx-instance01 nginx-instance02 -u hacluster -p $PASSWORD
nginx-instance01: Authorized
nginx-instance02: Authorized
+ sudo pcs cluster setup --name nginx-cluster nginx-instance01 nginx-instance02
Destroying cluster on nodes: nginx-instance01, nginx-instance02...
nginx-instance01: Stopping Cluster (pacemaker)...
nginx-instance02: Stopping Cluster (pacemaker)...
nginx-instance02: Successfully destroyed cluster
nginx-instance01: Successfully destroyed cluster
Sending 'pacemaker_remote authkey' to 'nginx-instance01', 'nginx-instance02'
nginx-instance01: successful distribution of the file 'pacemaker_remote authkey'
nginx-instance02: successful distribution of the file 'pacemaker_remote authkey'
Sending cluster config files to the nodes...
nginx-instance01: Succeeded
nginx-instance02: Succeeded
Synchronizing pcsd certificates on nodes nginx-instance01, nginx-instance02...
nginx-instance01: Success
nginx-instance02: Success
Restarting pcsd on the nodes in order to reload the certificates...
nginx-instance01: Success
nginx-instance02: Success
+ sudo pcs cluster start --all
nginx-instance01: Starting Cluster (corosync)...
nginx-instance02: Starting Cluster (corosync)...
nginx-instance01: Starting Cluster (pacemaker)...
nginx-instance02: Starting Cluster (pacemaker)...
+ sudo pcs status corosync
Membership information
----------------------
Nodeid Votes Name
1 1 nginx-instance01 (local)
2 1 nginx-instance02
+ sudo pcs cluster status
Cluster Status:
Critical: Unable to get stonith-history
Connection to the cluster-daemons terminated
Reading stonith-history failed
PCSD Status:
nginx-instance01: Online
nginx-instance02: Online
+ sudo pcs property set stonith-enabled=false
Настроить агент ресурсов
Скопируйте файлы агента ресурсов на оба ваших сервера.
loadbalancers=nginx_instance01,nginx_instance02
for instance in $(echo $loadbalancers | tr ',' ' '); do
ZONE=`gcloud compute instances list --filter="name=${instance}"|grep ${instance} | awk '{ print $2 }'`
gcloud compute scp --zone=$ZONE --internal-ip gcp-failoverd.sh gcp-assign-vip.sh ${instance}:~/
done
Войдите в обе виртуальные машины и запустите
sudo systemctl enable corosync.service pacemaker.service sudo mkdir -p /usr/lib/ocf/resource.d/gcp sudo mv gcp-failoverd.sh /usr/lib/ocf/resource.d/gcp/gcp-failoverd sudo mv gcp-assign-vip.sh /usr/bin/gcp-assign-vip.sh sudo chmod +x /usr/lib/ocf/resource.d/gcp/gcp-failoverd sudo chmod +x /usr/bin/gcp-assign-vip.sh sudo chown -R root:root /usr/lib/ocf/resource.d/gcp sudo chown root:root /usr/bin/gcp-assign-vip.sh
Запустить агент ресурсов
Авторизуйтесь на активном сервере nginx-instance01
sudo pcs resource create GCPFailoverd ocf:gcp:gcp-failoverd internal_vip=nginx-internal-vip external_vip=nginx-external-vip healthz=:80/ sudo pcs status
Выход
+ sudo pcs status Cluster name: nginx-cluster Stack: corosync Current DC: nginx-instance02 (version 1.1.20-5.el7_7.2-3c4c782f70) - partition with quorum Last updated: Thu Apr 16 15:09:13 2020 Last change: Thu Apr 16 14:49:31 2020 by hacluster via crmd on nginx-instance02 2 nodes configured 1 resource configured Online: [ nginx-instance01 nginx-instance02 ] Full list of resources: GCPFailoverd (ocf::gcp:gcp-failoverd): Started nginx-instance01 Daemon Status: corosync: active/enabled pacemaker: active/enabled pcsd: active/enabled
Дымовой тест
Теперь мы должны запустить некоторые дымовые тесты, чтобы увидеть, работает ли установка должным образом.
Получите внутренний IP
healthz=:80/
internal_vip=nginx-internal-vip
external_vip=nginx-external-vip
INTERNAL_IP=`gcloud compute addresses list --filter="name=$internal_vip"| grep $internal_vip | awk '{ print $2 }'`
Дождитесь выделения IP
INTERNAL_IP_STATUS=`gcloud compute addresses list --filter="name=$internal_vip"| grep $internal_vip | awk '{ print $NF }'`
while [[ $INTERNAL_IP_STATUS != "IN_USE" ]]; do
INTERNAL_IP_STATUS=`gcloud compute addresses list --filter="name=$internal_vip"| grep $internal_vip | awk '{ print $NF }'`
echo "Waiting for the IP $INTERNAL_IP to be alloted to an instance"
echo "Sleeping for 10 secs..."
sleep 10
done
Выход
INTERNAL_IP_STATUS=RESERVED Waiting for the IP 10.154.15.192 to be alloted to an instance Sleeping for 10 secs... INTERNAL_IP_STATUS=RESERVED Waiting for the IP 10.154.15.192 to be alloted to an instance Sleeping for 10 secs... INTERNAL_IP_STATUS=IN_USE
Получите экземпляр GCE, назначенный внутреннему IP-адресу
INTERNAL_INSTANCE_REGION=$(gcloud compute addresses list --filter="name=${internal_vip}"|grep ${internal_vip}|awk '{print $(NF-2)}')
INTERNAL_INSTANCE_NAME=$(gcloud compute addresses describe ${internal_vip} --region=${INTERNAL_INSTANCE_REGION} --format='get(users[0])'|awk -F'/' '{print $NF}')
INTERNAL_INSTANCE_ZONE=$(gcloud compute instances list --filter="name=${INTERNAL_INSTANCE_NAME}"|grep ${INTERNAL_INSTANCE_NAME}|awk '{print $2}')
INTERNAL_INSTANCE_STATUS=$(gcloud compute instances describe --zone=${INTERNAL_INSTANCE_ZONE} $INTERNAL_INSTANCE_NAME --format='get(status)')
echo "$INTERNAL_IP has been allocated to $INTERNAL_INSTANCE_NAME at $(date)"
Выход
10.154.15.192 has been allocated to nginx-instance01 at Thu Apr 16 14:52:00 UTC 2020
Вызов конечной точки NGINX
$ status=$(curl -s -o /dev/null -w '%{http_code}' http://$INTERNAL_IP$healthz)
$ echo "$(date): internal status: $status"
Выход
Thu Apr 16 14:49:58 UTC 2020: internal status: 200
Получите внешний IP
EXTERNAL_IP=`gcloud compute addresses list --filter="name=$external_vip"| grep $external_vip | awk '{ print $2 }'`
Дождитесь выделения IP
EXTERNAL_IP_STATUS=`gcloud compute addresses list --filter="name=$external_vip"| grep $external_vip | awk '{ print $NF }'`
while [[ $EXTERNAL_IP_STATUS != "IN_USE" ]]; do
EXTERNAL_IP_STATUS=`gcloud compute addresses list --filter="name=$external_vip"| grep $external_vip | awk '{ print $NF }'`
echo "Waiting for the IP $EXTERNAL_IP to be alloted to an instance"
echo "Sleeping for 10 secs..."
sleep 10
done
Выход
EXTERNAL_IP_STATUS=IN_USE
Получите экземпляр GCE, назначенный внешнему IP-адресу
EXTERNAL_INSTANCE_REGION=$(gcloud compute addresses list --filter="name=${external_vip}"|grep ${external_vip}|awk '{print $(NF-1)}')
EXTERNAL_INSTANCE_NAME=$(gcloud compute addresses describe ${external_vip} --region=${EXTERNAL_INSTANCE_REGION} --format='get(users[0])'|awk -F'/' '{print $NF}')
EXTERNAL_INSTANCE_ZONE=$(gcloud compute instances list --filter="name=${EXTERNAL_INSTANCE_NAME}"|grep ${EXTERNAL_INSTANCE_NAME}|awk '{print $2}')
EXTERNAL_INSTANCE_STATUS=$(gcloud compute instances describe --zone=${EXTERNAL_INSTANCE_ZONE} $EXTERNAL_INSTANCE_NAME --format='get(status)')
echo "$EXTERNAL_IP has been allocated to $EXTERNAL_INSTANCE_NAME at $(date)"
Выход
35.230.154.167 has been allocated to nginx-instance01 at Thu Apr 16 14:52:17 UTC 2020
Вызов конечной точки NGINX
$ status=$(curl -s -o /dev/null -w '%{http_code}' http://$EXTERNAL_IP$healthz)
$ echo "$(date): external status: $status"
Выход
Thu Apr 16 14:52:17 UTC 2020: external status: 200
Остановите экземпляр, на котором работает NGINX
gcloud compute instances stop -q --zone europe-west2-a nginx-instance01
Выход
Stopping instance(s) nginx-instance01...done. Updated [https://compute.googleapis.com/compute/v1/projects/my-project-1505734237566/zones/europe-west2-a/instances/nginx-instance01].
Подождите 60 секунд и повторите шаги. Вы обнаружите, что IP-адреса успешно переключены на второй экземпляр.
Заключение
Спасибо за чтение, надеюсь, вам понравилась статья. Ниже приведены полезные ссылки, которые я использовал в рассказе.