Практика разделения Etcd крупномасштабного кластера Sigma Ant

программист Эксплуатация и техническое обслуживание Kubernetes
Практика разделения Etcd крупномасштабного кластера Sigma Ant

Текст|Du Kewei (название цветка: Su Lin)

Старший инженер-разработчик, Ant Group

Отвечает за стабильность кластера ant Kubernetes Фокус на изменения компонентов кластера, обеспечение стабильности рисков

Эта статья 15738 слов читается 20 минут

Предисловие

Чтобы поддержать итеративное обновление бизнеса Ant, в этом году Ant Infrastructure запустила комплексный проект облачной среды Gzone. Требуется, чтобы Gzone была развернута в одном кластере вместе с облачной Rzone.Масштаб узлов, управляемых одним кластером Sigma, превысит 10 000, а бизнес, выполняемый одним кластером, будет более сложным.

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

Поскольку база данных хранилища данных кластера Sigma, etcd является краеугольным камнем всего кластера и может напрямую определять потолок производительности. Предел хранения одного кластера etcd, предложенный сообществом, составляет 8G, и емкость хранилища одного кластера etcd кластера Ant Sigma уже превысила этот предел.Облачный проект Gzone неизбежно увеличит нагрузку на etcd.

Во-первых, муравьиный бизнес смешивает churn-вычисления, оффлайн-вычисления и онлайн-бизнес, а также смешивает большое количество Pod’ов с жизненным циклом в минуты или даже секунды.Количество Pod’ов, создаваемых в день в одном кластере, также увеличилось до сотен. из тысяч, все из которых нуждаются в поддержке etcd;

Во-вторых, сложные бизнес-требования породили большое количество запросов List (список всех, список по пространству имен, список по метке), просмотр, создание, обновление, удаление.В соответствии с характеристиками хранилища etcd производительность этих запросов будет варьироваться в зависимости от масштаб хранилища etcd.Увеличивается и серьезно ослабляется, и даже вызывает etcd OOM, тайм-аут запроса и другие аномалии;

Наконец, увеличение количества запросов также усугубило всплеск запросов etcd на RT P99 из-за операций сжатия и дефрагментации и даже тайм-аутов запросов, что приводило к периодической потере ключевых компонентов кластера, таких как планировщик, служба CNI и другие операции оператора. type, что приводит к недоступности кластера.

Согласно предыдущему опыту, эффективным методом оптимизации является горизонтальное разделение данных для кластеров etcd.Обычное разделение заключается в хранении важных данных, таких как поды, в отдельном кластере etcd, тем самым снижая нагрузку на единое хранилище etcd и обработку запросов, а также уменьшая количество запросов. задержки. Однако данные ресурсов Pod являются особенными для кластеров Kubernetes и предъявляют высокие требования, которых нет у других ресурсов.В частности, необходимо быть очень осторожным при разделении кластеров K8s, которые уже обслуживают большие масштабы.

В этой статье в основном описываются некоторые практические опыты и идеи Ant Group в процессе разделения данных ресурсов Pod.

Бросая кирпичи и привлекая нефрит, пожалуйста, дайте нам много советов!

ЧАСТЬ 1 ПРОБЛЕМЫ

Из предыдущего опыта разделения данных подов мы знаем, что разделение данных подов — это сложный и сопряженный с высоким риском процесс, и причина этого заключается в специфике самих данных подов.

Pod — это комбинация группы контейнеров, наименьшей планируемой единицы в кластере Sigma и конечного носителя бизнес-нагрузок. Основным и конечным ресурсом доставки кластера Sigma является ресурс Pod.

Основным SLO кластера Sigma также являются такие индикаторы, как создание, удаление и обновление пода. Можно сказать, что данные ресурсов пода являются наиболее важными данными ресурсов кластера Sigma. В то же время кластер Sigma управляется событиями и предназначен для системы конечного состояния, поэтому, в дополнение к основным проблемам согласованности данных до и после разделения данных ресурса Pod, необходимо также учитывать влияние на другие компоненты в процессе разделения. обдуманный.

