Обновления Istio в промышленных масштабах без простоев
Как Airbnb переводит десятки тысяч подов на десятках Kubernetes-кластеров на новые версии Istio
Airbnb работает с Istio® в промышленных масштабах с 2019 года. Мы обслуживаем нагрузки на Kubernetes и виртуальных машинах (используя расширение Istio для ВМ). Суммарно это десятки тысяч подов, несколько десятков Kubernetes-кластеров и тысячи виртуальных машин. В пиковые периоды через Istio проходят десятки миллионов запросов в секунду (QPS). О нашем пути к Istio мы рассказали в докладе на IstioCon 2021, а подробности об архитектуре — в докладе на KubeCon 2021.
Istio — фундаментальный элемент нашей архитектуры, поэтому поддержание актуальности и плановые обновления превращаются в серьёзную задачу. Тем не менее мы уже обновили Istio в общей сложности 14 раз. В этой статье команда Service Mesh в Airbnb рассказывает, как безопасно обновлять Istio, не допуская снижения доступности сервисов.
Сложности
Инженеры Airbnb запускают тысячи самых разных сервисов. Координировать все команды, владеющие этими сервисами, нереально — значит, обновления должны выполняться независимо от каждой отдельной команды. Отслеживать сразу все сервисы тоже невозможно, поэтому риски нужно минимизировать за счёт постепенного развёртывания.
С учётом этого мы сформулировали следующие цели для процесса обновления:
-
Нулевое время простоя для нагрузок и пользователей. В этом и состоит «бесшовность» обновления — владелец сервиса не должен быть вовлечён в процесс обновления Istio.
-
Постепенное развёртывание с возможностью управлять тем, какие сервисы обновляются или откатываются.
-
Откат обновления должен быть возможен для всех сервисов сразу, без координации с каждой командой.
-
Все сервисы должны быть обновлены в пределах заданного срока.
Архитектура
Наш стенд состоит из одного управляющего кластера (management cluster), на котором работает Istiod и хранится вся конфигурация сервисной сетки (VirtualServices, DestinationRules и т. д.), и нескольких рабочих кластеров (workload clusters) с пользовательскими нагрузками. Виртуальные машины работают отдельно, но их манифесты Istio также деплоятся в управляющий кластер — в отдельных пространствах имён. Мы используем исключительно режим Sidecar: каждая нагрузка запускает istio-proxy. Режим Ambient мы пока не используем.
Процесс обновления
На верхнем уровне мы следуем модели canary-обновления Istio. Она предполагает одновременный запуск двух версий (ревизий) Istiod: текущей и той, на которую мы переходим. Обе образуют единую логическую сервисную сетку, поэтому нагрузки, подключённые к одному Istiod, могут взаимодействовать с нагрузками, подключёнными к другому, и наоборот. Версии Istiod различаются метками ревизий — например, 1–24–5 для Istio 1.24.5 и 1–25–2 для Istio 1.25.2.
Обновление затрагивает как Istiod (плоскость управления), так и istio-proxy (sidecar плоскости данных), работающий на всех подах и ВМ. Хотя Istio поддерживает подключение более старого istio-proxy к более новому Istiod, мы этим не пользуемся. Вместо этого мы атомарно развёртываем новую версию istio-proxy вместе с конфигурацией, указывающей, к какому Istiod подключаться. Например, istio-proxy, собранный для версии 1.24, подключается только к Istiod версии 1.24, а istio-proxy для 1.25 — только к Istiod версии 1.25. Это устраняет один из источников сложности при обновлениях — необходимость обеспечивать совместимость плоскости данных и плоскости управления разных версий.
Первый шаг обновления — задеплоить новый Istiod с новой меткой ревизии в управляющий кластер. Поскольку все нагрузки явно привязаны к конкретной ревизии, ни одна из них не подключится к новому Istiod, так что этот шаг не влечёт никаких последствий.
Всё остальное обновление — именно здесь сосредоточены основные усилия и риски: нагрузки постепенно переводятся на новую версию istio-proxy и подключаются к новому Istiod.
Несколько ревизий Istio, при которых разные нагрузки подключены к разным ревизиям.
Спецификация развёртывания
Управление версией istio-proxy для каждой нагрузки осуществляется через файл rollouts.yml. В нём указываются пространства имён нагрузок (в виде шаблонов) и процентное распределение версий Istio:
# "production" — значение по умолчанию; всё, что не совпало с другим шаблоном, попадает сюда.
production:
1-24-5: 100
".*-staging":
1-24-5: 75
1-25-2: 25
# Закреплённое пространство имён — нагрузка для сквозной верификации.
istio-e2e:
1-25-2: 100
Эта спецификация описывает желаемое состояние всех пространств имён. Каждое пространство имён сначала сопоставляется с «корзиной» (по наиболее длинному совпавшему шаблону), а затем версия выбирается согласно распределению для этой корзины. Распределение работает на уровне пространств имён, а не подов или ВМ. Например:
".*-staging":
1-24-5: 75
1-25-2: 25
означает, что 75% пространств имён с суффиксом -staging будут назначены на 1–24–5, а оставшиеся 25% — на 1–25–2. Это назначение детерминировано: используется консистентное хеширование. Большую часть процесса обновления составляет редактирование rollouts.yml и последующий мониторинг.
Такой подход позволяет избирательно обновлять нагрузки. Мы можем обновлять окружения по отдельности и следить за тем, чтобы в каждый момент на новой версии находился лишь определённый процент из них. Это даёт время «обкатать» обновление и выявить возможные регрессии.
Далее мы подробно расскажем, как изменение в rollouts.yml применяется к тысячам нагрузок — как на Kubernetes, так и на виртуальных машинах.
Kubernetes
Для каждой ревизии Istio на каждом рабочем кластере есть соответствующий MutatingAdmissionWebhook для инъекции sidecar. Этот вебхук отбирает поды с меткой istio.io/rev=<revision> и добавляет в них контейнеры istio-proxy и istio-init. При этом контейнер istio-proxy содержит переменную окружения PROXY_CONFIG, которая задаёт discoveryAddress для соответствующей ревизии Istiod. Именно так версия istio-proxy и конфигурация подключения к Istiod деплоятся атомарно — полностью силами инжектора sidecar.
В каждом Deployment нагрузки есть эта метка ревизии. Например, нагрузка, настроенная на Istio 1.24.5, будет иметь метку istio.io/rev=1–24–5 в шаблоне пода; таким образом, поды этого Deployment будут мутированы MutatingAdmissionWebhook для Istio 1.24.5.
Это стандартный способ обновления Istio, однако он требует, чтобы в каждом Deployment явно указывалась метка ревизии. Для обновления тысяч нагрузок каждая команда должна была бы обновить эту метку и задеплоить свой сервис. Ни откат для всех нагрузок сразу, ни завершение обновления на 100% не были бы реальными по одной и той же причине — слишком большая зависимость от того, чтобы каждая нагрузка была задеплоена.
Krispr
Чтобы не обновлять нагрузки по отдельности, метка ревизии никогда не указывается напрямую в исходном коде нагрузки. Вместо этого мы используем Krispr — собственный фреймворк мутаций, который подставляет эту метку. Krispr позволяет отвязать обновления инфраструктурных компонентов от деплоев конкретных нагрузок.
Kubernetes-нагрузки Airbnb описываются через внутреннее API, а не через Kubernetes-манифесты напрямую. Это абстракция компилируется в Kubernetes-манифесты во время CI. Krispr запускается в процессе этой компиляции и мутирует полученные манифесты. Одна из таких мутаций — добавление метки ревизии Istio в спецификацию пода каждого Deployment, при этом нужная метка выбирается из rollouts.yml. Если команда замечает проблему с нагрузкой при деплое, она может откатиться — и тем самым откатить и обновление Istio, не привлекая команду Service Mesh.
Кроме того, Krispr работает во время допуска (admission) пода. Если под допускается из Deployment, которому больше двух недель, Krispr повторно мутирует этот под и при необходимости обновляет метку ревизии. В сочетании с тем, что максимальный срок жизни наших Kubernetes-узлов составляет две недели (а значит, и максимальный срок жизни пода тоже), это гарантирует завершение обновления Istio. Большинство нагрузок обновится в момент деплоя (во время запуска Krispr в CI), а для тех, что не деплоятся регулярно, естественная ротация подов и повторная мутация обеспечат обновление максимум за четыре недели.
Итого для каждой нагрузки:
-
При сборке в CI Krispr мутирует Kubernetes-манифесты, добавляя метку ревизии Istio на основе
rollouts.yml. -
При допуске пода в кластер Krispr повторно мутирует его, если соответствующий Deployment старше двух недель, и при необходимости обновляет метку ревизии Istio.
-
MutatingAdmissionWebhook конкретной ревизии Istio мутирует под, инжектируя sidecar и задавая соответствующий
discoveryAddress.
Виртуальные машины
На ВМ мы деплоим артефакт, который содержит istio-proxy, скрипт для запуска istio-iptables (аналог контейнера istio-init) и discoveryAddress для Istiod. Упаковка istio-proxy и discoveryAddress в один артефакт позволяет атомарно обновлять и то, и другое.
Установку этого артефакта обеспечивает агент на хосте — демон mxagent. Он определяет нужную версию, опрашивая набор тегов типа «ключ — значение» на ВМ (например, теги EC2 на AWS или теги ресурсов на GCP). Эти теги играют ту же роль, что метка istio.io/rev для Kubernetes-нагрузок. При каждом изменении тегов mxagent скачивает и устанавливает артефакт соответствующей версии. Таким образом, обновление istio-proxy на ВМ сводится к обновлению этих тегов — mxagent сделает всё остальное.
Наши ВМ-нагрузки — в основном инфраструктурные платформы, код которых не деплоится с регулярной периодичностью. Поэтому ВМ не поддерживают обновление в момент деплоя (в отличие от Kubernetes-нагрузок). Аналогично, команды не могут самостоятельно откатить эти нагрузки, однако это было признано приемлемым, поскольку таких инфраструктурных платформ лишь несколько.
Обновление тегов управляется центральным контроллером mxrc, который сканирует устаревшие ВМ. Если согласно rollouts.yml ВМ должна иметь другой набор тегов, контроллер обновляет их. По смыслу это примерно соответствует мутации подов в момент допуска в Krispr — с тем отличием, что ВМ изменяемы (mutable) и долгоживущи, а потому обновляются «на месте».
Для безопасности mxrc учитывает состояние здоровья ВМ — в частности, статус пробы готовности на WorkloadEntry. По аналогии с семантикой maxUnavailable в Kubernetes, mxrc стремится удерживать долю недоступных ВМ (то есть неработоспособных и тех, на которых выполняется обновление) ниже заданного порога. Обновления выполняются постепенно, с целью перевести все ВМ нагрузки на новую версию за две недели.
По истечении двух недель все ВМ будут приведены в соответствие с желаемым состоянием из rollouts.yml.
Заключение
Поддерживать актуальность программного обеспечения с открытым исходным кодом непросто, особенно в крупных окружениях. Обновления и другие операции второго дня (Day-2 operations) нередко отходят на второй план, что лишь усугубляет проблему, когда обновление всё-таки становится необходимым — ради устранения уязвимостей, выхода из-под действия истёкшей поддержки, использования новых возможностей и т. д. Для Istio это особенно актуально: версии теряют поддержку очень быстро.
Несмотря на сложность и масштаб нашей сервисной сетки, мы успешно обновили Istio 14 раз. Это стало возможным благодаря проектированию с прицелом на удобство сопровождения, выстраиванию процесса, исключающего простои, и снижению рисков через постепенное развёртывание. Аналогичные процессы применяются и для ряда других фундаментальных инфраструктурных систем в Airbnb.
Направления дальнейшего развития
По мере развития инфраструктуры Airbnb мы рассматриваем несколько ключевых проектов для эволюции нашей сервисной сетки:
-
Переход на Ambient-режим как более экономичную и простую в управлении модель развёртывания Istio. В частности, это упростит обновления: не нужно будет трогать деплои нагрузок вовсе.
-
Разбиение единой производственной сетки на несколько для разделения доменов отказа (fault domains), обеспечения более чётких границ изоляции безопасности и дальнейшего масштабирования Istio. С точки зрения обновлений это дополнительно сократит радиус взрыва: сетки, обслуживающие только низкорисковые нагрузки (например, staging), можно будет обновлять в первую очередь.
Если подобная работа вас интересует, приглашаем подать заявку на открытые вакансии.
Благодарности
Всё, чего мы достигли в работе с Istio, — заслуга многих людей: Джунхо Ана (Jungho Ahn), Стивена Чана (Stephen Chan), Вэйбо Хэ (Weibo He), Дугласа Джордана (Douglas Jordan), Брайана Вулфа (Brian Wolfe), Эди Ян (Edie Yang), Дасол Юн (Dasol Yoon) и Ин Чжу (Ying Zhu).
Все названия продуктов, логотипы и торговые марки являются собственностью соответствующих правообладателей. Все названия компаний, продуктов и сервисов на этом сайте используются исключительно в целях идентификации. Их использование не означает какого-либо одобрения.