Стендап Сьогодні

Що я зробив, що я хочу зробити, і що це все значить.
Повсякденні здобутки в форматі стендапу.
Детальніше в статті

Підписатись на RSS
📢 Канал в Telegram @stendap_sogodni
🦣 @stendap_sogodni@shevtsov.me в Федиверсі

18.08.2023

Основи безпеки AWS: VPC - ваша локальна мережа

Розпочну короткий курс з безпеки в AWS та що я про неї встиг дізнатись.

З чого все починається — це VPC, “віртуальна приватна хмара”, а фактично — локальна мережа. Існує вона зрозуміло для чого — щоб провести чітку межу між ресурсами проєкту та зовнішнім світом. Раніше використання VPC було за бажанням, але на цей час всі нові ресурси створюються тільки так, бодай в VPC за замовчуванням.

Існування VPC уможливлює всі функції, про які я писатиму далі. Бо з локальною мережею ми точно знаємо, де наші ресурси: віртуальні машини, контейнери ECS, бази даних і так далі — а де чужі. Це саме логічне угрупування, а не фізичне, бо ресурси можуть розташовуватись будь-де в регіоні. Щодо безпеки, то VPC на неї прямо не впливає; шифрування трафіку відбувається в будь-якому разі.

Зазвичай компанія матиме один VPC. Більше має сенс, якщо у вас більше одного продукту (але тоді може бути доцільніше зробити окремі облікові записи AWS). Або якщо ви використовуєте більш одного регіону.

Різні VPC одна до одної — чужі. Якщо плануєте більше однієї VPC, важливо призначити їм різні простори адрес. Бо тоді можна в майбутньому залучити VPC Peering - поєднання двох VPC в одну. А от якщо адреси збігаються… тоді ніякого Peering не вийде. Але також раджу не робити окремих VPC, якщо не має пекучої потреби повного розділення ресурсів.

Не всі сервіси AWS підтримують VPC; як найбільш знайомий — то S3. Такі сервіси стосовно VPC будуть зовнішніми. Проте існує свіжа технологія PrivateLink, яка дозволяє підʼєднатись до деяких ресурсів “напряму”, що, як я розумію, просто спрощує маршрутизацію.


17.08.2023

Де взяти IP адресу?

У всього, до чого ми звертаємось в інтернеті, обовʼязково є IP адреса. Але що вони значать? Та де їх взяти? Про це ми не думаємо.

Взагалі IP адреса — суто віртуальний вказівник. Сам по собі він майже нічого не значить. IP адреси працюють в сукупності з таблицями маршрутів. Така таблиця міститься на кожному вузлі в Інтернеті та попросту каже, якому з локальних вузлів передавати пакети, призначені той чи іншій IP адресі. Бо інтернет працює за “естафетою” - саме її ми бачимо командою traceroute.

Тому з IP адресами в інтернеті відбувається багато цікавих подій, які ми не помічаємо. Наприклад: технологія Anycast дозволяє мати одну та ту саму IP адресу для різних серверів в різних місцях інтернету. Так DNS-сервіс 1.1.1.1 насправді має сервер десь поруч з тобою, а не один на всіх. (Мені показує Нідерланди. А, до речі, 8.8.8.8 веде аж до Каліфорнії.)

Нам адреси видає провайдер - потроху, бо кількість адрес обмежена. А що, якщо хочеться більше адрес? Може, ми придумали видавати кожному клієнтові по DNS серверу? Або просто створити власний хостинг?

Тоді можемо взяти в оренду цілий блок IP адрес. От, наприклад через компанію LIR Services (на правах досвіду, не реклами.) Коштує це від $1K за 256 адрес — найменший діапазон для оренди. Купити не можна, бо розподілом IP адрес у світі керують 5 великих агенцій - RIR.

Але що взагалі значить — володіти IP адресами? Далі треба, щоб хтось оголошував їх (щоб всі знали, куди передавати “естафету” до цих адрес) та маршрутизував до конкретних серверів. Хочеш робити це сам — треба реєструвати автономну систему - це ще витрати — на ліцензію та на підтримку. Тому гарно, що хмарні провайдери, наприклад, AWS, почали пропонувати сервіс BYOIP - тобто обслуговування діапазону, який ти заорендував в іншому місці. В них є своя автономна система, до якої буде доданий діапазон. Далі клієнти знатимуть, що твоя IP адреса мешкає на Амазоні — а AWS вже знатиме, як дістатися до конкретної віртуальної машини всередині.