Основными операциями в предыдущем процессе разделения опыта являются проверка целостности данных и отключение ключевых компонентов службы. Как следует из названия, проверка целостности данных предназначена для обеспечения согласованности данных до и после, а отключение ключевых компонентов службы — для предотвращения непредвиденных последствий, если компоненты не будут отключены во время процесса разделения, и может произойти неожиданное удаление модулей. , Статус Pod уничтожен и т. д. . Но если вы скопируете этот процесс в кластер ant Sigma, возникнет проблема.

Ant Sigma — это основная инфраструктура Ant Group, которая за более чем 2 года разработки превратилась в облачную базу с более чем 80 кластерами, а количество узлов в одном кластере может достигать масштаба 1,2w+. В таком крупномасштабном кластере внутри Ant работают миллионы подов, а количество краткосрочных подов, создаваемых каждый день, составляет 20+ раз. Чтобы удовлетворить различные потребности в развитии бизнеса, команда Sigma сотрудничает с несколькими облачными командами, такими как Ant Storage, Network и PaaS.На сегодняшний день Sigma создала сотни сторонних компонентов. Если модуль разделен для перезапуска компонентов, требуется много работы по обмену информацией с бизнес-стороной, и несколько человек должны работать вместе. Если все сделано небрежно, неполное расчесывание и отсутствие нескольких компонентов может привести к непредвиденным последствиям.

在这里插入图片描述

Суммируйте существующий процесс разделения данных Pod из текущей ситуации кластера ant Sigma:

  1. Ручное управление большим количеством компонентов требует много времени для перезапуска и подвержено ошибкам

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

  1. Большая продолжительность перерывов полного простоя SLO

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

  1. Слабые методы проверки целостности данных

В процессе разделения для переноса данных используется инструмент make-mirror с открытым исходным кодом etcd. ревизия важного поля исходного ключа, подлежащего уничтожению, что влияет на resourceVersion данных Pod, что может привести к непредвиденным последствиям. Подробнее о ревизии будет рассказано позже. Окончательный метод проверки заключается в проверке соответствия количества ключей, если данные промежуточного ключа повреждены, его невозможно найти.

ЧАСТЬ 2 Анализ проблемы

хорошие ожидания

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

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

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

在这里插入图片描述

Что делает разделение данных?

Как мы все знаем, etcd хранит в кластере Kubernetes различные данные о ресурсах, такие как Pod, Services, Configmaps, Deployments и т. д.

По умолчанию все данные ресурсов Kube-apiserver хранятся в кластере etcd.По мере увеличения масштаба хранилища кластер etcd будет сталкиваться с узкими местами в производительности. Это эмпирическая идея оптимизации, принятая в отрасли, для разделения данных etcd на основе измерения ресурсов для повышения производительности доступа Kube-apiserver к etcd Суть заключается в уменьшении размера данных одного кластера etcd и уменьшении QPS доступа для один кластер etcd.

В соответствии с масштабом и требованиями самого кластера Ant Sigma его необходимо разделить на 4 независимых кластера etcd, в которых соответственно хранятся Pods, Leases, события и другие данные о ресурсах.

在这里插入图片描述

Ресурс события

Данные ресурса события K8s не являются событием в контроле, но обычно представляют собой событие, которое происходит на связанном объекте, например, получение изображений Pod, запуск контейнера и т. д. В бизнесе CI/CD обычно требуется отображать временную шкалу состояния в упрощенном виде и часто извлекать данные ресурсов событий.

Сами данные ресурса события действительны (по умолчанию 2 часа).За исключением наблюдения за изменениями жизненного цикла объекта ресурса через событие, как правило, нет важной бизнес-зависимости.Поэтому данные события обычно считаются отбрасываемыми, и нет необходимости обеспечивать согласованность данных.

