Одного разу під час запланованого оновлення кластера k8s ми виявили, що майже всі наші POD (приблизно 500 з 1000) на нових вузлах не можуть запуститися, і хвилини швидко перетворилися на години. Ми активно шукаємо першопричину, але через три години PODS все ще перебував у статусі ContainerCreating
.
На щастя, це не було робоче середовище, тому період обслуговування було заплановано на вихідні. Ми мали час дослідити проблему без жодного тиску.
З чого почати пошук першопричини? Хочете дізнатися більше про рішення, яке ми знайшли? Пристебніться та насолоджуйтесь!
Проблема полягала в тому, що ми мали велику кількість образів докерів, які потрібно було отримати та запустити на кожному вузлі в кластері одночасно. Це сталося через те, що кілька одночасних завантажень образів докерів на одному вузлі можуть призвести до високого використання диска та збільшення часу холодного запуску.
Час від часу процес обробки компакт-диска займає до 3 годин, щоб отримати зображення. Однак цього разу він повністю застряг, оскільки кількість PODS під час оновлення EKS (inline, коли ми замінюємо всі вузли в кластері) була занадто великою.
Усі наші програми працюють у k8s (на базі EKS ). Щоб заощадити на наших витратах на DEV env, ми використовуємо точкові екземпляри.
Ми використовуємо образ AmazonLinux2 для вузлів.
У нас є велика кількість гілок функцій (FB) у середовищі розробки, які постійно розгортаються в нашому кластері Kubernetes. Кожен FB має власний набір програм, і кожна програма має власний набір залежностей (всередині зображення).
У нашому проекті майже 200 додатків і ця кількість зростає. Кожна програма використовує одне із 7 базових зображень докерів розміром ~2 ГБ. Максимальний загальний розмір заархівованого зображення (в ECR ) становить приблизно 3 ГБ.
Усі зображення зберігаються в Amazon Elastic Container Registry (ECR).
Ми використовуємо стандартний тип тома gp3 EBS для вузлів.
Розширений час холодного запуску: запуск нового пакета з новим зображенням може зайняти більше 1 години, особливо якщо кілька зображень завантажуються одночасно на одному вузлі.
Помилки ErrImagePull: часті помилки ErrImagePull
або зависання зі станами ContainerCreating
, що вказує на проблеми з отриманням зображення.
Високе використання диска: використання диска залишається майже на 100% під час процесу отримання образу, головним чином через інтенсивний дисковий ввід-вивід, необхідний для декомпресії (наприклад, «unpigz»).
Проблеми System DaemonSet: деякі системні DaemonSets (наприклад, aws-node
або ebs-csi-node
) перейшли в стан «не готовий» через тиск на диск, що вплинуло на готовність вузла.
Немає кешу зображень на вузлах: оскільки ми використовуємо точкові екземпляри, ми не можемо використовувати локальний диск для кешування зображень.
Це призводить до багатьох зупинених розгортань на гілках функцій, особливо через те, що різні FB мають різні набори базових зображень.
Після швидкого дослідження ми виявили, що основною проблемою був тиск диска на вузли через процес unpigz
. Цей процес відповідає за розпакування зображень докерів. Налаштування за замовчуванням для типу тому gp3 EBS ми не змінювали, тому що не підходять для нашого випадку.
Як перший крок ми вирішили зменшити кількість POD на вузлах.
Основна ідея рішення полягає в тому, щоб розігріти вузли перед початком процесу компакт-диска за допомогою найбільшої частини образу докера (рівня залежностей JS), який використовується як кореневий образ для всіх наших програм. У нас є принаймні 7 типів кореневих зображень із залежностями JS, які пов’язані з типом програми. Отже, давайте розберемо оригінальний дизайн CI/CD.
У нашому конвеєрі CI/CD ми маємо 3 стовпи:
Оригінальний конвеєр CI/CD:
На кроці Init
it: ми готуємо середовище/змінні, визначаємо набір зображень для перебудови тощо...
На кроці Build
: ми створюємо зображення та надсилаємо їх до ECR
На кроці Deploy
: ми розгортаємо образи на k8s (оновлюємо розгортання тощо)
Детальніше про оригінальний дизайн CICD:
main
гілки. У процесі CI ми завжди аналізуємо набір зображень, які були змінені у FB, і створюємо їх заново. main
гілка завжди стабільна, як за визначенням, завжди має бути остання версія базових зображень.Є вимоги до процесу розігріву.
Обов'язкові:
ContainerCreating
.Приємно мати покращення:
Проаналізувавши вимоги та обмеження, ми вирішили запровадити процес розігріву, який би попередньо нагрівав вузли базовими зображеннями кешу JS. Цей процес буде запущений до початку процесу CD, гарантуючи, що вузли готові до розгортання FB, і ми матимемо максимальний шанс потрапити в кеш.
Це вдосконалення ми розділили на три великих кроки:
Створіть набір вузлів (групу віртуальних вузлів) для кожного FB
Додайте базові зображення до сценарію хмарної ініціалізації для нових вузлів
Додайте етап попереднього розгортання, щоб запустити DaemonSet із розділом initContainers
, щоб завантажити необхідні образи докерів на вузли перед початком процесу CD.
Створіть новий набір вузлів для кожного FB через виклик API (до сторонньої системи автомасштабування) з нашого конвеєра CI.
Вирішені проблеми:
Ізоляція : кожен FB має власний набір вузлів, що гарантує, що на середовище не впливають інші FB.
Гнучкість : ми можемо легко змінити тип вузла та його термін служби.
Економічність : ми можемо видалити вузли одразу після видалення FB.
Прозорість : ми можемо легко відстежувати використання та продуктивність вузлів (кожен вузол має тег, пов’язаний із FB).
Ефективне використання спотових екземплярів : спотовий екземпляр починається з уже визначених базових зображень, тобто після запуску спотового вузла вже є базові зображення на вузлі (з головної гілки).
Завантажте всі базові зображення JS із головної гілки на нові вузли за допомогою сценарію cloud-init
.
Поки зображення завантажуються у фоновому режимі, процес компакт-диска може продовжувати створювати нові зображення без проблем. Крім того, наступні вузли (які будуть створені системою автомасштабування) з цієї групи будуть створені з оновленими даними cloud-init
, які вже містять інструкції для завантаження зображень перед запуском.
Вирішені проблеми:
Вирішення проблеми : навантаження на диск зникло, оскільки ми оновили сценарій cloud-init
, додавши завантаження базових зображень із головної гілки. Це дозволяє нам отримати доступ до кешу під час першого запуску FB.
Ефективне використання спотових екземплярів : спотовий екземпляр починається з оновлених даних cloud-init
. Це означає, що після запуску спотового вузла на вузлі вже є базові зображення (з головної гілки).
Покращена продуктивність : процес CD може продовжувати створювати нові образи без проблем.
Ця дія додала ~17 секунд (виклик API) до нашого конвеєра CI/CD.
Ця дія має сенс лише в перший раз, коли ми запускаємо FB. Наступного разу ми розгорнемо наші програми на вже існуючих вузлах, які вже мають базові образи, які ми доставили під час попереднього розгортання.
Нам потрібен цей крок, оскільки зображення FB відрізняються від зображень основної гілки. Нам потрібно завантажити базові зображення FB на вузли перед початком процесу CD. Це допоможе зменшити подовжений час холодного запуску та високе використання диска, які можуть виникнути, коли одночасно завантажується кілька важких образів.
Цілі етапу перед розгортанням
Запобігання тиску на диск : Послідовне завантаження найважчих образів докерів. Після кроку init-deploy ми вже маємо базові зображення на вузлах, що означає, що ми маємо великі шанси отримати кеш звернень.
Підвищення ефективності розгортання : переконайтеся, що вузли попередньо нагріті основними образами докерів, що призведе до швидшого (майже негайного) часу запуску POD.
Підвищення стабільності : мінімізуйте ймовірність виникнення помилок ErrImagePull
/ ContainerCreating
і переконайтеся, що набори системних демонов залишаються в стані «готовості».
На цьому кроці ми додаємо 10–15 хвилин до процесу CD.
Деталі кроку перед розгортанням:
initContainers
.initContainers
виконується перед запуском основного контейнера, забезпечуючи завантаження необхідних зображень до запуску основного контейнера.Порівняння вихідного та оновленого етапів із процесом попереднього нагрівання.
Крок | Ініціалізувати крок розгортання | Крок перед розгортанням | Розгорнути | Загальний час | різниця |
---|---|---|---|---|---|
Без попереднього розігріву | 0 | 0 | 11 хв 21 с | 11 хв 21 с | 0 |
З підігрівом | 8 секунд | 58 секунд | 25 секунд | 1 хв 31 с | -9 хв 50 с |
Головне, час «Розгортання» змінено (від першої команди застосування до стану запуску модулів) з 11 хвилин 21 секунди до 25 секунд. Загальний час змінився з 11 хв 21 с до 1 хв 31 с.
Важливий момент, якщо немає базових зображень з основної гілки, то час «Розгортання» буде таким же, як і вихідний час, або трохи більше. Але все одно ми вирішили проблему з тиском диска і часом холодного старту.
Основну проблему ContainerCreating
було вирішено шляхом розігріву. В якості переваги ми значно скоротили час холодного запуску POD.
Тиск на диск зник, тому що ми вже маємо базові зображення на вузлах. Системні daemonSets перебувають у «готовому» та «справному» стані (оскільки немає тиску на диск), і ми не виявили помилок ErrImagePull
пов’язаних із цією проблемою.
PS: я хотів би подякувати чудовій технічній команді Justt ( https://www.linkedin.com/company/justt-ai ) за невтомну роботу та справді творчий підхід до будь-якої проблеми, з якою вони стикаються з. Зокрема, я хочу подякувати Ронні Шарабі, чудовому керівнику, який відповідає за чудову роботу, яку виконує команда. Я з нетерпінням чекаю нових і нових чудових прикладів того, як ваша креативність впливає на продукт Justt.