Проблемът с „умното“ почистване на изображения на контейнери и неговото решение в 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, sierra и т.н.

NB: Определените тук критерии са формулирани на база опит от взаимодействие с десетки екипи разработчици от различни компании. Въпреки това, разбира се, в зависимост от особеностите в процесите на разработка и използваната инфраструктура (например, ако не се използва Kubernetes), тези критерии могат да се различават.

Спазването на критериите и съществуващите решения

Популярните услуги с контейнерен регистър обикновено предлагат свои политики за почистване на образи: в тях можете да задавате условия, при които тагът се изтрива от регистъра. Възможностите на тези условия обаче са ограничени до такива параметри като имена, време на създаване и количество тагове*.

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

Този набор от параметри е напълно достатъчен, за да отговори на четвъртия критерий — тоест да отбира изображения, отговарящи на версиите. Въпреки това за всички останали критерии се налага да изберете компромисно решение (по-строга или, напротив, по-мекият режим) — в зависимост от очакванията и финансовите възможности.

Например, третият критерий — свързан с нуждите на разработчиците — може да бъде решен чрез организиране на процесите в рамките на екипите: специфично именуване на изображения, водене на специални allow списъци и вътрешни споразумения. Но в крайна сметка той все пак трябва да бъде автоматизиран. А ако възможностите на готовите решения не са достатъчни, трябва да се прави нещо свое.

Ситуацията с първите два критерия е аналогична: те не могат да бъдат удовлетворени без получаване на данни от външна система — тази, в която се извършва разгръщането на приложения (в нашия случай това е Kubernetes).

Илюстрация на работния процес в 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 автоматично се изтриваха и свързаните образи в регистъра.
  • Броят на образите, свързани с Git-етикети и комити, можеше да се регулира от броя на етикетите, използвани в избраната схема, и времето на създаване на свързания комит.

Като цяло, получената реализация задоволяваше нашите нужди, но скоро ни очакваше ново предизвикателство. Факт е, че по време на използването на схемите за етикетиране чрез Git-примитиви ние се сблъскахме с редица недостатъци. (Тъй като тяхното описание излиза извън рамките на темата на тази статия, всички желаещи могат да се запознаят с подробностите тук..) Затова, взимайки решение за преминаване към по-ефективен подход за етикетиране (етикетиране на базата на съдържание), трябваше да преразгледаме и реализирането на почистването на образите.

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

Защо? При етикетирането в контекста на етикетиране на базата на съдържание всеки етикет може да отговаря на множество комити в 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 и производството.

Крайната алгоритъм е обобщен в следните стъпки:

  • Получаване на манифести от контейнерен регистър.
  • Изключване на изображения, използвани в Kubernetes, тъй като вече сме ги отсяли, запитвайки K8s API.
  • Сканиране на Git-историята и изключване на изображенията според зададените политики.
  • Изтриване на останалите изображения.

Връщайки се към нашата илюстрация, ето какво се получава с werf:

Проблемът с „умното“ почистване на изображения на контейнери и неговото решение в werf

Но дори и да не използвате werf, подобен подход към напредналото почистване на изображения — в една или друга реализация (в съответствие с предпочитания подход към тагиране на изображения) — може да се приложи и в други системи/инструменти. За това е достатъчно да запомните проблемите, които възникват, и да намерите възможностите във вашият стек, които позволяват внедряване на тяхното решение по най-гладкия начин. Надяваме се, че пътят, който преминахме, ще помогне да се погледне и на вашия случаен случай с нови детайли и мисли.

Заключение

  • Рано или късно с проблема с пренаселеността на регистъра се сблъсква повечето екипи.
  • При търсене на решения на първо място е необходимо да се определят критериите за актуалност на изображението.
  • Инструментите, предлагани от популярни услуги за container registry, позволяват много лесно изчистване, което не взима предвид "външния свят": образите, използвани в Kubernetes, и особеностите на работните процеси в екипа.
  • Гъвкав и ефективен алгоритъм трябва да има представа за CI/CD процесите и да оперира не само с данни за Docker образи.

P.S.

Прочетете също в нашия блог:

Източник: habr.com

Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри 🔥 Купете надежден хостинг за сайтове със защита от DDoS, VPS и VDS сървъри | ProHoster