Из-за вышеуказанных характеристик данных разделение событий является самым простым. Вам нужно только изменить начальную конфигурацию APIServer и перезапустить APIServer. Нет необходимости выполнять миграцию данных или очищать старые данные. За исключением Kube-apiserver, весь процесс разделения не требует перезапуска компонента или изменения конфигурации.

Аренда ресурсов

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

Каждый kubelet использует объект Lease для создания отчетов пульса, которые по умолчанию отправляются каждые 10 секунд. Чем больше узлов, тем больше запросов на обновление берет на себя etcd.Количество обновлений в минуту аренды узлов в 6 раз превышает общее количество узлов.10 000 узлов – это 60 000 раз в минуту, что все равно очень впечатляет. Обновление ресурса аренды очень важно для оценки того, готов ли узел, поэтому он выделяется отдельно.

Основная логика выбора компонентов класса контроллера в основном представляет собой используемый пакет основного кода выбора с открытым исходным кодом, то есть все компоненты, использующие основную логику выбора аренды, представляют собой унифицированную основную логику выбора. Логика кода отчетов пульса Kubelet находится под нашим контролем. Из анализа кода видно, что лизинговые ресурсы не требуют строгой согласованности данных, необходимо только следить за тем, чтобы лизинговые данные обновлялись в течение определенного периода времени, что не повлияет на нормальное функционирование компонентов с использованием лизинга.

Время по умолчанию для Kubelet, чтобы определить, находится ли логика Ready в диспетчере контроллеров, составляет 40 секунд, то есть, если соответствующий ресурс Lease был обновлен в течение 40 секунд, он не будет оцениваться как NotReady. Кроме того, время 40 с можно настроить, пока оно обновляется в это время, это не повлияет на нормальную работу. Основной выбор Продолжительность аренды компонента класса контроллера, который использует основной выбор, обычно составляет 5–65 с, что можно установить самостоятельно.

Таким образом, хотя разделение ресурсов аренды сложнее, чем событие, оно также относительно просто. Дополнительным шагом является то, что в процессе разделения данные ресурса аренды в старом etcd необходимо синхронизировать с новым кластером etcd.Как правило, мы используем инструмент make-mirror etcdctl для синхронизации данных. В это время, если компонент обновляет объект Lease, запрос может попасть в старый etcd или в новый etcd. Обновления, приземлившиеся в старом etcd, будут синхронизированы с новым etcd с помощью инструмента make-mirror, а поскольку объектов Lease меньше, весь процесс длится короткий период времени, и проблем не возникает. Кроме того, необходимо удалить данные ресурса Lease в старом etcd после завершения миграции и разделения, чтобы освободить пространство, занимаемое блокировкой.Хотя место мало, не тратьте его зря. Подобно разделению ресурсов событий, весь процесс разделения также не требует перезапуска или изменения конфигурации каких-либо компонентов, кроме kube-apiserver.

Ресурсы модуля

Ресурсы модуля могут быть наиболее знакомыми данными о ресурсах, и все рабочие нагрузки в конечном итоге выполняются модулями. Ядром управления кластером K8s является планирование и управление ресурсами Pod. Данные ресурсов пода требуют строгой согласованности данных, и нельзя пропускать события наблюдения, генерируемые любым обновлением пода, иначе это может повлиять на доставку ресурсов пода. Характеристики ресурсов Pod также являются причиной необходимости масштабного перезапуска соответствующих компонентов в процессе традиционного разделения данных ресурсов Pod.Причины будут проанализированы позже.

Сам компонент сообщества kube-apiserver уже имеет конфигурацию --etcd-servers-overrides для установки отдельного хранилища etcd по типам ресурсов.

--etcd-servers-overrides strings Per-resource etcd servers overrides, comma separated. The individual override format: group/resource#servers, where servers are URLs, semicolon separated. Note that this applies only to resources compiled into this server binary.

Краткий пример конфигурации нашего общего разделения ресурсов выглядит следующим образом:

конфигурация разделения событий

--etcd-servers-overrides=/events#1.события и т.д. Хи Хи: 2 Хи Хи 2.даже https://и т.д…