Все це складно для розуміння, але цілком доступно кожному з бюджетом. Сервісу приватного DNS - бути!


16.08.2023

Чому Кафка обмежує розмір пачки в байтах?

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

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

(Це неповне повідомлення — не така страшна справа. По-перше, поки йде активне споживання, дані не повинні накопичуватись, тож вони не сягнуть порогу пачки та не будуть обрізані. По-друге, якщо вже будуть, то пачка міститиме тисячі повідомлень чи більше — та швидше один раз відкинути решту, ніж тисячу раз додавати, поки не наберемо потрібний розмір.)

Все, зрозуміло, заради продуктивності. Буквально немає швидшої операції, ніж зчитувати сиру двійкову послідовність. Так Kafka й досягає пропускної здатності в мільйони записів на секунду.

Рекомендую для прочитання опис протоколу Kafka, щоб краще зрозуміти ідеї, на яких вона побудована.

Примітка: є ще опція max.poll.records та можна подумати, що вона є альтернативою для fetch.max.bytes. Проте ні — обмеження за кількістю записів відбувається вже на боці клієнта, після того, як вони отримані. Така опція є зручною абстракцією для нашого коду, не більше.


15.08.2023

Дерево задач для Obsidian

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

В минулому пості я розповідав про синтаксичне дерево Markdown. Довгий час я планував працювати з цим деревом, як воно є, та просто доповнювати вузли потрібними мені атрибутами (наприклад, задачу — її статусом.) Проте це незручно: задачі губляться посеред інших елементів.

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

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

Щоб мати можливість відтворити зміни назад в документ, потрібно привʼязати задачі до відповідних їх місць документа, тобто вузлів синтаксичного дерева. Це можна було б робити за допомогою матеріалізованого шляху, тобто зберігати шлях до вузла. Але оскільки в JavaScript всі обʼєкти завжди передаються за посиланням, то можна просто зберігати сам вузол в атрибут задачі. Далі можна, скажімо, виконати певні перетворення задач (додати дати закінчення), поміняти вузли, та згенерувати з синтаксичного дерева новий документ.

А Obsidian Tasks мені перестав подобатись, не так він працює, як я хочу. Ідеї в них хороші, але можна їх дорозвинути.


14.08.2023

Kafka проти черг: події проти задач

Як цікаво: з першого погляду Kafka майже не відрізняється від черг, таких як SQS або RabbitMQ. Проте відмінності, на мою думку, починаються з того, як кожна система дивиться на повідомлення всередині.

Задача в черзі — це наказ на роботу. Значить, кожна задача витратить нетривіальний проміжок часу. А це, своєю чергою, значить, що задач буде не так вже й багато (порівняно з подіями.) Задачі цікавлять нас поодинці: кожну задачу ми робимо окремо, а коли зробили — підтверджуємо, що зроблено.

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

Така різниця інформує API Кафки: воно розроблене, щоб якнайшвидше отримувати події, яких буде багато. Реально багато — хоч мільйон на секунду. Коли отримали та обробили, відмічаємо події як оброблені. Не кожну окремо (бо кому то цікаво робити з мільйоном записів), а всі разом — за зсувом.

Отже, Кафку має сенс ставити там, де вам потрібно обробляти значний обсяг подій та продукувати стан, або якось реагувати на зміни стану. А про Кафку як чергу гарно написали на StackOverflow.


13.08.2023

Емоції дороги

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


12.08.2023

Мʼякість TypeScript

Мабуть, цікавий аспект, про який можна й не замислюватись — все, що робить TypeScript, робиться в момент компіляції. Далі залишається тільки код JavaScript. Більш за те, поки код TypeScript є синтаксично коректним, він буде працювати. Я хотів написати “працювати, але з багами”, але часто ситуація зворотна: це типи містять помилки, а код цілком вірний. Думаю, без такої можливості TypeScript далеко б не пішов, тому що багато бібліотек, що існують, не підходять для точної типізації.

Якщо ти з типізованих мов бачив тільки TypeScript, то навпаки, може скластися враження, що це стандартна система. Але ні: практично всі типізовані мови, що я знаю, не погодяться компілювати код без повністю вірних типів. (Не кажу про RBS для Ruby, бо він для мене надто сирий. Головним чином, аналог DefinitelyTyped поки не виріс.) Тож TypeScript являє особливу, “мʼяку” екосистему.

Мʼякість — це не тільки можливість виконати код без правильних типів. Це й розширення типів, про яке я писав вчора. І підхід типізації “за зразком” - тобто, замість жорстко заданих класів тип об’єкта перевіряється за наявними атрибутами.

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


