etcd API

Oгляд центрального дизайну API etcd

Цей документ призначений для надання огляду центрального дизайну API v3 etcd. Це не слід плутати з API v2 etcd, який був визнаний застарілим з випуском etcd v3.5. Він не є всеосяжним, але має на меті зосередитися на основних ідеях, необхідних для розуміння etcd без відволікання на менш поширені виклики API. Усі API etcd визначені в gRPC сервісах, які категоризують віддалені виклики процедур (RPC), зрозумілі серверу etcd. Повний перелік усіх RPC etcd задокументований у markdown у списку gRPC API.

gRPC сервіси

Кожен запит API, надісланий на сервер etcd, є віддаленим викликом процедур gRPC. RPC в etcd категоризовані за функціональністю в сервіси.

Сервіси, важливі для роботи з ключовим простором etcd, включають:

  • KV — створює, оновлює, отримує та видаляє пари ключ-значення.
  • Watch — відстежує зміни ключів.
  • Lease — примітиви для споживання повідомлень keep-alive клієнта.

Сервіси, які керують самим кластером, включають:

  • Auth — механізм автентифікації на основі ролей для автентифікації користувачів.
  • Cluster — надає інформацію про членство та засоби конфігурації.
  • Maintenance — робить знімки для відновлення, дефрагментує сховище та повертає інформацію про стан кожного члена.

Запити та відповіді

Усі RPC в etcd слідують одному формату. Кожен RPC має функцію Name, яка приймає NameRequest як аргумент і повертає NameResponse як відповідь. Наприклад, ось опис RPC Range:

service KV { Range(RangeRequest) returns (RangeResponse) ... }

Заголовок відповіді

Усі відповіді від API etcd мають прикріплений заголовок відповіді, який включає метадані кластера для відповіді:

message ResponseHeader { uint64 cluster_id = 1; uint64 member_id = 2; int64 revision = 3; uint64 raft_term = 4; }
  • Cluster_ID — ID кластера, який генерує відповідь.
  • Member_ID — ID члена, який генерує відповідь.
  • Revision — ревізія сховища ключ-значення під час генерації відповіді.
  • Raft_Term — термін Raft члена під час генерації відповіді.

Застосунок може читати поле Cluster_ID або Member_ID, щоб переконатися, що він спілкується з потрібним кластером (членом).

Застосунки можуть використовувати поле Revision, щоб знати останню ревізію сховища ключ-значення. Це особливо корисно, коли застосунки вказують історичну ревізію для виконання запиту подорожі в часі і хочуть знати останню ревізію на момент запиту.

Застосунки можуть використовувати Raft_Term, щоб виявити, коли кластер завершує зробити нові вибори лідера.

Key-Value API

Key-Value API маніпулює парами ключ-значення, збереженими в etcd. Більшість запитів до etcd зазвичай є запитами ключ-значення.

Системні примітиви

Пара ключ-значення

Пара ключ-значення є найменшою одиницею, якою може маніпулювати API ключ-значення. Кожна пара ключ-значення має кілька полів, визначених у protobuf форматі:

message KeyValue { bytes key = 1; int64 create_revision = 2; int64 mod_revision = 3; int64 version = 4; bytes value = 5; int64 lease = 6; }
  • Key — ключ у байтах. Порожній ключ не дозволений.
  • Value — значення у байтах.
  • Version — версія ключа. Видалення скидає версію до нуля, а будь-яка модифікація ключа збільшує його версію.
  • Create_Revision — ревізія останнього створення ключа.
  • Mod_Revision — ревізія останньої модифікації ключа.
  • Lease — ID оренди, прикріпленої до ключа. Якщо lease дорівнює 0, то до ключа не прикріплена оренда.

Крім ключа та значення, etcd додає додаткові метадані ревізії як частину повідомлення ключа. Ця інформація про ревізії впорядковує ключі за часом створення та модифікації, що є корисним для керування паралелізмом при розподіленій синхронізації. Розподілені спільні блокування клієнта etcd використовують ревізію створення для очікування володіння блокуванням. Аналогічно, ревізія модифікації використовується для виявлення конфліктів програмної транзакційної памʼяті та очікування оновлень виборів лідера.

Ревізії

etcd підтримує 64-розрядний лічильник кластера, ревізію сховища, який збільшується щоразу, коли змінюється ключовий простір. Ревізія служить глобальним логічним годинником, послідовно впорядковуючи всі оновлення сховища. Зміна, представлена новою ревізією, є інкрементальною; дані, повʼязані з ревізією, є даними, які змінили сховище. Внутрішньо нова ревізія означає запис змін у B+дерево бекенду, ключоване за збільшеною ревізією.

Ревізії стають більш цінними, якщо врахувати багатоверсійний бекенд контролю паралелізму etcd. Модель MVCC означає, що сховище ключ-значення можна переглядати з минулих ревізій, оскільки історичні ревізії ключів зберігаються. Політика збереження цієї історії може бути налаштована адміністраторами кластера для тонкого керування сховищем; зазвичай etcd видаляє старі ревізії ключів за таймером. Типовий кластер etcd зберігає застарілі дані ключів протягом годин. Це також забезпечує надійне оброблення тривалих відключень клієнтів, а не лише тимчасових мережевих збоїв: спостерігачі просто відновлюються з останньої спостережуваної історичної ревізії. Аналогічно, щоб читати зі сховища в певний момент часу, запити на читання можуть бути позначені ревізією для повернення ключів з перегляду ключового простору в момент часу, коли ця ревізія була зафіксована.

Діапазони ключів

Модель даних etcd індексує всі ключі у пласкому двійковому ключовому просторі. Це відрізняється від інших систем сховищ ключ-значення, які використовують ієрархічну систему організації ключів у теках. Замість переліку ключів за текою, ключі отримуються за інтервалами ключів [a, b).

Ці інтервали часто називають “діапазонами” в etcd. Операції над діапазонами є більш потужними, ніж операції над теками. Як і в ієрархічному сховищі, інтервали підтримують одноразові пошуки ключів через [a, a+1) (наприклад, [‘a’, ‘a\x00’) шукає ‘a’) та пошуки тек шляхом кодування ключів за глибиною теки. Крім цих операцій, інтервали також можуть кодувати префікси; наприклад, інтервал ['a', 'b') шукає всі ключі з префіксом рядка ‘a’.

За угодою, діапазони для запиту позначаються полями key та range_end. Поле key є першим ключем діапазону і повинно бути непорожнім. range_end — це ключ, що слідує за останнім ключем діапазону. Якщо range_end не вказано або порожнє, діапазон визначається як такий, що містить лише аргумент ключа. Якщо range_end дорівнює ключу плюс один (наприклад, “aa”+1 == “ab”, “a\xff”+1 == “b”), то діапазон представляє всі ключі з префіксом ключа. Якщо і key, і range_end дорівнюють ‘\0’, то діапазон представляє всі ключі. Якщо range_end дорівнює ‘\0’, діапазон включає всі ключі, більші або рівні аргументу ключа.

Діапазон

Ключі отримуються зі сховища ключ-значення за допомогою виклику API Range, який приймає RangeRequest:

message RangeRequest { enum SortOrder { NONE = 0; // стандартно, без сортування ASCEND = 1; // спочатку найнижче цільове значення DESCEND = 2; // спочатку найвище цільове значення } enum SortTarget { KEY = 0; VERSION = 1; CREATE = 2; MOD = 3; VALUE = 4; } bytes key = 1; bytes range_end = 2; int64 limit = 3; int64 revision = 4; SortOrder sort_order = 5; SortTarget sort_target = 6; bool serializable = 7; bool keys_only = 8; bool count_only = 9; int64 min_mod_revision = 10; int64 max_mod_revision = 11; int64 min_create_revision = 12; int64 max_create_revision = 13; }
  • Key, Range_End — Діапазон ключів для отримання.
  • Limit — максимальна кількість ключів, що повертаються для запиту. Коли ліміт встановлено на 0, це розглядається як без обмежень.
  • Revision — момент часу сховища ключ-значення для використання для діапазону. Якщо ревізія менша або дорівнює нулю, діапазон охоплює останнє сховище ключ-значення. Якщо ревізія стиснута, ErrCompacted повертається як відповідь.
  • Sort_Order — порядок сортування для відсортованих запитів.
  • Sort_Target — поле ключ-значення для сортування.
  • Serializable — встановлює запит діапазону для використання серіалізованих локальних читань учасників. Стандартно, Range є лінеаризованим; він відображає поточний консенсус кластера. Для кращої продуктивності та доступності, в обмін на можливі застарілі читання, серіалізований запит діапазону обслуговується локально без необхідності досягати консенсусу з іншими вузлами в кластері.
  • Keys_Only — повертає лише ключі, а не значення.
  • Count_Only — повертає лише кількість ключів у діапазоні.
  • Min_Mod_Revision — нижня межа для модифікацій ключів; фільтрує менші модифікації.
  • Max_Mod_Revision — верхня межа для модифікацій ключів; фільтрує більші модифікації.
  • Min_Create_Revision — нижня межа для створення ключів; фільтрує менші створення.
  • Max_Create_Revision — верхня межа для створення ключів; фільтрує більші створення.

Клієнт отримує повідомлення RangeResponse від виклику Range:

message RangeResponse { ResponseHeader header = 1; repeated mvccpb.KeyValue kvs = 2; bool more = 3; int64 count = 4; }
  • Kvs — список пар ключ-значення, що відповідають запиту діапазону. Коли Count_Only встановлено, Kvs порожній.
  • More — вказує, чи є ще ключі для повернення в запитуваному діапазоні, якщо встановлено limit.
  • Count — загальна кількість ключів, що задовольняють запит діапазону.

Put

Ключі зберігаються в сховищі ключ-значення шляхом виклику Put, який приймає PutRequest:

message PutRequest { bytes key = 1; bytes value = 2; int64 lease = 3; bool prev_kv = 4; bool ignore_value = 5; bool ignore_lease = 6; }
  • Key — назва ключа для збереження в сховищі ключ-значення.
  • Value — значення, у байтах, для асоціації з ключем у сховищі ключ-значення.
  • Lease — ID lease для асоціації з ключем у сховищі ключ-значення. Значення lease 0 вказує на відсутність lease.
  • Prev_Kv — коли встановлено, відповідає даними пари ключ-значення перед оновленням з цього запиту Put.
  • Ignore_Value — коли встановлено, оновлює ключ без зміни його поточного значення. Повертає помилку, якщо ключ не існує.
  • Ignore_Lease — коли встановлено, оновлює ключ без зміни його поточної lease. Повертає помилку, якщо ключ не існує.

Клієнт отримує повідомлення PutResponse від виклику Put:

message PutResponse { ResponseHeader header = 1; mvccpb.KeyValue prev_kv = 2; }
  • Prev_Kv — пара ключ-значення, перезаписана Put, якщо Prev_Kv було встановлено в PutRequest.

Видалення діапазону

Діапазони ключів видаляються за допомогою виклику DeleteRange, який приймає DeleteRangeRequest:

message DeleteRangeRequest { bytes key = 1; bytes range_end = 2; bool prev_kv = 3; }
  • Key, Range_End — Діапазон ключів для видалення.
  • Prev_Kv — коли встановлено, повертає вміст видалених пар ключ-значення.

Клієнт отримує повідомлення DeleteRangeResponse від виклику DeleteRange:

message DeleteRangeResponse { ResponseHeader header = 1; int64 deleted = 2; repeated mvccpb.KeyValue prev_kvs = 3; }
  • Deleted — кількість видалених ключів.
  • Prev_Kv — список усіх пар ключ-значення, видалених операцією DeleteRange.

Транзакція

Транзакція є атомарною конструкцією If/Then/Else над сховищем ключ-значення. Вона надає примітив для групування запитів у атомарні блоки (тобто then/else), виконання яких охороняється (тобто if) на основі вмісту сховища ключ-значення. Транзакції можуть використовуватися для захисту ключів від ненавмисних одночасних оновлень, побудови операцій порівняння та заміни та розробки вищого рівня контролю конкурентності.

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

Всі транзакції захищаються сполучником порівняння, подібним до оператора If. Кожне порівняння перевіряє один ключ у сховищі. Воно може перевіряти відсутність або наявність значення, порівнювати з заданим значенням або перевіряти ревізію або версію ключа. Два різних порівняння можуть застосовуватися до одного або різних ключів. Усі порівняння застосовуються атомарно; якщо всі порівняння є істинними, транзакція вважається успішною, і etcd застосовує блок запитів then / success, інакше вона вважається невдалою і застосовує блок запитів else / failure.

Кожне порівняння кодується як повідомлення Compare:

message Compare { enum CompareResult { EQUAL = 0; GREATER = 1; LESS = 2; NOT_EQUAL = 3; } enum CompareTarget { VERSION = 0; CREATE = 1; MOD = 2; VALUE= 3; } CompareResult result = 1; // target - це поле ключ-значення для перевірки порівняння. CompareTarget target = 2; // key - це ключ для операції порівняння. bytes key = 3; oneof target_union { int64 version = 4; int64 create_revision = 5; int64 mod_revision = 6; bytes value = 7; } }
  • Result — тип логічної операції порівняння (наприклад, рівність, менше ніж тощо).
  • Target — поле ключ-значення для порівняння. Або версія ключа, або ревізія створення, або ревізія модифікації, або значення.
  • Key — ключ для порівняння.
  • Target_Union — дані, вказані користувачем для порівняння.

Після обробки блоку порівняння транзакція застосовує блок запитів. Блок — це список повідомлень RequestOp:

message RequestOp { // request - це обʼєднання типів запитів, прийнятих транзакцією. oneof request { RangeRequest request_range = 1; PutRequest request_put = 2; DeleteRangeRequest request_delete_range = 3; } }
  • Request_Range — RangeRequest.
  • Request_Put — PutRequest. Ключі повинні бути унікальними. Вони не можуть ділити ключі з іншими запитами Put або Delete.
  • Request_Delete_Range — DeleteRangeRequest. Вони не можуть ділити ключі з іншими запитами Put або Delete.

В цілому, транзакція видається за допомогою виклику API Txn, який приймає TxnRequest:

message TxnRequest { repeated Compare compare = 1; repeated RequestOp success = 2; repeated RequestOp failure = 3; }
  • Compare — Список предикатів, що представляють поєднання термінів для захисту транзакції.
  • Success — Список запитів для обробки, якщо всі тести порівняння оцінюються як істинні.
  • Failure — Список запитів для обробки, якщо будь-який тест порівняння оцінюється як хибний.

Клієнт отримує повідомлення TxnResponse від виклику Txn:

message TxnResponse { ResponseHeader header = 1; bool succeeded = 2; repeated ResponseOp responses = 3; }
  • Succeeded — Чи оцінюється Compare як істинний або хибний.
  • Responses — Список відповідей, що відповідають результатам застосування блоку Success, якщо succeeded є істинним, або блоку Failure, якщо succeeded є хибним.

Список Responses відповідає результатам застосування списку RequestOp, причому кожна відповідь кодується як ResponseOp:

message ResponseOp { oneof response { RangeResponse response_range = 1; PutResponse response_put = 2; DeleteRangeResponse response_delete_range = 3; } }

Заголовок відповіді, включений у кожну внутрішню відповідь, не слід інтерпретувати жодним чином. Якщо клієнтам потрібно отримати останню ревізію, вони завжди повинні перевіряти заголовок відповіді верхнього рівня у TxnResponse.

Watch API

API Watch надає інтерфейс на основі подій для асинхронного моніторингу змін ключів. Спостереження etcd чекає на зміни ключів, постійно спостерігаючи з даної ревізії, поточної або історичної, і передає оновлення ключів назад клієнту.

Події

Кожна зміна кожного ключа представлена повідомленнями Event. Повідомлення Event надає як дані оновлення, так і тип оновлення:

message Event { enum EventType { PUT = 0; DELETE = 1; } EventType type = 1; KeyValue kv = 2; KeyValue prev_kv = 3; }
  • Type — Тип події. Тип PUT вказує на нові дані, збережені в ключі. Тип DELETE вказує на видалення ключа.
  • KV — Пара KeyValue, повʼязана з подією. Подія PUT містить поточну пару kv. Подія PUT з kv.Version=1 вказує на створення ключа. Подія DELETE містить видалений ключ з його ревізією модифікації, встановленою на ревізію видалення.
  • Prev_KV — Пара ключ-значення для ключа з ревізії безпосередньо перед подією. Щоб заощадити пропускну здатність, вона заповнюється лише у випадку, якщо спостереження явно увімкнуло її.

Потоки спостереження

Спостереження є довготривалими запитами та використовують потоки gRPC для передачі даних подій. Потік спостереження є двонапрямним; клієнт записує в потік для встановлення спостережень і читає для отримання подій спостереження. Один потік спостереження може мультиплексувати багато різних спостережень, позначаючи події ідентифікаторами для кожного спостереження. Це мультиплексування допомагає зменшити обсяг памʼяті та накладні витрати на зʼєднання на основному кластері etcd.

Щоб дізнатися про гарантії, надані щодо подій спостереження, будь ласка, прочитайте гарантії API etcd.

Клієнт створює спостереження, надсилаючи WatchCreateRequest через потік, повернутий Watch:

message WatchCreateRequest { bytes key = 1; bytes range_end = 2; int64 start_revision = 3; bool progress_notify = 4; enum FilterType { NOPUT = 0; NODELETE = 1; } repeated FilterType filters = 5; bool prev_kv = 6; }
  • Key, Range_End — Діапазон ключів для спостереження.
  • Start_Revision — Необовʼязкова ревізія для початку спостереження включно. Якщо не вказано, він буде передавати події після ревізії заголовка відповіді на створення спостереження. Вся доступна історія подій може бути переглянута, починаючи з останньої ревізії стиснення.
  • Progress_Notify — Коли встановлено, спостереження періодично отримуватиме WatchResponse без подій, якщо не було останніх подій. Це корисно, коли клієнти бажають відновити відключене спостереження, починаючи з останньої відомої ревізії. Сервер etcd вирішує, як часто надсилати сповіщення на основі поточного навантаження сервера.
  • Filters — Список типів подій для фільтрації на стороні сервера.
  • Prev_Kv — Коли встановлено, спостереження отримує дані ключ-значення до події. Це корисно для знання, які дані були перезаписані.

У відповідь на WatchCreateRequest або якщо є нова подія для деякого встановленого спостереження, клієнт отримує WatchResponse:

message WatchResponse { ResponseHeader header = 1; int64 watch_id = 2; bool created = 3; bool canceled = 4; int64 compact_revision = 5; repeated mvccpb.Event events = 11; }
  • Watch_ID — ID спостереження, що відповідає відповіді.
  • Created — встановлено на true, якщо відповідь стосується запиту на створення спостереження. Клієнт повинен зберегти ID і очікувати отримання подій для спостереження на потоці. Усі події, надіслані створеному спостерігачу, матимуть однаковий watch_id.
  • Canceled — встановлено на true, якщо відповідь стосується запиту на скасування спостереження. Більше подій не буде надіслано скасованому спостерігачу.
  • Compact_Revision — встановлено на мінімальну історичну ревізію, доступну для etcd, якщо спостерігач намагається спостерігати за стиснутою ревізією. Це відбувається при створенні спостереження на стиснутій ревізії або якщо спостерігач не може наздогнати прогрес сховища ключ-значення. Спостереження буде скасовано; створення нових спостережень з тією ж start_revision не вдасться.
  • Events — список нових подій у послідовності, що відповідає даному watch ID.

Якщо клієнт бажає припинити отримання подій для спостереження, він видає WatchCancelRequest:

message WatchCancelRequest { int64 watch_id = 1; }
  • Watch_ID — ID спостереження для скасування, щоб більше не передавати події.

Lease API

Lease є механізмом для виявлення життєздатності клієнта. Кластер надає lease з часом життя. Lease закінчується, якщо кластер etcd не отримує keepAlive протягом заданого періоду TTL.

Щоб привʼязати оренди до сховища ключ-значення, кожен ключ може бути прикріплений до не більше ніж однієї оренди. Коли оренда закінчується або відкликається, усі ключі, прикріплені до цієї оренди, будуть видалені. Кожен прострочений ключ генерує подію видалення в історії подій.

Отримання lease

Lease отримуються через виклик API LeaseGrant, який приймає LeaseGrantRequest:

message LeaseGrantRequest { int64 TTL = 1; int64 ID = 2; }
  • TTL — рекомендований час життя, у секундах.
  • ID — запитуваний ID для оренди. Якщо ID встановлено на 0, etcd вибере ID.

Клієнт отримує LeaseGrantResponse від виклику LeaseGrant:

message LeaseGrantResponse { ResponseHeader header = 1; int64 ID = 2; int64 TTL = 3; }
  • ID — ID оренди для наданої оренди.
  • TTL — це вибраний сервером час життя, у секундах, для оренди.
message LeaseRevokeRequest { int64 ID = 1; }
  • ID — ID оренди для відкликання. Коли оренда відкликається, усі прикріплені ключі видаляються.

Keep alives

Оренди оновлюються за допомогою двонапрямного потоку, створеного викликом API LeaseKeepAlive. Коли клієнт бажає оновити оренду, він надсилає LeaseKeepAliveRequest через потік:

message LeaseKeepAliveRequest { int64 ID = 1; }
  • ID — ID оренди для підтримки життя.

Потік keep alive відповідає LeaseKeepAliveResponse:

message LeaseKeepAliveResponse { ResponseHeader header = 1; int64 ID = 2; int64 TTL = 3; }
  • ID — оренда, яка була оновлена новим TTL.
  • TTL — новий час життя, у секундах, який залишився для оренди.