аренда раздельной конфигурации

--etcd-servers-overrides=coordination.k8s.io/leases#1.аренда и т. д. хи хи: 2 хи хи 2. аренда https://etc…

pods разделенная конфигурация

--etcd-servers-overrides=/pods#1.pods.heehee.net:2heehee;https://etc 2.pods…

Нужен ли перезапуск компонента?

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

Так почему возникает эта проблема? Чтобы ответить на этот вопрос, нам нужно четко объяснить все, от основной концепции дизайна K8s до конкретных деталей реализации.Давайте углубимся в детали.

Если K8s — это обычная бизнес-система, разделение данных ресурсов Pod влияет только на место хранения, где kube-apiserver обращается к ресурсам Pod, то есть, если воздействие только на уровне kube-apiserver, этой статьи не будет.

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

Однако K8s — это совсем другой фейерверк!

在这里插入图片描述

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

Компоненты расширения предназначены для конечного состояния. В конечном состоянии в основном есть две концепции состояния: Желаемое состояние и Текущее состояние.Все объекты в кластере имеют желаемое состояние и текущее состояние.

  • Желаемое состояние — это просто конечное состояние, описываемое данными Yaml объекта, который мы отправляем в кластер;

  • Текущее состояние — это фактическое состояние объекта в кластере.

Запросы данных, которые мы используем, такие как создание, обновление, исправление, удаление и т. д., — это все действия по модификации, которые мы выполняем для конечного состояния, выражая наши ожидания в отношении конечного состояния.После выполнения этих действий текущее состояние кластера и желаемое состояния различны. , различные компоненты расширения Операторов (Контроллеров) в кластере постоянно настраиваются (Reconclie) через разницу между ними и переводят объект из текущего состояния в конечное состояние.

在这里插入图片描述

Текущие компоненты класса Operators в основном разрабатываются с использованием фреймворков с открытым исходным кодом, поэтому можно считать, что логика кода их работающих компонентов является последовательной и унифицированной. Внутри компонента Operator окончательное конечное состояние заключается в получении данных yaml объекта конечного конечного состояния путем отправки запроса List в kube-apiserver, но для уменьшения нагрузки на kube-apiserver запрос List выполняется только один раз, когда компонент запускается (если нет ненормальной ожидаемой ошибки), если есть какие-либо изменения в объекте данных конечного состояния yaml, сообщение о событии (WatchEvent) будет активно отправлено оператору через kube-apiserver.

С этой точки зрения также можно сказать, что кластер K8s является конечным состоянием, управляемым событиями.

在这里插入图片描述

Поток сообщений WatchEvent между оператором и kube-apiserver должен гарантировать, что ни одно событие не будет потеряно.Данные yaml, возвращаемые первоначальным запросом списка, плюс событие изменения WatchEvent — это конечное состояние, которое должен увидеть оператор, и это также желаемое пользователем состояние. Важной концепцией для обеспечения того, чтобы события не были потеряны, является resourceVersion.

Это поле есть у каждого объекта в кластере, даже у ресурсов, определенных пользователем через CRD (CustomResourceDefinition).

Дело в том, что упомянутая выше resourceVersion тесно связана с уникальной функцией (ревизией) самого хранилища etcd, особенно для запросов списка, которые активно используются операторами. Разделение данных и перенос их в новый кластер хранения etcd напрямую повлияет на resourceVersion объектов ресурсов.

Итак, снова возникает вопрос, что такое редакция etcd? Какова связь с resourceVersion объекта ресурса K8s?

3 версии Etcd

В Etcd есть три вида ревизий, а именно, ревизия, CreateRevision и ModRevision.Взаимосвязь и характеристики этих трех видов ревизий резюмируются следующим образом:

Когда ключ-значение записывается или обновляется, будет поле Revision, и оно гарантированно будет строго увеличиваться, что на самом деле является логическими часами MVCC в etcd.

在这里插入图片描述

K8s ResourceVersion и Etcd Revision

