Привет всем,
У меня есть один проект, который представляет из себя сетап в два сервера с обычным LAMP-стеком. Оба сервера разнесены по разным хостинговым площадкам.
Ранее на этих серверах поднял site-to-site vpn, и настроил репликацию сервисов и базы данных. На текущем этапе встал вопрос, а как настроить файловер между серверами? - Что бы при проблемах на одном сервере, пользовательский трафик перекинулся на резервный.
Единственное решение, как сказал мой один коллега - дешево и сердито, это настроить фейловер на уровне dns.
Доменное имя хостится на Cloudflare, под бесплатным тарифным планом. На бесплатном аккаунте не доступны фишки балансировки, за включенние этого аддона надо доплатить 5$. Но мы можем использовать API Cloudflare для манипуляции над поддоменом, в обновлении его ip-адреса.
Для в взаимодействия с API напишем небольшой скриптик на bash, при помощи которого будем оправлять изменения на clouflare.
Получаем токен к API
Прежде чем начать, нужно получить токен для работы c API. Подключаемся к панели, и переходим в настройки своего профиля:
Затем перемещаемся в раздел c api-токенами:
На новом окне жмем на создание нового токена:
Далее нужно будет выбрать шаблон с правами на использование api. Мы создадим свой кастомный токен.
В менюшке создания токена, указываем имя для будущего токена.
А в разделе permissions
даем доступ на взаимодействие с объекта DNS. Затем указываем, с какой именно зоной хотим работать.
Жмем подтверждение, и создаем новый токен:
Откроется новая страничка с токеном, мы просто копируем его.
Пишем скриптец
В зоне моего домена я добавил запись с именем service.nixhub.ru
с указанием ip-адреса первого сервера. Теперь напишем скриптец, который будет проверять доступность сервиса на первой ноде.
И в случаи недоступности, отправит запрос в API Cloudflare на изменение ip-адреса.
Нечего оригинальнее я не придумал, кроме того как осуществлять проверки доступности хоста через curl-запрос к вебсерверу и icmp-проверкой. Ну и собственно сам сприптец:
#!/bin/bash
CF_ZONE="nixhub.ru"
CF_NSRECORD="service.nixhub.ru"
CF_TOKEN="tokens"
IP_ADDR=$(curl -s -X GET https://checkip.amazonaws.com)
IP_DOMAIN=$(host $CF_NSRECORD | grep -Eioh "([0-9]{1,3}[\.]){3}[0-9]{1,3}")s
NGNX_AVABILITY=$(curl -s $CF_NSRECORD >>/dev/null && echo up || echo down)
HOST_AVABILITY=$(ping -c3 $CF_NSRECORD >>/dev/null && echo up || echo down)
LOG_PATH="/var/log/cfupdate.log"
function message_to_file() {
MESSAGE=$1
TIMESTAMP=$(date "+%Y-%m-%d/%H:%M")
echo "$TIMESTAMP --> $MESSAGE" >> $LOG_PATH
}
function update_dns_record() {
CF_ZONEID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$CF_ZONE&status=active" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')
CF_ARECORDID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONEID/dns_records?type=A&name=$CF_NSRECORD" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$CF_ZONEID/dns_records/$CF_ARECORDID" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$CF_NSRECORD\",\"content\":\"$IP_ADDR\",\"ttl\":900,\"proxied\":false}" | jq
}
if [[ $IP_ADDR == $IP_DOMAIN ]]; then
message_to_file "DNS Record don't needed update"
exit
fi
if [[ $NGNX_AVABILITY = "down" || $HOST_AVABILITY = "down" ]]; then
message_to_file "Nginx state is $NGNX_AVABILITY. Host is $HOST_AVABILITY. Perform cf failover."
update_dns_record
fi
По классике, сначала определяем ряд статических переменных:
CF_ZONE="nixhub.ru"
CF_NSRECORD="service.nixhub.ru"
CF_TOKEN="token"
CF_ZONE
- в значении этой переменной указывается доменная зона, с которой будут происходить изменения. И к которой мы ранее предоставили доступ по токенуCF_NSRECORD
- тут указываем а-запись, которую в будущем и будем менять.CF_TOKEN
- здесь вставляем ранее полученный токен от cloudflare.LOG_PATH
- в этой переменной хранится путь к журналу (логу)
Далее список переменных, в значение которых подставляется результат выполнения команд:
IP_ADDR=$(curl -s -X GET https://checkip.amazonaws.com)
IP_ADDR
- данная переменная будет помещать к себе результат выполнения командыcurl
, которая отправит запрос на внешний ресурс для получение текущего ip-адреса.
Последующие переменные, получают значения от проверок доступности первого сервера.
NGNX_AVABILITY=$(curl -s $CF_NSRECORD >>/dev/null && echo up || echo down)
HOST_AVABILITY=$(ping -c3 $CF_NSRECORD >>/dev/null && echo up || echo down)
NGNX_AVABILITY
- тут в значении результат выполнения командыcurl
, которой мы дергаем страничку сайта и вывод перенаправляем в/dev/null
. Затем через двойной амперсанд выполняется логической условие. Если сайт доступен, то значение будет строкаup
. Иначе помещается строкаdown
.HOST_AVABILITY
- здесь также будет значенияup
илиdown
, которые мы получем в результате выполнения командыping
.
В скриптец включил две функции.
function message_to_file() {
MESSAGE=$1
TIMESTAMP=$(date "+%Y-%m-%d/%H:%M")
echo "$TIMESTAMP --> $MESSAGE" >> $LOG_PATH
}
function update_dns_record() {
CF_ZONEID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$CF_ZONE&status=active" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')
CF_ARECORDID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONEID/dns_records?type=A&name=$CF_NSRECORD" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')
curl -s -X PUT "https://api.cloudflare.com/client/v4/zones/$CF_ZONEID/dns_records/$CF_ARECORDID" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" \
--data "{\"type\":\"A\",\"name\":\"$CF_NSRECORD\",\"content\":\"$IP_ADDR\",\"ttl\":900,\"proxied\":false}" | jq
}
-
Функция
message_to_file()
в качестве одного аргумента принимает на себя какую-то строку. И далее к этой строке конкатинирует время, затем записывает все в журнал. -
Функция
update_dns_record()
просто выполняет post-запрос на API Cloudflare, для изменения записи.
Во внутрь последней функции включены свои локальные переменные, которые хранят с себе значения id объектов cloudfrare. Значения данных переменных мы также получаем за счет отправки через curl
запроса в API. Далее через пайп полученный вывод перенаправляем в jq
и вычленяем нужные нам id или контент.
CF_ZONEID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones?name=$CF_ZONE&status=active" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')
CF_ARECORDID=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONEID/dns_records?type=A&name=$CF_NSRECORD" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .[0] | .id')
CF_CURRENT_IP=$(curl -s -X GET "https://api.cloudflare.com/client/v4/zones/$CF_ZONEID/dns_records/$CF_ARECORDID" \
-H "Authorization: Bearer $CF_TOKEN" \
-H "Content-Type: application/json" | jq -r '{"result"}[] | .content')
Эти переменные вынес в функцию, что бы исключить лишние обращения в CloudFlare.
Ну и наконец, финальные условия:
if [[ $IP_ADDR == $CF_CURRENT_IP ]]; then
message_to_file "DNS Record don't needed update"
exit
fi
Этот условный оператор проверяет текущий ip-адрес ноды и ip-адрес закрепленный за А-записью в cloudflare. И если они совпадают, то скрипт просто завершает свою работу.
Следующее условие проверяет значения в переменных - NGNX_AVABILITY
, HOST_AVABILITY
. И если хоть одно значение down
, то в сначала в лог добавится сообщение о переключении. Затем выполнится функция update_dns_record
.
if [[ $NGNX_AVABILITY = "down" || $HOST_AVABILITY = "down" ]]; then
message_to_file "Nginx state is $NGNX_AVABILITY. Host is $HOST_AVABILITY. Perform cf failover."
update_dns_record
fi
Задание в cron
Теперь остается скриптец завернуть в cron-задание. Мне показалось логично создать задачу на втором сервере.
root@service2:~$ vim /etc/crontab
---
*/10 * * * * root /bin/bash /root/scripts/cfupdate.sh
В данном случаи сприпт будет запускаться каждые 10 минут.
Стоит отметить также момент, если в ваших dns включена опция proxy, то придется пересмотреть условия проверок доступности мастер сервера. Иначе нечего работать не будет.