Проблема «розумного» очищення образів контейнерів та її вирішення у werf

Проблема «розумного» очищення образів контейнерів та її вирішення у werf

У статті розглянута проблематика очищення образів, що накопичуються в реєстрах контейнерів (Docker Registry та його аналогах) у реаліях сучасних CI/CD-пайплайнів для cloud native-додатків, що доставляють у Kubernetes. Наведено основні критерії актуальності образів і складності при автоматизації очищення, збереження місця та задоволення потреб команд. Нарешті, з прикладу конкретного Open Source-проекту ми розповімо, як це складнощі можна подолати.

Запровадження

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

  1. використовувати фіксовану кількість тегів для образів;
  2. будь-яким чином очищати образи.


Перше обмеження іноді допустиме для невеликих команд. Якщо розробникам вистачає постійних тегів (latest, main, test, boris і т.п.), реєстр не буде роздмухуватися в розмірах і довгий час можна взагалі не думати про очищення. Адже всі неактуальні образи перетираються, а для очищення просто не залишається роботи (все робиться штатним збирачем сміття).

Тим не менш, такий підхід сильно обмежує розробку і рідко застосовується до CI/CD сучасних проектів. Невід'ємною частиною розробки стала автоматизація, яка дозволяє набагато швидше тестувати, розгортати та доставляти новий функціонал користувачам. Наприклад, у нас у всіх проектах при кожному коміті автоматично створюється CI-пайплайн. У ньому збирається образ, тестується, викочується в різні Kubernetes-контури для налагодження і перевірок, що залишилися, а якщо все добре - зміни доходять до кінцевого користувача. І це давно не rocket science, а буденність для багатьох - швидше за все і для вас, якщо ви читаєте цю статтю.

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

Але як узагалі визначити, чи актуальний образ?

Критерії актуальності образу

У переважній більшості випадків основні критерії будуть такі:

1. Перший (найочевидніший і найкритичніший з усіх) — це образи, які зараз використовуються в Kubernetes. Видалення цих образів може призвести до серйозних витрат у зв'язку з простоєм production (наприклад, образи можуть бути потрібні при реплікації) або звести нанівець зусилля команди, яка займається налагодженням на якомусь із контурів. (З цієї причини ми навіть зробили спеціальний Prometheus exporter, що відстежує відсутність таких образів у будь-якому Kubernetes-кластері.)

2. Другий (менш очевидний, але теж дуже важливий і знову відноситься до експлуатації) - образи, які потрібні для відкату у разі виявлення серйозних проблем у поточній версії. Наприклад, у випадку Helm це образи, які використовуються в збережених версіях релізу. (До речі, за умовчанням у Helm ліміт у 256 ревізій, але навряд чи у когось реально є потреба у збереженні такого великої кількості версій?..) Адже ми, зокрема, для того і зберігаємо версії, щоб їх можна було потім використовувати, тобто. «відкочуватися» на них у разі потреби.

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

4. Четвертий - образи, які відповідають версіям нашого додатку, тобто. є кінцевим продуктом: v1.0.0, 20.04.01, Сьєрра і т.д.

NB: Визначені критерії були сформульовані на основі досвіду взаємодії з десятками команд розробників з різних компаній. Однак, звичайно, залежно від особливостей у процесах розробки та інфраструктури, що використовується (наприклад, не використовується Kubernetes), ці критерії можуть відрізнятися.

Відповідність критеріям та існуючі рішення

Популярні сервіси з container registry, як правило, пропонують свої політики очищення образів: у них ви можете визначати умови, за яких тег видаляється з registry. Однак можливості цих умов обмежуються такими параметрами, як імена, час створення та кількість тегів*.

* Залежить від конкретних реалізацій container registry. Ми розглядали можливості наступних рішень: Azure CR, Docker Hub, ECR, GCR, GitHub Packages, GitLab Container Registry, Harbor Registry, JFrog Artifactory, Quay.io – станом на вересень 2020 року.

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

Наприклад, третій критерій — пов'язані з потребами розробників — може вирішуватися з допомогою організації процесів усередині команд: специфічного іменування образів, ведення спеціальних allow lists і внутрішніх домовленостей. Але зрештою його все одно необхідно автоматизувати. А якщо можливостей готових рішень не вистачає, то доводиться робити щось своє.

Аналогічна і ситуація з двома першими критеріями: їх неможливо задовольнити без отримання даних від зовнішньої системи - тієї, де відбувається розгортання додатків (у нашому випадку це Kubernetes).

Ілюстрація workflow в Git

Припустимо, ви працюєте приблизно за такою схемою в Git:

Проблема «розумного» очищення образів контейнерів та її вирішення у werf

Іконкою з головою на схемі відзначені образи контейнерів, які зараз розгорнуті в Kubernetes для будь-яких користувачів (кінцевих користувачів, тестувальників, менеджерів тощо) або використовуються розробниками для налагодження та подібних цілей.

Що станеться, якщо політики очищення дозволяють залишати (не видаляти) образи тільки за заданими назвами тегів?

Проблема «розумного» очищення образів контейнерів та її вирішення у werf

Очевидно, такий сценарій нікого не потішить.

Що зміниться, якщо політики дозволяють не видаляти образи за заданим часовим інтервалом / числом останніх коммітів?

Проблема «розумного» очищення образів контейнерів та її вирішення у werf

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

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

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

Наш шлях до універсального очищення образів

Звідки така потреба? Справа в тому, що ми не окремо взята група розробників, а команда, яка обслуговує відразу безліч таких, допомагаючи комплексно вирішувати питання CI/CD. І головний технічний інструмент для цього – Open Source-утиліта werf. Її особливість у тому, що вона не виконує єдиної функції, а супроводжує процеси безперервної доставки на всіх етапах: від складання до деплою.

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

* Хоча самі реєстри можуть бути різними (Docker Registry, GitLab Container Registry, Harbor і т.д.), їх користувачі стикаються з тими самими проблемами. Універсальне рішення у разі залежить від реалізації реєстру, т.к. виконується поза самими реєстрами і пропонує однакову поведінку для всіх.

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

Отже, ми взялися зовнішньої реалізацією механізму для очищення образів — замість можливостей, що вже вбудовані в реєстри для контейнерів. Першим кроком стало використання Docker Registry API для створення тих самих примітивних політик за кількістю тегів і часу їх створення (згаданих вище). До них було додано allow list на основі образів, що використовуються в розгорнутій інфраструктурі, тобто. Kubernetes. Для останнього було достатньо через Kubernetes API перебирати всі задеплоєні ресурси та отримувати список із значень image.

Таке тривіальне рішення закрило найкритичнішу проблему (критерій №1), але стало лише початком нашого шляху покращення механізму очищення. Наступним — і значно цікавішим — кроком стало рішення пов'язати опубліковані образи з історією Git.

Схеми тегування

Для початку ми вибрали підхід, при якому кінцевий образ повинен зберігати необхідну інформацію для очищення і побудували процес на схемах тегування. При публікації образу користувач вибирав певну опцію тегування (git-branch, git-commit або git-tag) і використав відповідне значення. У CI-системах установка цих значень виконувалася автоматично виходячи з змінних оточення. По суті кінцевий образ пов'язувався з певним Git-примітивомзберігаючи необхідні дані для очищення в лейблах.

У рамках такого підходу вийшов набір політик, який дозволяв використовувати Git як єдине джерело правди:

  • При видаленні гілки/тегу Git автоматично видалялися і пов'язані образи в registry.
  • Кількість образів, пов'язана з Git-тегами та комітами, можна було регулювати кількістю тегів, використаних у вибраній схемі, та часом створення пов'язаного комміту.

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

Новий алгоритм

Чому? При тегуванні в рамках content-based кожен тег може задовольняти безліч коммітів у Git. При очищенні образів не можна виходити більше лише з комміту, на якому новий тег було додано до реєстру.

Для нового алгоритму очищення було вирішено уникнути схем тегування і побудувати процес на мета-образах, кожен з яких зберігає зв'язку з:

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

Іншими словами, була забезпечена зв'язок публікованих тегів з комітами в Git.

Підсумкова конфігурація та загальний алгоритм

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

  • множиною references, тобто. Git-тегами або Git-гілками, що використовуються при скануванні;
  • і лімітом шуканих образів для кожного reference з безлічі.

Для ілюстрації — ось як почала виглядати конфігурація політик за умовчанням:

cleanup:
  keepPolicies:
  - references:
      tag: /.*/
      limit:
        last: 10
  - references:
      branch: /.*/
      limit:
        last: 10
        in: 168h
        operator: And
    imagesPerReference:
      last: 2
      in: 168h
      operator: And
  - references:  
      branch: /^(main|staging|production)$/
    imagesPerReference:
      last: 10

Така конфігурація містить три політики, які відповідають таким правилам:

  1. Зберігати образ для останніх 10 Git-тегів (за датою створення тега).
  2. Зберігати не більше 2 образів, опублікованих за останній тиждень, для не більше 10 гілок з активністю за останній тиждень.
  3. Зберігати по 10 образів для гілок main, staging и production.

Підсумковий алгоритм зводиться до наступних кроків:

  • Отримання маніфестів із container registry.
  • Виняток образів, які у Kubernetes, т.к. їх ми попередньо відібрали, опитавши K8s API.
  • Сканування Git-історії та виключення образів за заданими політиками.
  • Видалення образів, що залишилися.

Повертаючись до нашої ілюстрації, ось що виходить із werf:

Проблема «розумного» очищення образів контейнерів та її вирішення у werf

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

Висновок

  • Рано чи пізно з проблемою переповнення registry стикається більшість команд.
  • Під час пошуку рішень насамперед необхідно визначити критерії актуальності образу.
  • Інструменти, що пропонуються популярними сервісами container registry, дозволяють організувати дуже просте очищення, яке не враховує «зовнішній світ»: образи, що використовуються в Kubernetes, та особливості робочих процесів у команді.
  • Гнучкий та ефективний алгоритм повинен мати уявлення про CI/CD-процеси, оперувати не лише даними Docker-образів.

PS

Читайте також у нашому блозі:

Джерело: habr.com

Додати коментар або відгук