Каждый объект, выходящий из kube-apiserver, должен иметь поле resourceVersion, которое можно использовать для определения того, изменился ли объект, и контроля параллелизма.

Более подробную информацию можно увидеть в комментариях к коду:

// ObjectMeta is metadata that all persisted resources must have, which includes all objects
// users must create.
type ObjectMeta struct {  
    ...// omit code here
    // An opaque value that represents the internal version of this object that can
  // be used by clients to determine when objects have changed. May be used for optimistic
  // concurrency, change detection, and the watch operation on a resource or set of resources.
  // Clients must treat these values as opaque and passed unmodified back to the server.
  // They may only be valid for a particular resource or set of resources.
  //
  // Populated by the system.
  // Read-only.
  // Value must be treated as opaque by clients and .
  // More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency
  // +optional
  ResourceVersion string `json:"resourceVersion,omitempty" protobuf:"bytes,6,opt,name=resourceVersion"`
    ...// omit code here
}

Операции записи create, update, patch и delete в глаголах запроса kube-apiserver обновят ревизию в etcd, точнее говоря, вызовут рост ревизии.

Соответствующая связь между полем resourceVersion в объекте ресурса в K8s и различными ревизиями в etcd резюмируется следующим образом:

在这里插入图片描述

Среди всех запросов и ответов kube-apiserver особое внимание следует уделить ответу List. ResourceVersion запроса списка — это заголовок etcd.Revision, который является логическими часами MVCC etcd.Операция записи в любой ключ в etcd вызывает монотонное увеличение версии, что, в свою очередь, влияет на значение resourceVersion в запросе списка. отклик.

Например, даже если для ресурсов Pod в test-namespace нет действия модификации, если List Pods находится в test-namespace, resourceVersion в ответе, вероятно, будет увеличиваться каждый раз (поскольку другие ключи в etcd имеют операции записи).

В нашем непрерывном разделении данных пода компонента мы запрещаем только операцию записи пода, а другие данные не запрещены.Во время непрерывного действия обновления конфигурации kube-apiserver это неизбежно приведет к тому, что редакция старого etcd будет намного больше, чем хранилище Pod.new etcd данных. Это вызвало серьезные несоответствия до и после разделения списка resourceVersion.

Значение resourceVersion является ключом к тому, чтобы события не терялись в Operator. Поэтому разбиение данных etcd влияет не только на kube-apiserver, но и на многие компоненты класса Operator.Потеря события change вызовет такие проблемы, как сбой доставки Pod и грязные данные.

在这里插入图片描述

在这里插入图片描述

До сих пор, хотя мы узнали, что список resourceVersion, полученный оператором, несовместим, список resourceVersion, возвращаемый из старого etcd, больше, чем из нового etcd, так какое это имеет отношение к оператору, теряющему событие обновления пода? ?

Чтобы ответить на этот вопрос, нам нужно начать с ListAndWatch в дизайне совместной работы компонентов K8s, который должен быть на стороне клиента Client-go и на стороне сервера kube-apiserver.

ListAndWatch в Client-go

Мы все знаем, что компоненты Operator реагируют на события благодаря пакету кода Client-go с открытым исходным кодом.

在这里插入图片描述

Схематическая диаграмма событий объекта данных Client-go в Operator

Основным ключом является метод ListAndWatch, который гарантирует, что клиент не потеряет resourceVersion события, полученного через запрос списка в этом методе.

ListAndWatch выведет список всех объектов в первый раз, получит номер версии объекта ресурса, а затем просмотрит номер версии объекта ресурса, чтобы увидеть, был ли он изменен. Во-первых, номер версии ресурса установлен на 0, а list() может вызвать задержку в локальном кеше относительно содержимого в etcd. Reflector дополнит задержанную часть с помощью метода watch, чтобы данные локального кэша согласовывались с данными etcd.

Код ключа следующий:

