История нашего расследования непредвиденных триггеров Consul "Watch" и того, что мы узнали в ходе этого процесса.

В предыдущем посте мы рассмотрели концепцию «часов» в Consul и подробнее остановились на том, как они реализованы.



Как я объяснил в сообщении выше (которое я настоятельно рекомендую вам прочитать, чтобы получить некоторый контекст), GO-PAY использует Consul для обнаружения услуг, а «часы» Consul помогают нам отслеживать статус услуги.

Этот подход хорошо работал у нас в течение последних двух лет, но мы столкнулись с некоторыми интересными проблемами при масштабном внедрении Consul.

В этом посте мы объясним, как мы выявили проблемы, исследовали их и узнали больше о Consul (и его истории исправлений) в процессе. Также обратите внимание на несколько советов по использованию Consul в производстве с большим коллективным разумом.

Но я забегаю вперед. Начнем с самого начала:

Подстрекательский инцидент

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

1. Каждый раз, когда новый экземпляр наблюдаемой службы регистрируется в Consul.

2. Существующий экземпляр отменяет регистрацию при корректном завершении работы.

3. Проверка работоспособности этой службы начинает давать сбой.

По крайней мере, так было у нас. Но в производственной среде мы заметили, что наблюдение для каждой из наших служб периодически запускалось без каких-либо изменений в своем состоянии (см. Рисунок 1).

Рисунок 1 показывает нашу панель управления APM, показывающую высокую пропускную способность повторных запусков Watch. И это несмотря на то, что наблюдаемая служба работоспособна при работе на Consul v0.7.0.

Потом наступили последствия

Наша структура RPC предназначена для равномерного маршрутизации и распределения трафика между работоспособными экземплярами целевой службы. Это делается с помощью алгоритма балансировки нагрузки на стороне клиента (который будет расширен в следующей публикации). Этот механизм динамического переключения трафика с неработоспособных экземпляров на исправные создается с помощью Consul Watch Handlers.

Мы делаем это, прикрепляя функцию Watch Handler к часам соответствующей службы. При каждом триггере отслеживания эта функция запрашивает каталог Consul, чтобы найти текущие работоспособные экземпляры наблюдаемой службы, и вызывает ловушку для обновления / создания подключений к ним. Но, как видно из рисунка 1, из-за высокой частоты срабатывания триггеров наблюдения каждая служба слишком часто пыталась обновить свое соединение с целевой службой.

Это была серьезная проблема.

Когда мы обнаружили эту проблему, мы использовали Consul Server v0.7.0 и несколько агентов более высоких версий, таких как v0.9.3, v1.0.5 и v1.4.0. Изучив исходный код Consul, прошлые проблемы и общедоступные списки рассылки, мы поняли, что единственным решением было обновить нашу версию Consul с v0.7.0 до как минимум v1.0.7.

Это устранило проблему. (См. Рисунок 2)

Рисунок 2 показывает ту же панель управления APM после обновления Consul, отражающую падение пропускной способности слежения почти до нуля, когда служба полностью исправна. Это ожидаемое поведение.

Но что было за исправление? 🤔

Чтобы понять, что именно было исправлено в последних версиях Consul, давайте сначала начнем с основ.

Если вы читали предыдущий пост о Consul Watches, вы должны быть знакомы с концепцией значения заголовка X-consul-index. Мы заметили, что в нашем кластере Consul значение этого заголовка X-consul-index непрерывно увеличивалось без каких-либо изменений состояния ни в одной из нижестоящих служб. Это постоянно увеличивающееся значение X-Consul-Index в каждом блокирующем запросе во время проверки работоспособности приводило к периодическому срабатыванию часов (без каких-либо изменений в работоспособности службы). Это привело нас к следующему вопросу:

Что может привести к частому изменению «X-Consul-Index» или «modifyIndex» в кластере Consul?

В идеале modifyIndex должен изменяться только тогда, когда служба регистрируется или отменяет регистрацию, или когда проверка работоспособности, выполняемая локальным агентом, обнаруживает изменение ее статуса. Затем агент передает этот новый статус серверу Consul через RPC. (Подробнее см. Архитектура консула). Но помимо этих основных операций, Consul также периодически выполняет антиэнтропию.

Короче говоря, антиэнтропия - это процесс периодической синхронизации агентом своего локального состояния с состоянием каталога сервера, чтобы каталог оставался синхронизированным.

Во время этой локальной синхронизации состояния агент Consul, работающий на данном узле, будет извлекать все службы и проверки, зарегистрированные на этом узле, из каталога сервера (использует / v1 / catalog / node / ‹node-id› и / v1 / health / node / ‹node-id› соответственно). Затем он выбирает все службы и проверки от локальных агентов (использует /v1/agent/services и /v1/agent/checks соответственно) . После того, как у него есть оба, он сравнивает их по их соответствующим выходным данным.

