werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

27 май в главната зала на конференцията DevOpsConf 2019, проведена като част от фестивала RIT++ 2019, като част от раздела „Непрекъсната доставка“, беше даден доклад „werf - нашият инструмент за CI/CD в Kubernetes“. Говори се за тези проблеми и предизвикателства, пред които всеки се изправя при внедряването в Kubernetes, както и за нюанси, които може да не се забелязват веднага. Анализирайки възможните решения, ние показваме как това се прилага в инструмент с отворен код werf.

След представянето нашата помощна програма (известна преди като dapp) достигна историческо събитие от 1000 звезди в GitHub — надяваме се, че нарастващата общност от потребители ще улесни живота на много инженери на DevOps.

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

И така, представяме видео на репортажа (~47 минути, много по-информативно от статията) и основният извадка от нея в текстов вид. Отивам!

Доставяне на код на Kubernetes

Говоренето вече няма да бъде за werf, а за CI/CD в Kubernetes, което означава, че нашият софтуер е пакетиран в Docker контейнери (Говорих за това в Доклад за 2016 г), а K8s ще се използва за стартирането му в производството (повече за това в 2017 години).

Как изглежда доставката в Kubernetes?

  • Има Git хранилище с кода и инструкции за изграждането му. Приложението е вградено в изображение на Docker и е публикувано в регистъра на Docker.
  • Същото хранилище съдържа и инструкции как да разположите и стартирате приложението. На етапа на внедряване тези инструкции се изпращат до Kubernetes, който получава желаното изображение от регистъра и го стартира.
  • Освен това обикновено има тестове. Някои от тях могат да бъдат направени при публикуване на изображение. Можете също така (следвайки същите инструкции) да разположите копие на приложението (в отделно пространство от имена на K8s или отделен клъстер) и да изпълните тестове там.
  • И накрая, имате нужда от CI система, която получава събития от Git (или кликвания върху бутони) и извиква всички определени етапи: изграждане, публикуване, внедряване, тестване.

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

Тук има няколко важни бележки:

  1. Защото имаме неизменна инфраструктура (неизменна инфраструктура), изображението на приложението, което се използва на всички етапи (постановка, производство и т.н.), трябва да има такъв. Говорих за това по-подробно и с примери. тук.
  2. Тъй като следваме подхода на инфраструктурата като код (IaC), кодът на приложението, инструкциите за сглобяване и стартиране трябва да бъдат точно в едно хранилище. За повече информация относно това вижте същия доклад.
  3. Верига за доставка (доставка) обикновено го виждаме така: приложението е сглобено, тествано, пуснато (етап на освобождаване) и това е всичко - доставката е извършена. Но в действителност потребителят получава това, което сте пуснали, не след това, когато го доставихте на производство и когато той успя да отиде там и това производство проработи. Така че вярвам, че веригата за доставка приключва само на оперативен етап (бягам), или по-точно, дори в момента, в който кодът е премахнат от производството (замяната му с нов).

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

Етап на изграждане

Изглежда, че можете да говорите за изграждане на Docker изображения през 2019 г., когато всеки знае как да напише Dockerfiles и да стартира docker build?.. Ето нюансите, на които бих искал да обърна внимание:

  1. Тегло на изображението има значение, така че използвайте многоетапенда оставите в изображението само приложението, което наистина е необходимо за операцията.
  2. Брой слоеве трябва да бъдат сведени до минимум чрез комбиниране на вериги от RUN-заповеди според смисъла.
  3. Това обаче добавя проблеми отстраняване на грешки, защото когато сборката се срине, трябва да намерите правилната команда от веригата, която е причинила проблема.
  4. Скорост на сглобяване важно, защото искаме бързо да внедрим промените и да видим резултатите. Например, не искате да възстановявате зависимостите в езиковите библиотеки всеки път, когато създавате приложение.
  5. Често от едно Git хранилище, от което се нуждаете много изображения, което може да бъде разрешено чрез набор от Dockerfiles (или именувани етапи в един файл) и Bash скрипт с тяхното последователно сглобяване.

Това беше само върхът на айсберга, пред който са изправени всички. Но има и други проблеми, по-специално:

  1. Често на етапа на сглобяване имаме нужда от нещо монтиране (например, кеширайте резултата от команда като apt в директория на трета страна).
  2. Ние искаме Ansible вместо да пише в shell.
  3. Ние искаме изграждане без Docker (защо се нуждаем от допълнителна виртуална машина, в която трябва да конфигурираме всичко за това, когато вече имаме Kubernetes клъстер, в който можем да изпълняваме контейнери?).
  4. Паралелен монтаж, което може да се разбира по различни начини: различни команди от Dockerfile (ако се използва multi-stage), няколко ангажимента на едно и също хранилище, няколко Dockerfiles.
  5. Разпределено сглобяване: Искаме да събираме неща в капсули, които са „ефимерни“, защото техният кеш изчезва, което означава, че трябва да се съхранява някъде отделно.
  6. Накрая нарекох върха на желанията автомагия: Би било идеално да отидете в хранилището, да въведете някаква команда и да получите готово изображение, сглобено с разбиране как и какво да направите правилно. Но аз лично не съм сигурен, че по този начин могат да се предвидят всички нюанси.

А ето и проектите:

  • moby/buildkit — строител от Docker Inc (вече интегриран в текущите версии на Docker), който се опитва да реши всички тези проблеми;
  • канико — конструктор от Google, който ви позволява да изграждате без Docker;
  • Buildpacks.io — Опитът на CNCF да направи автоматична магия и по-специално интересно решение с пребазиране на слоеве;
  • и куп други помощни програми, като напр билда, оригинални инструменти/изобр...

...и вижте колко звезди имат в GitHub. Тоест, от една страна, docker build съществува и може да направи нещо, но в действителност проблемът не е напълно решен - доказателство за това е паралелното развитие на алтернативни колектори, всеки от които решава част от проблемите.

Сглобяване в werf

Така че трябва werf (по-рано известен като dapp) — Помощна програма с отворен код от компанията Flant, която правим от много години. Всичко започна преди 5 години с Bash скриптове, които оптимизираха сглобяването на Dockerfiles, а през последните 3 години беше извършено пълноценно развитие в рамките на един проект със собствено Git хранилище (първо в Ruby, а след това пренаписани към Go и в същото време преименуван). Какви проблеми със сглобяването се решават в werf?

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

Проблемите, защриховани в синьо, вече са изпълнени, паралелното изграждане е направено в рамките на един и същ хост, а проблемите, подчертани в жълто, се планира да бъдат завършени до края на лятото.

Етап на публикуване в регистъра (публикуване)

Набрахме docker push... - какво може да е трудно за качването на изображение в регистъра? И тогава възниква въпросът: „Какъв етикет да сложа на изображението?“ Възниква поради причината, която имаме Gitflow (или друга Git стратегия) и Kubernetes и индустрията се опитва да гарантира, че случващото се в Kubernetes следва това, което се случва в Git. В крайна сметка Git е единственият ни източник на истина.

Какво толкова трудно има в това? Осигурете възпроизводимост: от ангажимент в Git, който е неизменен по природа (неизменен), към Docker изображение, което трябва да се запази същото.

За нас също е важно определи произхода, защото искаме да разберем от кой комит е създадено приложението, работещо в Kubernetes (тогава можем да правим разлики и подобни неща).

Стратегии за маркиране

Първият е прост git таг. Имаме регистър с изображение, маркирано като 1.0. Kubernetes има сцена и продукция, където се качва това изображение. В Git правим ангажименти и в даден момент маркираме 2.0. Събираме го според инструкциите от хранилището и го поставяме в регистъра с етикета 2.0. Пускаме го на сцена и, ако всичко е наред, тогава на производство.

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

Проблемът с този подход е, че първо поставихме етикета и едва след това го тествахме и внедрихме. Защо? Първо, това е просто нелогично: ние издаваме версия на софтуер, която още не сме тествали (не можем да направим друго, защото за да проверим, трябва да поставим етикет). Второ, този път не е съвместим с Gitflow.

Вторият вариант - git commit + етикет. Главният клон има етикет 1.0; за него в регистъра - изображение, внедрено в производството. В допълнение, клъстерът на Kubernetes има предварителен преглед и контури за етапи. След това следваме Gitflow: в основния клон за разработка (develop) създаваме нови функции, което води до ангажимент с идентификатора #c1. Ние го събираме и го публикуваме в регистъра, използвайки този идентификатор (#c1). Със същия идентификатор, който пускаме за предварителен преглед. Ние правим същото с ангажиментите #c2 и #c3.