// Run repeatedly uses the reflector's ListAndWatch to fetch all the
// objects and subsequent deltas.
// Run will exit when stopCh is closed.
func (r *Reflector) Run(stopCh <-chan struct{}) {
  klog.V(2).Infof("Starting reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
  wait.BackoffUntil(func() {
    if err := r.ListAndWatch(stopCh); err != nil {
      utilruntime.HandleError(err)
    }
  }, r.backoffManager, true, stopCh)
  klog.V(2).Infof("Stopping reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
}
// ListAndWatch first lists all items and get the resource version at the moment of call,
// and then use the resource version to watch.
// It returns error if ListAndWatch didn't even try to initialize watch.
func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
  var resourceVersion string
  // Explicitly set "0" as resource version - it's fine for the List()
  // to be served from cache and potentially be delayed relative to
  // etcd contents. Reflector framework will catch up via Watch() eventually.
  options := metav1.ListOptions{ResourceVersion: "0"}

  if err := func() error {
    var list runtime.Object
      ... // omit code here
    listMetaInterface, err := meta.ListAccessor(list)
      ... // omit code here
    resourceVersion = listMetaInterface.GetResourceVersion()
        ... // omit code here
    r.setLastSyncResourceVersion(resourceVersion)
    ... // omit code here
    return nil
  }(); err != nil {
    return err
  }
    ... // omit code here
  for {
        ... // omit code here
    options = metav1.ListOptions{
      ResourceVersion: resourceVersion,
      ... // omit code here
    }
    w, err := r.listerWatcher.Watch(options)
        ... // omit code here
    if err := r.watchHandler(w, &resourceVersion, resyncerrc, stopCh); err != nil {
        ... // omit code here
      return nil
    }
  }
}

Сгруппировано в блок-схему, чтобы было понятнее:

在这里插入图片描述

Смотреть обработку в kube-apiserver

После прочтения логики обработки клиента, давайте посмотрим на обработку сервера.Ключевым является обработка запроса наблюдения kube-apiserver.Для каждого запроса наблюдения kube-apiserver создаст новый наблюдатель и запустит горутину watchServer для обслуживания запроса на просмотр. Отправляйте сообщения о событиях ресурса клиенту на этот вновь созданный сервер watchServer.

在这里插入图片描述

Но вот в чем дело, параметр watchRV в клиентском запросе на просмотр берется из ответа List в Client-go, а kube-apiserver отправляет клиенту только сообщения о событиях большего размера, чем watchRV.В процессе разделения клиентский watchRV может быть намного больше. чем у kube — resourceVersion события, локального для apiserver, что является основной причиной потери клиентом сообщения о событии обновления Pod.

С этой точки зрения необходимо перезапустить компонент «Оператор».Перезапуск компонента может вызвать повторный список Client-go и получить последнюю версию списка подов, чтобы не потерять сообщение о событии обновления пода.

在这里插入图片描述

ЧАСТЬ 3 Проблема решена

Решить проблему с перезагрузкой

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

Проблема перезапуска компонентов в основном затрагивает две темы: Client-go на стороне клиента и kube-apiserver на стороне сервера, поэтому для решения проблемы мы можем начать с этих двух тем и искать точку прорыва проблемы.

Прежде всего, для клиента Client-go ключевой момент — позволить ListAndWatch повторно инициировать запрос List для получения последней версии resourceVersion kube-apiserver, чтобы не потерять последующие сообщения о событиях. Если Client-go может снова запросить обновление локальной resourceVersion через List в определенное время, проблема будет решена, но если код client-go изменен, его все равно нужно выпустить и перезапустить, чтобы он вступил в силу, тогда проблема как его не использовать. Изменив код Client-go, вы можете повторно инициировать запрос списка.

Мы повторно просматриваем логический поток ListAndWatch и можем обнаружить, что ключ к оценке того, нужно ли инициировать запрос List, заключается в оценке возвращаемой ошибки метода Watch. Ошибка, возвращаемая методом watch, определяется по ответу kube-apiserver на запрос watch.Давайте сосредоточимся на kube-apiserver на стороне сервера.

在这里插入图片描述

Различная обработка запросов на просмотр

Обработка запросов на просмотр в kube-apiserver была представлена ​​выше.Мы можем достичь нашей цели, изменив процесс обработки запросов на просмотр в kube-apiserver для достижения взаимного сотрудничества с Client-go.

Из вышеизложенного мы знаем, что watchRV Client-go намного больше, чем resourceVersion в локальном кэше наблюдения kube-apiserver.В соответствии с этой функцией kube-apiserver может отправить указанную ошибку (TooLargeResourceVersionError), чтобы вызвать действие повторного списка Client-go. Компонент kube-apiserver неизбежно требует перезапуска, после обновления конфигурации можно выполнять логику нашей трансформации.

Логика трансформации представлена ​​следующим образом:

在这里插入图片描述

Техническая гарантия согласованности данных

Предыдущий опыт заключается в реализации переноса данных через инструмент etcd make-mirror, преимущество в том, что он прост и удобен, а инструмент с открытым исходным кодом можно использовать из коробки. Недостаток в том, что эта работа проста в реализации, то есть прочитать ключ с одного etcd, а потом переписать его на другой etcd, не поддерживает возобновление передачи в точке останова и не дружит с миграцией больших объемов данных и занимает много времени. Кроме того, информация createRevision в ключе etcd также уничтожается. Поэтому после завершения миграции требуется строгое тестирование целостности данных.

В ответ на вышеуказанную проблему мы можем изменить наш образ мышления.Наша суть заключается в том, чтобы сделать миграцию данных.Структура хранения (KeyValue) самого etcd особенная, и мы надеемся сохранить целостность данных до и после. Поэтому я подумал об инструменте моментальных снимков etcd.Инструмент моментальных снимков изначально использовался для аварийного восстановления etcd, то есть новый экземпляр etcd можно воссоздать, используя данные моментального снимка etcd. И данные, проходящие через снапшот, могут поддерживать целостность исходного значения ключа в новом etcd, чего мы и хотим.

// etcd KeyValue 数据结构
type KeyValue struct {
  // key is the key in bytes. An empty key is not allowed.
  Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"`
  // create_revision is the revision of last creation on this key.
  CreateRevision int64 `protobuf:"varint,2,opt,name=create_revision,json=createRevision,proto3" json:"create_revision,omitempty"`
  // mod_revision is the revision of last modification on this key.
  ModRevision int64 `protobuf:"varint,3,opt,name=mod_revision,json=modRevision,proto3" json:"mod_revision,omitempty"`
  // version is the version of the key. A deletion resets
  // the version to zero and any modification of the key
  // increases its version.
  Version int64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"`
  // value is the value held by the key, in bytes.
  Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"`
  // lease is the ID of the lease that attached to key.
  // When the attached lease expires, the key will be deleted.
  // If lease is 0, then no lease is attached to the key.
  Lease int64 `protobuf:"varint,6,opt,name=lease,proto3" json:"lease,omitempty"`
}

Отсечение данных миграции

Хотя данные моментального снимка etcd имеют целостность KeyValue, которую мы хотим, данные, хранящиеся в реконструированном etcd, — это все данные старого etcd, а это не то, что нам нужно. Конечно, мы можем запустить очистку избыточных данных после создания etcd, но это не лучший способ.

Мы можем реализовать отсечение данных в процессе создания моментального снимка, преобразовав инструмент моментального снимка etcd. В модели хранения etcd есть список сегментов. Бакеты — это концепция хранения etcd. Соответствуя реляционной базе данных, ее можно рассматривать как таблицу, и каждый ключ в ней соответствует строке в таблице. Самая важная корзина — это корзина с именем key, в которой хранятся все объекты ресурсов в K8s. Ключи всех объектов ресурсов в K8s имеют фиксированный формат, в соответствии с категорией ресурса и пространством имен каждый ресурс имеет фиксированный префикс. Например, префикс данных Pod — /registry/Pods/. В процессе создания снимка мы можем различать данные Pod на основе этого префикса и вырезать данные, не относящиеся к Pod.

Кроме того, в соответствии с характеристиками etcd, размер хранилища etcd для данных моментального снимка равен размеру файла жесткого диска etcd.Есть два значения: общий размер db и размер db inuse.Размер общего размера db – это размер файла хранилища, занимаемого etcd на жестком диске., который содержит много данных, ставших ненужными ключами, но не очищенными. Размер используемого размера базы данных — это общий размер всех доступных данных. Когда метод дефрагментации etcd используется нечасто для организации дискового пространства, значение total обычно намного больше, чем значение inuse.

При отсечении данных, даже если мы обрезаем данные, отличные от Pod, данные всего моментального снимка не изменятся.На данный момент нам нужно использовать метод дефрагментации, чтобы освободить избыточное пространство для хранения.

На приведенной ниже диаграмме вы можете увидеть процесс изменения общего количества db.Окончательный размер данных снимка, который мы получаем, — это размер данных Pod, что очень важно для нас, чтобы сэкономить время передачи данных.

在这里插入图片描述

在这里插入图片描述

Запретная яма

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

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

// 第一个版本配置,有问题
apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: deny-pods-write
webhooks:
- admissionReviewVersions:
  - v1beta1
  clientConfig:
    url: https://extensions.xxx/always-deny
  failurePolicy: Fail
  name: always-deny.extensions.k8s
  namespaceSelector: {}
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - "*"
    resources:
    - pods
    scope: '*'  
  sideEffects: NoneOnDryRun

После расследования было обнаружено, что поле состояния Pod было обновлено.Прочитав код apiserver, мы обнаружили, что ресурс, связанный с хранилищем Pod, является не только одним Pod, но и следующими типами.Pod status и Pod разные для хранения apiserver Ресурсы.

"pods":             podStorage.Pod,
"pods/attach":      podStorage.Attach,
"pods/status":      podStorage.Status,
"pods/log":         podStorage.Log,
"pods/exec":        podStorage.Exec,
"pods/portforward": podStorage.PortForward,
"pods/proxy":       podStorage.Proxy,
"pods/binding":     podStorage.Binding,

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

Это небольшая яма, записанная здесь.

apiVersion: admissionregistration.k8s.io/v1
kind: MutatingWebhookConfiguration
metadata:
  name: deny-pods-write
webhooks:
- admissionReviewVersions:
  - v1beta1
  clientConfig:
    url: https://extensions.xxx/always-deny
  failurePolicy: Fail
  name: always-deny.extensions.k8s
  namespaceSelector: {}
  rules:
  - apiGroups:
    - ""
    apiVersions:
    - v1
    operations:
    - "*"
    resources:
    - pods
    - pods/status
    - pods/binding
    scope: '*'  
  sideEffects: NoneOnDryRun

окончательный процесс разделения

После решения предыдущих задач вышел наш окончательный процесс расщепления.

Индикация следующая:

在这里插入图片描述

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

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

Никаких перезапусков компонентов не требуется, за исключением того, что kube-apiserver не может избежать необходимости обновлять перезапуск конфигурации хранилища. В то же время это также экономит много времени при общении с владельцами компонентов, а также позволяет избежать многих неопределенностей во многих операциях.

Один человек может справиться со всем процессом расщепления.

ЧАСТЬ 4 Заключительное резюме

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

Настоящий процесс знакомит со всем мыслительным процессом и ключевыми моментами реализации.

在这里插入图片描述

Весь мыслительный процесс и ключевые моменты реализации

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

Знание «почему» является обязательным на большинстве рабочих мест, и хотя это занимает много нашего времени, оно того стоит.

В заключение старой поговоркой:

Используйте его с одним сердцем Поделиться с тобой.

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

(1) [лимит хранилища etcd]:

etc.io/docs/v3.3/…

(2) [и т. д. снимок]:

etc.io/docs/v3.3/ о...

(3) [Восхождение на вершину масштаба — практика оптимизации крупномасштабного кластера Sigma ApiServer от Ant Group]: