Перейти до вмісту

Блог ·

Як агент обирає правильний периферійний вузол

DNS з кількома записами A, периферія на четвертому рівні й PROXY protocol v2, з вимірами: що клієнт робить із кількома адресами і чому повернення складніше.

Автор: Provibr

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

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

Проблемою є адреса, а не трафік

Імʼя хоста шлюзу розвʼязувалося в джерело. Його знав кожен зареєстрований агент, і знав кожен, хто вміє набрати dig. Обʼємна повінь, спрямована на ту адресу, кладе площину керування для всіх, і зробити щось розумне постфактум неможливо.

Мета була вузькою, і її формулювання тримає задум чесним: адреса джерела зникає зі шляху, яким користуються агенти, тож повінь бʼє в машину, яку можна замінити за десять хвилин. Захищати вміст не було метою периферії. Взаємний TLS уже робить це наскрізно, а периферійний вузол, здатний зазирнути в трафік, був би пониженням.

Четвертий рівень, бо термінація TLS зламала б те, що важить

Очевидним задумом є зворотний проксі, що термінує TLS на периферії. Тут він неправильний, і на те є дві причини, властиві саме такому типу систем.

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

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

Це коштує однієї речі. Кожне зʼєднання тепер має вигляд такого, що походить із периферії, тож джерело більше не знає, хто телефонує.

PROXY protocol v2 і різниця між заявою та виміром

Стандартним виправленням є PROXY protocol: пересилач додає до кожного зʼєднання невеликий двійковий заголовок з початковою адресою. Шлюз розбирає його і повертає справжню адресу у свої журнали.

Проєктне рішення, яке варто скопіювати, полягає в тому, щоб тримати два значення, а не одне.

  • Адреса із заголовка це те, чим другий кінець називає клієнта. Це заява.
  • Зовнішня адреса сокета, машина, з якої байти справді прийшли, є тим, що ми виміряли самі.

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

Дві нотатки з реалізації, що зберегли нам нерви:

Винюхуйте, не вимагайте. Перехід на PROXY protocol не є атомарним. Якщо перші байти мають вигляд TLS ClientHello, а не сигнатури протоколу, поверніть їх у потік і перешліть зʼєднання незайманим. Так розгортання розбирача й вмикання його на пересилачах стають двома незалежними кроками.

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

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

Виявлення: набір A і є списком

Як агент дізнається, що периферійний вузол не один? Варіанти були такі: записи TXT зі структурованим вмістом, записи SRV або просто кілька записів A на тому самому імені.

Кілька записів A перемогли на одному аргументі. Набір A вже є списком місць, куди підключатися. Запис TXT був би другим джерелом правди про той самий факт, а ці двоє можуть розійтися. SRV додав би ваги, які нам не потрібні, бо агент міряє сам, а не слухає, кому віддати перевагу. TTL короткий, тож прибрати периферійний вузол це зміна в DNS плюс одне TTL терпіння.

Що клієнт насправді робить із кількома записами A

Перш ніж будувати будь-яку логіку вибору, варто було виміряти те, що дістається задарма. Сучасний HTTP-клієнт пробує адреси з набору записів і розподіляє між ними свій таймаут підключення. Ми поставили першою завідомо мертву адресу й виміряли:

Поведінка мертвої адресиЧас до підключення до живої
Зʼєднання відхилено7 мс
Немає маршруту до хоста7,5 мс
Мовчазне відкидання SYN, таймаут підключення 25 с12,5 с
Мовчазне відкидання SYN, таймаут підключення 10 с5,0 с

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

Перемикання на резерв не є вибором

Безкоштовне перемикання бере ту адресу, яка відповіла першою, а це не те саме, що найкраща. Агент в Амстердамі днями сидів на периферійному вузлі в іншій країні, бо саме той відповів першим, а сесія потім жила тиждень.

Тож агент міряє. Дрібниці, які мали значення:

Зондуйте повним рукостисканням TLS, а не підключенням TCP. Периферійний вузол може прийняти зʼєднання й нічого не переслати, а це рівно той збій, який зонд і має ловити. На голе підключення зламаний пересилач відповідає так само бадьоро, як робочий.

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

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

Знецінюйте за набором, а не за вибором. Якщо периферійний вузол додано або прибрано, вимір є застарілим, навіть якщо обрана адреса й досі в наборі.

Не робіть нічого, коли нема з чого обирати. Менш ніж із двома кандидатами весь механізм пропускають, і саме так буває в розробці, де імʼя розвʼязується в одну адресу.

І правило, що керує всім цим: вибір ніколи не має ставати новим режимом відмови. DNS відмовляє, ніхто не відповідає, файл стану не записується: кожен із цих шляхів закінчується на «вибору немає», і агент підключається рівно так само, як до того, як усе це зʼявилося.

Повернутися важче, ніж перемкнутися

Перемкнутися легко. Складна половина полягає в тому, що потім агент лишається на повільнішому периферійному вузлі стільки, скільки живе та сесія, а це можуть бути тижні.

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

  • Інтервал у пʼять хвилин із повним розкидом, щоб зонди цілого парку не синхронізувалися. Щохвилини коштувало б у пʼять разів більше й не давало б нічого, бо гістерезис нижче все одно розтягує будь-яке повернення на два раунди.
  • Серія з двох перед поверненням. Один успішний зонд це периферійний вузол, який щойно закінчив перезапускатися.
  • Два пороги для періодичного переміру: абсолютний у 20 мс і відносний у 25 %. Виконатися мають обидва. Сам лише абсолютний схвалив би перехід зі 100 мс на 79 мс; сам лише відносний схвалив би з 4 мс на 3 мс. Два периферійні вузли в одному центрі обробки даних різняться менш ніж на мілісекунду, і без цього вони мінялися б місцями через день.

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

Спостереження з іншого боку

Платформа спостерігає те саме зі свого кінця, і дві дрібниці там варто вкрасти.

Розвʼязуйте імʼя так, як це робить агент, через системний розвʼязувач, а не прямим запитом до DNS. Прямий запит просто впав на машині, чий розвʼязувач указував сам на себе, а важливіше те, що системним розвʼязувачем користується справжній трафік, тож він повертає той самий список.

Беріть два числа з одного сокета: підключення TCP, яке є оборотом до самого периферійного вузла, і повне рукостискання TLS, якому доводиться пройти тунелем до джерела й назад. Здоровʼя тримається на другому. Пересилач із мертвим бекендом відповідає на перше бездоганно.

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

Чого воно не розвʼязує

Периферія не захищає вміст, і було б помилкою почати вважати її межею безпеки. Адресу, яка лежала під час повного виміру, знову виявить лише наступний вимір, і це відома та прийнята прогалина. А TTL у DNS є нижньою межею того, як швидко периферійний вузол виходить із ротації, і це ціна за те, що набір A є списком.

Саме так агент дістається платформи в Provibr, де він зʼєднується лише назовні і тримає облікові дані на власному хості клієнта.

Читати далі

Почніть з одного хоста й одного агента

Створіть обліковий запис, встановіть агента однією командою та підключіть свій перший хост Proxmox. Пробний період триває 14 днів і не потребує платіжних даних.