Когато разбрахме, че има достатъчно функции, започваме да стабилизираме всичко. Създайте клон в Git release_1.1 (на основата #c3 на develop). Няма нужда да събирате това издание, защото... това беше направено в предишната стъпка. Следователно, можем просто да го разгърнем за постановка. Поправяме грешки в #c4 и по подобен начин се разгръщат за постановка. В същото време тече разработка в develop, откъдето периодично се вземат промени release_1.1. В даден момент получаваме комит, компилиран и качен в етапа, от който сме доволни (#c25).

След това обединяваме (с превъртане напред) клона за освобождаване (release_1.1) в master. Поставяме етикет с новата версия на този ангажимент (1.1). Но това изображение вече е събрано в регистъра, така че за да не го събираме отново, просто добавяме втори етикет към съществуващото изображение (сега то има тагове в регистъра #c25 и 1.1). След това го пускаме в производство.

Има недостатък, че само едно изображение се качва в етапа (#c25), а в производството е малко по-различно (1.1), но знаем, че „физически“ това са едно и също изображение от регистъра.

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

Истинският недостатък е, че няма поддръжка за ангажименти за сливане, трябва да направите бързо превъртане напред.

Можем да отидем по-далеч и да направим трик... Нека да разгледаме пример за прост Dockerfile:

FROM ruby:2.3 as assets
RUN mkdir -p /app
WORKDIR /app
COPY . ./
RUN gem install bundler && bundle install
RUN bundle exec rake assets:precompile
CMD bundle exec puma -C config/puma.rb

FROM nginx:alpine
COPY --from=assets /app/public /usr/share/nginx/www/public

Нека изградим файл от него по следния принцип:

  • SHA256 от идентификаторите на използваните изображения (ruby:2.3 и nginx:alpine), които са контролни суми на тяхното съдържание;
  • всички отбори (RUN, CMD и др.);
  • SHA256 от добавени файлове.

... и вземете контролната сума (отново SHA256) от такъв файл. Това подпис всичко, което определя съдържанието на Docker изображението.

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

Да се ​​върнем към диаграмата и вместо ангажименти ще използваме такива подписи, т.е. маркиране на изображения с подписи.

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

Сега, когато е необходимо, например, да обединим промените от версия към master, можем да направим реално обединяване: то ще има различен идентификатор, но същия подпис. Със същия идентификатор ще внедрим изображението в продукцията.

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

Маркиране в werf

В werf отидохме още по-далеч и се подготвяме да направим разпределено компилиране с кеш, който не се съхранява на една машина... И така, ние изграждаме два типа Docker изображения, ние ги наричаме етап и изображение.

Хранилището на werf Git съхранява специфични за изграждането инструкции, които описват различните етапи на изграждането (преди Инсталиране, инсталирам, преди Настройка, структура). Събираме изображението на първия етап със сигнатура, дефинирана като контролна сума на първите стъпки. След това добавяме изходния код, за новото сценично изображение изчисляваме неговата контролна сума... Тези операции се повтарят за всички етапи, в резултат на което получаваме набор от сценични изображения. След това правим крайното изображение, което съдържа и метаданни за неговия произход. И ние маркираме това изображение по различни начини (подробности по-късно).

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

Да предположим, че след това се появява нов комит, в който е променен само кодът на приложението. Какво ще се случи? За промени в кода ще бъде създадена корекция и ще бъде подготвено ново сценично изображение. Сигнатурата му ще бъде определена като контролната сума на старото сценично изображение и новата корекция. От това изображение ще се формира ново окончателно изображение. Подобно поведение ще възникне при промени в други етапи.

По този начин сценичните изображения са кеш, който може да се съхранява разпределено, а вече създадените от него изображения се качват в регистъра на Docker.

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

Почистване на системния регистър

Не говорим за изтриване на слоеве, които са останали висящи след изтрити етикети - това е стандартна функция на самия регистър на Docker. Говорим за ситуация, когато се натрупат много Docker тагове и разбираме, че вече не ни трябват някои от тях, но заемат място (и/или плащаме за това).

Какви са стратегиите за почистване?

  1. Можете просто да не правите нищо не чисти. Понякога наистина е по-лесно да платите малко за допълнително пространство, отколкото да разплетете огромна плетеница от тагове. Но това работи само до определен момент.
  2. Пълно нулиране. Ако изтриете всички изображения и възстановите само текущите в CI системата, може да възникне проблем. Ако контейнерът бъде рестартиран в производство, за него ще бъде заредено ново изображение - такова, което все още не е тествано от никого. Това убива идеята за неизменна инфраструктура.
  3. Синьозелено. Един регистър започна да препълва - качваме изображения в друг. Същият проблем като в предишния метод: в кой момент можете да изчистите регистъра, който е започнал да препълва?
  4. По време. Изтриване на всички изображения, по-стари от 1 месец? Но определено ще има услуга, която не е актуализирана от месец...
  5. ръчно определи какво вече може да бъде изтрито.

Има две наистина жизнеспособни опции: не почиствайте или комбинация от синьо-зелено + ръчно. В последния случай говорим за следното: когато разберете, че е време да почистите регистъра, създавате нов и добавяте всички нови изображения към него в продължение на например месец. И след месец вижте кои подове в Kubernetes все още използват стария регистър и ги прехвърлете също в новия регистър.

До какво стигнахме werf? Ние събираме:

  1. Git head: всички тагове, всички разклонения - ако приемем, че имаме нужда от всичко, което е маркирано в Git в изображенията (и ако не, тогава трябва да го изтрием в самия Git);
  2. всички подове, които в момента се изпомпват към Kubernetes;
  3. стари ReplicaSets (това, което беше пуснато наскоро), и също планираме да сканираме изданията на Helm и да изберем най-новите изображения там.

... и направете бял списък от този набор - списък с изображения, които няма да изтрием. Изчистваме всичко останало, след което намираме осиротели сценични изображения и ги изтриваме.

Етап на разгръщане

Надеждна декларативност

Първата точка, на която бих искал да обърна внимание при внедряването, е внедряването на актуализираната конфигурация на ресурса, декларирана декларативно. Оригиналният YAML документ, описващ ресурсите на Kubernetes, винаги е много различен от резултата, който действително се изпълнява в клъстера. Тъй като Kubernetes добавя към конфигурацията:

  1. идентификатори;
  2. сервизна информация;
  3. много стойности по подразбиране;
  4. раздел с актуално състояние;
  5. промени, направени като част от webhook за допускане;
  6. резултатът от работата на различни контролери (и планировчика).

Следователно, когато се появи нова конфигурация на ресурс (нов), не можем просто да вземем и презапишем текущата, „жива“ конфигурация с него (живея). За да направим това, ще трябва да сравним нов с последната приложена конфигурация (последно приложено) и се навийте живея получен пластир.

Този подход се нарича 2-посочно сливане. Използва се например в Хелм.

Има и 3-посочно сливане, която се различава по това, че:

  • сравняване последно приложено и нов, разглеждаме какво е изтрито;
  • сравняване нов и живея, разглеждаме какво е добавено или променено;
  • сумираният пластир се прилага към живея.

Разполагаме над 1000 приложения с Helm, така че всъщност живеем с двупосочно сливане. Той обаче има редица проблеми, които сме разрешили с нашите пачове, които помагат на Helm да работи нормално.

Реално състояние на разпространение

След като нашата CI система генерира нова конфигурация за Kubernetes въз основа на следващото събитие, тя я предава за използване (Приложи) към клъстер - използвайки Helm или kubectl apply. След това се случва вече описаното N-way сливане, на което API на Kubernetes отговаря одобрително на CI системата, а това на нейния потребител.

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

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

За да направите всичко правилно, тази схема изисква допълнителна връзка - специален тракер, който ще получава информация за състоянието от Kubernetes API и ще я предава за по-нататъшен анализ на реалното състояние на нещата. Създадохме библиотека с отворен код в Go - cubedog (виж анонса му тук), който решава този проблем и е вграден в werf.

Поведението на този инструмент за проследяване на ниво werf се конфигурира с помощта на анотации, които се поставят в Deployments или StatefulSets. Основна анотация - fail-mode - разбира следните значения:

  • IgnoreAndContinueDeployProcess — пренебрегваме проблемите с внедряването на този компонент и продължаваме внедряването;
  • FailWholeDeployProcessImmediately — грешка в този компонент спира процеса на внедряване;
  • HopeUntilEndOfDeployProcess — надяваме се, че този компонент ще работи до края на внедряването.

Например тази комбинация от ресурси и стойности на анотация fail-mode:

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

Когато внедряваме за първи път, базата данни (MongoDB) може още да не е готова - внедряванията ще бъдат неуспешни. Но можете да изчакате момента, в който да започне, и внедряването все още ще се извърши.

Има още две анотации за kubedog в werf:

  • failures-allowed-per-replica — броя на разрешените падания за всяка реплика;
  • show-logs-until — регулира момента, до който werf показва (в stdout) регистрационни файлове от всички разгърнати подове. По подразбиране е PodIsReady (за да игнорираме съобщения, които вероятно не искаме, когато трафикът започне да идва към групата), но стойностите също са валидни: ControllerIsReady и EndOfDeploy.

Какво още искаме от внедряването?

В допълнение към вече описаните две точки, бихме искали:

  • да видиш трупи - и то само необходимите, а не всичко подред;
  • писта напредък, защото ако работата виси „безшумно“ за няколко минути, важно е да разберете какво се случва там;
  • имам автоматично връщане назад в случай, че нещо се обърка (и затова е изключително важно да знаете реалния статус на внедряването). Разгръщането трябва да е атомарно: или преминава до края, или всичко се връща в предишното си състояние.

Резултати от

За нас като компания, за да внедрим всички описани нюанси на различни етапи на доставка (изграждане, публикуване, внедряване), са достатъчни CI система и помощна програма werf.

Вместо заключение:

werf - нашият инструмент за CI / CD в Kubernetes (общ преглед и видео отчет)

С помощта на werf постигнахме добър напредък в решаването на голям брой проблеми за инженерите на DevOps и ще се радваме, ако по-широката общност поне опита тази помощна програма в действие. Ще бъде по-лесно да постигнете добър резултат заедно.

Видеоклипове и слайдове

Видео от представлението (~47 минути):

Представяне на доклада:

PS

Други доклади за Kubernetes в нашия блог:

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

Добавяне на нов коментар