11.08.2023

Розширення модуля в TypeScript

Я люблю TypeScript за те, як вона вдало розвʼязала питання інтеграції з кодом, що вже існує — а саме, з JavaScript. Завдяки можливості впровадити типи для будь-якої бібліотеки, TypeScript чудово інтегрувався з екосистемою та майже не обмежує вибір залежностей. Ну, багато ідіом JavaScript просто погано типізуються, але про це розмова окрема.

Напевно, кожний знає про проєкт DefinitelyTyped, тобто про пакети @types/*, які й додають типи до бібліотек на JavaScript. Але чи задавалися ви тим, як воно працює? Видається, дуже просто — вони містять так звані шаблони модулів. Власне, щоб TypeScript “побачив” модуль, він має або бути написаний на TypeScript (логічно), або мати шаблон.

Менш відома можливість — розширення модулів. Насправді не обовʼязково мати весь шаблон в одному місці. Цілком можливо розбити оголошення на дві частини. Більш за те, додаткові оголошення можуть бути зовсім в іншому файлі та навіть пакеті. Якщо TypeScript підвантажує в проєкт файл, в якому містяться оголошення, то він їх використає.

Причому розширяти можна й оголошення чужих модулів. Це як раз покриває ту незручну ідіому JavaScript, коли додатковий функціонал надається у вигляді плагіну. Як приклад: типи до Day.js. Або, ви можете виправити незручні типи залежностей прямо в додатку.

А якщо плагін — це окремий пакет, то треба памʼятати одну незручну річ: вкладені залежності. Бо той пакет, типи якого ви розширюєте, має не бути такою. Взагалі, при розробці пакетів-плагінів краще не створювати явних залежностей, а використовувати peerDependencies, або навіть devDependencies, та нехай споживачі самі знають, який головний пакет їм ставити.


10.08.2023

Логічна реплікація в PostgreSQL

Логічна реплікація (на відміну від фізичної) - це відносно новий (з 2018) та цікавий спосіб перенесення даних з однієї бази PostgreSQL в іншу. Оскільки зазвичай люди звикли до реплікації як відтворення копії цілої бази, зазначу, що логічна реплікація — це зовсім інша технологія. Працює інакше, вирішує інші завдання.

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

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

Є декілька нюансів, про які треба памʼятати. Головне, таблиця-отримувач має бути сумісною з таблицею-джерелом за структурою. Найчастіше натрапляємо на те, що коли додаєш стовпчик в джерело, то перед цим треба було вже додати в отримувача. Але також пильнувати треба за індексами та іншим.

Бо якщо PostgreSQL не може застосувати чергову зміну у таблицю, то він просто призупиняє реплікацію. Хороші новини — вона відновиться автоматично, як тільки зміни стануть можливими. Погані — при паузі реплікації на стороні джерела накопичується журнал, причому стрімко. Якщо цього не помітити, можна заповнити журналом все місце на диску, від чого база лягає ще серйозніше.

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

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

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


09.08.2023

Машини AWS Burstable (T-class)

Сьогодні моє уявлення про мінімальний масштаб сервісів було доповнене ще одним критерієм. Йдеться про так звані burstable (тобто “імпульсні”) машини AWS.

Коли бачиш в категорії машини літеру T - то це ті самі імпульсні машини. Їх можна помітити не тільки в EC2, але й в RDS, ElastiCache, та будь-якому іншому сервісі. Коштують вони дешевше, тож виглядають як гарний вибір за відсутністю конкретних вимог. Проте зі швидкодією T-класу є нюанс: вони не здатні працювати на зазначених показниках постійно. Натомість машини класу T накопичують кредити, поки не мають навантаження, а під час навантаження навпаки, ці кредити витрачають.

Що буде, коли кредити закінчаться? Машина буде обмежена до так званої базової швидкодії, що може значить від 5% до 40% від зазначених показників — в залежності від розмірів машини. Причому буде обмежений як CPU, так і дискова активність. Та до того ж щоб помітити цю ситуацію, треба стежити за балансом кредитів, бо за графіком CPU її не помітиш. Наприклад, графік буде показувати, що машина витрачає безпечні 20% CPU, в той час, як вона задихається від навантаження.

Одним словом, в продакшн машини T-класу брати не можна. Бо опинишся в неприємній ситуації, та, скоріше, посеред важкої задачі, яка через обмеження триватиме ще довше. Хоча ще такий Unlimited mode, коли робота поза вичерпанням кредитів можлива, тільки за додаткові гроші.