В этом сравнении он проверяет равенство следующих полей ServiceID, ServiceName, CheckName, CheckStatus, CheckOutput и ServiceTags. Здесь важно отметить, что он не смотрит только на вывод поля C heckStatus, он смотрит на все эти поля для сравнения на равенство. Если все они совпадают, он не вызывает сервер для синхронизации своего локального состояния. В противном случае он вызовет сервер Consul для проверки синхронизации, сделав вызов API для /catalog/register с телом запроса в качестве полей проверки локального агента.



Теперь, если это сравнение равенства не удается во время каждого запуска антиэнтропии, то сервер Consul будет каждый раз получать запись для этой конкретной службы и применять ее к журналу raft. Это увеличивает значение поля modifyIndex в ответе API, когда кто-то впоследствии вызывает / v1 / catalog / service.

Эти записи, которые не имеют различий в изменении состояния, называются «идемпотентными записями» в кластере Consul. Вот почему все эти API-интерфейсы блокирующих запросов не утверждают, что каждый раз, когда вы вызываете API, вы гарантированно получаете другой результат. Это может быть тот же результат, но с другим значением заголовка X-Consul-Index.

см. ссылки [1], [2], [3] для получения дополнительной информации об этом.

Копать глубже

Таким образом, следующим шагом в отладке было выяснить, почему проверка равенства дает сбой во время каждого антиэнтропийного цикла. Изучив исходный код Consul и прошлые журналы проблем на Github, мы пришли к выводу, что произошла ошибка, из-за которой Consul не поддерживал сохранение S erviceTags при проверках работоспособности агентов. Из-за этого во время каждого антиэнтропийного цикла выходные данные проверки каталога и проверки агента не совпадали. Это произошло потому, что при проверке работоспособности в каталоге все S erviceTags будут добавлены во время регистрации службы, но проверки локального агента на этом узле их не будут. Это привело к тому, что агент Consul синхронизировал свое локальное состояние во время каждого цикла энтропии, что привело к множеству идемпотентных записей для этой службы. Эта проблема была исправлена ​​в v1.0.5 как часть данного PR для исправления ошибки.

Если v1.0.5 устранила проблему, зачем переходить на v1.0.7? 🤷‍♀

В более ранних реализациях Consul индексирование рафта имело особую конструкцию. Я объясню на примере кластера Consul, имеющего две службы с именами service-A и service-B. Предположим, service-A ' состояние / проверки изменяются / синхронизируются, а состояние / проверки service-B - нет. Даже тогда ответ API / v1 / catalog / service / service-B будет иметь значение заголовка X-Consul-Index, равное modifyIndex из service-A.

Проще говоря, заголовок X-Consul-Index не зависел от конкретной услуги. Это был индекс, по которому любая служба в кластере была в последний раз изменена в журнале raft. Таким образом, одна временная или неправильно настроенная проверка службы в кластере вызовет пробуждение всех часов, так как запись в modifyIndex одной временной службы увеличит значение X-Consul-Index на любой другой конечной точке службы каталога в цикле наблюдения.

Это было исправлено в v1.0.7 как часть исправления этой ошибки. Поэтому нам было необходимо выполнить обновление до v1.0.7 как на агенте, так и на сервере, поскольку эта логика заполнения X-Consul-Index в заголовках ответов выполняется сервер.

Дневник случая

Основываясь на нашем опыте использования Consul, мы поняли, что Consul меняется довольно быстро, и что-то часто ломается во время обновления версии. При этом сообщество активно вносит необходимые исправления и упоминает критические изменения в примечаниях к обновлению.

Вот несколько вещей, о которых следует помнить при использовании Consul:

1. Убедитесь, что у вас есть надлежащая автоматизация для обновления кластеров Consul в производственной среде, так как вам придется делать это чаще, чем вы думаете.

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

Подробные инструкции по обновлению Consul можно найти здесь.

Использованная литература:

  1. Что такое идемпотентная запись и когда это происходит?
  2. Побочные эффекты идемпотентной записи
  3. Функция updateSyncState исходного кода Consul, используемая во время антиэнтропии
  4. Консул блокирует запросы
  5. Примечания к обновлению Консула
  6. Инструкция по апгрейду Консула
  7. Что такое сервисная сетка и зачем она вам нужна?

GOJEK управляет одним из крупнейших кластеров JRuby, Java и Go в Юго-Восточной Азии, обрабатывает более 100 миллионов транзакций в месяц и сотрудничает с 2 миллионами партнеров-драйверов. Мы меняем динамику платформ по запросу, улучшая качество жизни и попутно оказывая влияние на общество. Мы также ищем больше талантов, которые помогут нам решать сложные технические проблемы. Хотите попробовать свои силы в борьбе с масштабами в компании лучших в своем деле? Ваше путешествие начинается на gojek.jobs. Давай займемся взломом ✌️