Сегодня я планирую рассказать, как нужно писать приложения и какие есть требования для того, чтобы ваше приложение хорошо работало в Kubernetes. Чтобы с приложением не было никакой головной боли, чтобы не приходилось придумывать и выстраивать какие-то «костыли» вокруг него — и работало всё так, как это задумывалось самим Kubernetes.


Эта лекция в рамках «Вечерней школы Слёрма по Кубернетес». Вы можете просмотреть открытые теоретические лекции Вечерней Школы на Youtube, сгруппированные в плейлист. Для тех же, кому удобнее текст, а не видео, мы подготовили эту статью.


Зовут меня Павел Селиванов, на текущий момент я являюсь ведущим DevOps инженером компании Mail.ru Cloud Solutions, мы делаем «облака», мы делаем мэнедж-кубернетисы и так далее. В мои задачи сейчас как раз-таки входит помощь в разработке, раскатывание эти облаков, раскатывание приложения, которые мы пишем и непосредственно разработка инструментария, который мы предоставляем для наших пользователей.



Я DevOps-ом занимаюсь, думаю, что последние, наверное, года три. Но, в принципе, то, чем занимается DevOps, я занимаюсь, наверное, лет пять уже точно. До этого я занимался скорей больше админскими вещами. С Kubernetes я начал работать очень давно — уже, наверное, прошло порядка четырех лет с тех пор, как я начал с ним работать.


Вообще начинал я, когда у Kubernetes была версии 1.3, наверное, а может быть и 1.2 — когда он был еще в зачаточном состоянии. Сейчас он уже совсем не в зачаточном состоянии находится — и очевидно, что на рынке есть огромный спрос на инженеров, которые в Kubernetes хотели бы уметь. И у компаний есть очень большой спрос на таких людей. Поэтому, собственно, и появилась эта лекция.


Если говорить по плану того, о чем я буду рассказывать, это выглядит вот так, в скобочках написано (TL;DR) – «too long; don’t read». Моя сегодняшняя презентация будет представлять из себя бесконечные списки.



На самом деле, я сам не люблю такие презентации, когда их делают, но тут такая тема, что я когда готовил эту презентацию, я просто не придумал на самом деле, как по-другому организовать эту информацию.


Потому что по большому счету эта информация — это «ctrl+c, ctrl+v», из, в том числе нашей Вики в разделе DevOps, где у нас написаны требования к разработчикам: «ребята, чтобы ваше приложение мы запустили в Kubernetes, оно должно быть вот таким».


Поэтому получилась презентация таким большим списком. Извините. Постараюсь рассказывать максимально, чтобы было нескучно по возможности.


Что мы с вами сейчас будем разбирать:


  • это, во-первых, логи (журналы приложения?), что с ними делать в Kubernetes, как с ними быть, какими они должны быть;
  • что делать с конфигурациями в Kubernetes, какие для Kubernetes лучшие-худшие способы конфигурировать приложение;
  • поговорим о том, что такое проверки доступности вообще, как они должны выглядеть;
  • поговорим о том, что такое graceful shutdown;
  • поговорим еще раз про ресурсы;
  • еще раз затронем тему хранения данных;
  • и в конце я расскажу, что такое термин этот загадочный cloud-native приложение. Cloudnativeness, как прилагательное от этого термина.

Логи


Я предлагаю начать с логов — с того, куда эти логи нужно в Kubernetes пихать. Вот вы запустили в Kubernetes приложение. По классике, раньше приложения всегда писали логи куда-нибудь в файлик. Плохие приложения писали логи в файлик в домашней директории разработчика, который запустил это приложение. Хорошие приложения писали логи в файлик куда-нибудь в /var/log.



Соответственно, дальше, у хороших админов в инфраструктурах были настроены какие-нибудь штуки, которые эти логи могут ротировать — тот же самый rsyslog элементарно, который смотрит на эти логи и когда с ними что-то происходит, их становится много, он создает резервные копии, складывает туда логи, удаляет старые файлики, больше, чем за неделю, за полгода и еще за сколько-нибудь. По идее, у нас должно быть предусмотрено, чтобы просто тупо из-за того, что приложение пишет логи, место на серверах продакшна (боевых серверах?) не кончалось. И, соответственно, весь продакшн не останавливался из-за логов.


Когда мы переходим в мир Kubernetes и запускаем там все то же самое, первое, на что можно обратить внимание – это на то, что люди, как писали логи в файлике, так и продолжают их писать.


Оказывается, если мы будем говорить про Kubernetes, что правильным местом для того, чтобы из докер-контейнера куда-то написать логи, это просто писать их из приложения в так называемые Stdout/Stderr, то есть потоки стандартного вывода операционной системы, стандартного вывода ошибок. Это самый правильный, самый простой и самый логичный способ, куда девать логи в принципе в докере и конкретно в Кубернетисе. Потому что, если ваше приложение пишет логи в Stdout/Stderr, то дальше это уже задача докера и надстройки над ним Kubernetes, что с этими логами делать. Докер по умолчанию будет складывать свои специальные файлики в JSON формате.


Тут уже возникает вопрос, что вы с этими логами будете делать дальше. Самый простой способ, понятно, у нас есть возможность сделать kubectl logs и посмотреть эти логи этих «подов». Но, наверное, это не очень хороший вариант — с логами нужно что-то дальше делать.


Пока еще поговорим заодно, раз уж мы затронули тему логов, о такой штуке, как логи должны выглядеть. То есть, это не относится напрямую к Kubernetes, но когда мы начинаем задумываться о том, что делать с логами, хорошо бы задуматься и об этом тоже.


Нам нужен какой-то инструмент, по-хорошему, который эти логи, которые у нас докер складывает в свои файлики, возьмет и куда-то их отправит. По большому счету, обычно мы внутри Kubernetes запускаем в виде DaemonSet какой-нибудь агент – сборщик логов, которому просто сказано, где лежат логи, которые складывает докер. И этот агент-сборщик их просто берет, возможно, даже по пути как-то парсит, возможно обогащает какой-то дополнительной метаинформацией и, в итоге, отправляет на хранение куда-то. Там уже возможны вариации. Самое распространенное, наверное, Elasticsearch, где можно хранить логи, их можно оттуда удобно доставать. Потом с помощью запроса, с помощью Kibana, например, строить по ним графики, строить по ним алерты и так далее.


Самая главная мысль, еще раз ее хочу повторить, о том, что внутри докера, в частности, внутри Kubernetes, складывать ваши логи в файлик – это очень плохая идея.


Потому что во-первых, логи внутри контейнера в файлике проблематично доставать. Нужно сначала зайти в контейнер, сделать туда exec, а потом уже смотреть логи. Следующий момент – если у вас логи в файлике, то в контейнерах обычно минималистичное окружение и там нет утилит, которые нужны обычно для нормальной работы с логами. Их погрепать, посмотреть, открыть в текстовом редакторе. Следующий момент, когда у нас логи лежат в файлике внутри контейнера, в случае, если этот контейнер удалится, вы понимаете, логи погибнут вместе с ним. Соответственно, любой рестарт контейнера — и логов уже нет. Опять же, плохой вариант.


И последний момент – это то, что внутри контейнеров у вас обычно существует ваше приложение и все – оно обычно является единственным запущенным процессом. Ни о каком процессе, который бы ротировал файлы с вашими логами, речи вообще не идет. Как только логи начинают записываться в файлик, это значит, что, извините, продакшн сервера мы начнем терять. Потому что, во-первых, их проблематично найти, их никто не отслеживает, плюс их никто не контролирует — соответственно, файлик растет бесконечно, пока просто место на сервере не кончится. Поэтому, еще раз говорю, что логи в докере, в частности, в Kubernetes, в файлик – это затея плохая.


Следующий момент, тут я хочу опять же об этом поговорить – раз уж мы затрагиваем тему логов, то хорошо бы поговорить и о том, как логи должны выглядеть, для того, чтобы с ними было удобно работать. Как я говорил, тема не относится напрямую к Kubernetes, но зато очень хорошо относится к теме DevOps. К теме культуры разработки и дружбы двух этих разных отделов – Dev и Ops, чтобы всем было удобно.


Значит в идеале, на сегодняшний день, логи нужно писать в JSON формате. Если у вас какое-нибудь непонятное приложение ваше самописное, которое пишет логи в непонятных форматах, потому что вы там вставляете какой-нибудь print или что-то типа того, то самое время уже нагуглить какой-нибудь фрэймворк, какую-нибудь обертку, которая позволяет реализовывать нормальное логирование; включить там параметры логирования в JSON, потому что JSON простой формат, его парсить просто элементарно.


Если у вас JSON не прокатывает по каким-нибудь критериям, неизвестно каким, то хотя бы пишите логи в том формате, который можно парсить. Тут, скорее, стоит задумываться о том, что, например, если у вас запущена куча каких-нибудь контейнеров или просто процессов с nginx, и у каждого свои настройки логирования, то, наверное, кажется, вам парсить их будет очень неудобно. Потому что на каждую новый nginx instance вам нужно написать собственный парсер, потому что они пишут логи по-разному. Опять же, наверное, тут стоило задуматься о том, чтобы все эти nginx instance имели одну и ту же конфигурацию логирования, и абсолютно единообразно писали все свои логи. То же самое касается абсолютно всех приложений.


Я в итоге еще хочу масла в огонь добавить, о том, что в идеале, логов многострочного формата стоит избегать. Тут какая штука, если вы когда-то работали со сборщиками логов, то, скорее всего, вы видели, что они вам обещают, что они умеют работать с многострочными логами, умеют их собирать и так далее. На самом деле, нормально, полноценно и без ошибок собирать многострочные логи, не умеет, по-моему, на сегодняшний день ни один сборщик. По-человечески, чтобы это было удобно и без ошибок.



Но stack trace — это всегда многострочные логи и как их избегать. Тут вопрос в том, что лог – это запись о событии, а стактрэйс фактически логом не является. Если мы логи собираем и складываем их куда-нибудь в Elasticsearch и по ним потом рисуем графики, строим какие-нибудь отчеты работы пользователей на вашем сайте, то когда у вас вылазит stack trace – это значит, что у вас происходит какая-то непредвиденная, необработанная ситуация в вашем приложении. И stack trace имеет смысл закидывать автоматически куда-нибудь в систему, которая их умеет трэкать.


Это то ПО (то же Sentry), которое сделано специально для того, чтобы работать со stack trace. Оно может создавать сразу же автоматизированно задачи, назначать на кого-то, аллертить, когда стактрейсы происходят, группировать эти стактрэйсы по одному типу и так далее. В принципе, не имеет особого смысла говорить о стактрэйсах, когда мы говорим о логах, потому что это, все-таки, разные вещи с разным предназначением.


Конфигурация


Дальше у нас по поводу конфигурации в Kubernetes: что с этим делать и как приложения внутри Kubernetes должны конфигурироваться. Вообще, я обычно говорю о том, что докер – он не про контейнеры. Все знают, что докер – это контейнеры, даже те, кто особо с докером не работал. Повторюсь, Докер это не про контейнеры.


Докер, по моему мнению, это про стандарты. И там стандарты всего практически: стандарты сборки вашего приложения, стандарты установки вашего приложения.



И эта штука — мы ей пользовались и раньше, просто с приходом контейнеров это стало особо популярно — эта штука называется ENV (environment) переменные, то есть переменные окружения, которые есть в вашей операционной системе. Это вообще идеальный способ конфигурировать ваше приложение, потому что, если у вас есть приложения на JAVA, Python, Go, Perl не дай бог, и они все могут читать переменные database host, database user, database password, то идеально. У вас приложения на четырех разных языках конфигурируются в плане базы данных одним и тем же способом. Нет больше никаких разных конфигов.


Все можно сконфигурировать с помощью ENV переменных. Когда мы говорим про Kubernetes, там есть прекрасный способ объявлять ENV переменные прямо внутри Deployment. Соотвественно, если мы говорим про секретные данные, то секретные данные из ENV переменных (пароли к базам данных и т.д.), мы можем сразу запихать в секрет, создать секрет кластер и в описании ENV в Deployment указать, что мы не непосредственно объявляем значение этой переменной, а значение этой переменной database password будем читать из секрета. Это стандартное поведение Kubernetes. И это самый идеальный вариант конфигурировать ваши приложения. Просто на уровне кода, опять же к разработчикам это относится. Если вы DevOps, можно попросить: «Ребята, пожалуйста, научите ваше приложение читать переменные окружения. И будет нам счастье всем».


Если еще и все будут читать одинаково названные переменные окружения в компании, то это вообще шикарно. Чтобы не было такого, что одни ждут postgres database, другие database name, третьи database еще что-нибудь, четвертые dbn какой-нибудь там, чтобы, соответственно, единообразие было.


Проблема наступает, когда у вас переменных окружения становится так много, что просто открываешь Deployment — а там пятьсот строчек переменных окружения. В таком случае, переменные окружения вы уже просто переросли — и дальше уже не надо себя мучить. В таком случае, имело бы смысл начать пользоваться конфигами. То есть, обучить ваше приложение использовать конфиги.


Только вопрос в том, что конфиги – это не то, что вы думаете. Config.pi – это не тот конфиг, которым удобно пользоваться. Или какой-нибудь конфиг в вашем собственном формате, альтернативно одаренном – это тоже не тот конфиг, который я подразумеваю.


То, о чем я говорю, это конфигурация в приемлемых форматах, то есть, на сегодняшний день самым популярным стандартом является стандарт .yaml. Его понятно, как читать, он человекочитаемый, его понятно, как читать из приложения.


Соответственно, помимо YAML, можно еще, например, попользоваться JSON, парсить примерно так же удобно, как YAML в плане чтения оттуда конфигурации приложения. Читать людьми заметно неудобнее. Можно попробовать формат, а-ля ini. Его читать прям совсем удобно, с точки зрения человека, но его может быть неудобно автоматизированно обрабатывать, в том плане, если вы когда-то захотите генерить свои конфиги, вот ini формат уже может быть неудобно генерить.


Но в любом случае, какой бы формат вы ни выбрали, суть в том, что с точки зрения Kubernetes — это очень удобно. Вы весь свой конфиг можете положить внутри Kubernetes, в ConfigMap. И потом этот configmap взять и попросить внутри вашего пода замонтировать в какую-нибудь определенную директорию, где ваше приложение будет конфигурацию из этого configmap читать так, как будто бы это просто файлик. Это, собственно, то, что хорошо делать, когда у вас в приложении становится конфигурационных опций много. Или просто структура какая-нибудь сложная, вложенность есть.


Если у вас есть configmap, то очень прекрасно вы можете научить свое приложение, например, автоматически отслеживать изменения в файле, куда замонтирован configmap, и еще и автоматически релоадить ваше приложение при изменении конфигов. Это вообще идеальный вариант был бы.


Об этом я уже тоже, опять же, говорил — секретную информацию не в конфигмап, секретную информацию не в переменные, секретную информацию в секреты. Оттуда эту секретную информацию подключать в диплойменты. Обычно мы все описания кубернетовских объектов, диплойменты, конфигмапы, сервисы храним в git. Соответственно, запихивать пароль к базе данных в git, даже если это ваш git, который у вас внутренний в компании – плохая идея. Потому что, как минимум, git помнит все и удалить просто оттуда пароли не так просто.


Health check


Следующий момент – это о такая штука, которая называется Health check. Вообще, Health check – это просто проверка того, что ваше приложение работает. При этом мы чаще всего говорим о неких веб-приложениях, у которых, соответственно, с точки зрения хэлсчека(лучше не переводить здесь и далее) это будет какой-нибудь специальный URL, который они обрабатывают стандартно, обычно делают /health.


При обращении на этот URL, соответственно, наше приложение говорит или «да, окей, у меня все хорошо 200» или «нет, у меня всё не хорошо, какой-нибудь 500». Соответственно, если у нас приложение не http, не веб-приложение, мы сейчас говорим какой-нибудь демон, мы можем придумать, как делать хэлсчеки. То есть, необязательно, если приложение не http, то все работает без хэлсчека и сделать это никак нельзя. Можно обновлять периодически информацию в файлике какую-нибудь, можно придумать к вашему daemon специальную какую-нибудь команду, типа, daemon status, которая будет говорить «да, все нормально, daemon работает, он живой».


Для чего это нужно? Первое самое очевидное, наверное, для чего нужен хэлсчек — для того, чтобы понимать, что приложение работает. То есть, просто тупо, когда оно сейчас поднято, оно выглядит как рабочее, чтобы точно быть уверенным, что оно работает. А то получается так, что под с приложением запущен, контейнер работает, инстенс работает, все нормально — а там пользователи уже оборвали все телефоны у техподдержки и говорят «вы что там, ..., заснули, ничего не работает».


Вот хэлсчек — это как раз-таки такой способ увидеть с точки зрения пользователя, что оно работает. Один из способов. Давайте говорить так. С точки зрения Kubernetes, это еще и способ понимать, когда приложение запускается, потому что мы понимаем, что есть разница между тем, когда запустился контейнер, создался и стартанул и когда непосредственно в этом контейнере запустилось приложение. Потому что если мы возьмем какое-нибудь среднестатистическое java-приложение и попробуем его запустить в доке, то секунд сорок, а то и минуту, а то и десять, оно может прекрасно стартовать. При этом можно хоть обстучаться в его порты, оно там не ответит, то есть оно еще не готово принимать трафик.


Опять же, с помощью хэлсчека и с помощью того, что мы обращаемся сюда, мы можем понимать в Kubernetes, что в приложении не просто контейнер поднялся, а прям приложение стартануло, оно на хэлсчек уже отвечает, значит туда можно запускать трафик.



То, о чем я говорю сейчас, называется Readiness/Liveness пробы в рамках Kubernetes, соответственно, readiness пробы у нас как раз-таки отвечают за доступность приложения в балансировке. То есть если readiness пробы выполняются в приложении, то значит все окей, на приложение идет клиентский трафик. Если readiness пробы не выполняются, то приложение просто не участвует, конкретно этот instance, не участвует в балансировке, оно убирается с балансировки, клиентский трафик не идет. Соответственно, Liveness пробы в рамках Kubernetes нужны для того, чтобы в случае, если приложение «залипло», его «рестартануть». Если liveness проба не работает у приложения, которое объявлено в Kubernetes, то приложение не просто убирается с балансировки, оно именно рестартится.


И тут такой важный момент, о котором хотелось бы сказать, с точки зрения практики, обычно все-таки чаще используется и чаще нужна readiness проба, чем liveness проба. То есть просто бездумно объявлять и readiness, и liveness пробы, потому что Kubernetes так умеет, а давайте использовать всё, что он умеет – не очень хорошая идея. Объясню почему. Потому что есть пунктик номер два в пробах – это о том, что неплохо было бы проверять нижележащий сервис в ваших хэлсчеках. Это значит, что если у вас есть веб-приложение, отдающее какую-то информацию, которую в свою очередь оно, естественно, должно где-то взять. В базе данных, например. Ну, и сохраняет в эту же базу данных информацию, которая в это REST API поступает. То соответственно, если у вас хэлсчек отвечает просто типа обратились на слешхэлс, приложение говорит «200, окей, все хорошо», а при этом у вас у приложения база данных недоступна, а приложение на хэлсчек говорит «200, окей, все хорошо» — это плохой хэлсчек. Так не должно работать.


То есть, ваше приложение, когда к нему приходит запрос на /health, оно не просто отвечает, «200, ок», оно сначала сначала идет, например, в базу данных, пробует подключиться к ней, делает там что-нибудь совсем элементарное, типа селект один, просто проверяет, что в базе данных коннект есть и в базу данных можно выполнить запрос. Если это все прошло успешно, то отвечает «200, ок». Если не прошло успешно, то говорит, что ошибка, база данных недоступна.


Поэтому, в связи с этим, опять же возвращаюсь к Readiness/Liveness пробам — почему readiness проба скорее всего вам нужна, а liveness проба под вопросом. Потому что, если вы будете описывать хэлсчеки именно так, как я сейчас сказал, то получится у нас недоступна в части instanceв или со всех instanceв база данных, к примеру. Когда у вас объявлена readiness проба, у нас хэлсчеки начали фэйлиться, и приложения соответственно все, с которых недоступна база данных, они выключаются просто из балансировки и фактически «висят» просто в запущенном состоянии и ждут, пока у них базы данных заработают.


Если у нас объявлена liveness проба, то представьте, у нас сломалась база данных, а у вас в Kubernetes половина всего начинает рестартовать, потому что liveness-проба падает. Это значит, нужно рестартовать. Это совсем не то, что вы хотите, у меня даже был в практике личный опыт. У нас было приложение, которое было чатом и которое было написано в JS и входило в базу данных Mongo. И была как раз-таки проблема в том, что это было на заре моей работы с Kubernetes, мы описывали readiness, liveness пробы по принципу того, что Kubernetes умеет — значит будем использовать. Соответственно, в какой-то момент Mongo немножко «затупила» и проба начала фэйлиться. Соответственно, по ливнесс пробе поды начали «убиваться».


Как вы понимаете, они когда «убиваются», это чат, то есть, к нему висят куча соединений от клиентов. Они тоже «убиваются» — нет не клиенты, только соединения — все не одновременно и из-за того, что не одновременно убиваются, кто-то раньше, кто-то позже, они не одновременно стартуют. Плюс стандартный рандом, мы не можем предсказать с точностью до миллисекунды время старта каждый раз приложения, поэтому они это делают по одному instance. Один инфоспот поднимается, добавляется в балансировку, все клиенты приходят туда, он не выдерживает такой нагрузки, потому что он один, а их там работает, грубо говоря, десяток, и падает. Поднимается следующий, на него вся нагрузка, он тоже падает. Ну, и каскадом просто продолжаются эти падения. В итоге чем это решилось – просто пришлось прям жестко остановить трафик пользователей на это приложение, дать всем instance подняться и после этого разом запустить весь трафик пользователей для того, чтобы он уже распределился на все десять instances.


Не будь бы это liveness проба объявленной, которая заставила бы это все рестартовать, приложение бы отлично с этим справилось. Но у нас отключается все из балансировки, потому что базы данных недоступны и все пользователи «отвалились». Потом, когда эта база данных стала доступна, все включается в балансировку, но приложениям не нужно заново стартовать, не нужно тратить на это время и ресурсы. Они все уже здесь, они готовы к трафику, соответственно, просто открывается трафик, все отлично – приложение на месте, все продолжает работать.


Поэтому, readiness и liveness пробы это разное, даже более того, можно теоретически делать разные хэлсчеки, один типа рэди, один типа лив, например, и проверять разные вещи. На readiness пробах проверять бэкенды ваши. А на liveness пробе, например, не проверять, с точки зрения того, что liveness пробы это вообще просто приложение отвечает, если вообще оно в состоянии ответить.


Потому что liveness проба по большому счету это когда мы «залипли». Бесконечный цикл начался или еще что-нибудь — и больше запросы не обрабатываются. Поэтому имеет смысл даже их разделять — и разную логику в них реализовывать.


По поводу того, чем нужно отвечать, когда у вас происходит проба, когда вы делаете хэлсчеки. Просто это боль на самом деле. Те, кто знаком с этим, наверное, будут смеяться — но серьезно, я видел сервисы в своей жизни, которые в ответ на запросы в стопроцентных случаях отвечают «200». То есть, который успешен. Но при этом в теле ответа они пишут «ошибка такая-то».


То есть статус ответа вам приходит — все успешно. Но при этом вы должны пропарсить тело, потому что в теле написано «извините, запрос завершился с ошибкой» и это прям реальность. Это я видел в реальной жизни.


И чтобы кому-то не было смешно, а кому-то очень больно, все-таки стоит придерживаться простого правила. В хэлсчеках, да и в принципе в работе с веб-приложениями.


Если все прошло успешно, то отвечайте двухсотым ответом. В принципе, любой двухсотый ответ устроит. Если вы очень хорошо читали рэгси и знаете, что одни статусы ответа отличаются от других, отвечайте подходящим: 204, 5, 10, 15, что угодно. Если не очень хорошо, то просто «два ноль ноль». Если все пошло плохо и хэлсчек не отвечает, то отвечаете любым пятисотым. Опять же, если вы понимаете, как нужно отвечать, чем отличаются разные статусы ответа друг от друга. Если не понимаете, то 502 – ваш вариант отвечать на хэлсчеки, если что-то пошло не так.


Такой еще момент, хочу немного вернуться по поводу проверки нижележащих сервисов. Если вы начнете, например, проверять все нижележащие сервисы, которые стоят за вашим приложением — вообще все. То у нас получается с точки зрения микросервисной архитектуры, у нас есть такое понятие, как «низкое зацепление» — то есть, когда ваши сервисы между собой минимально зависимы. В случае выхода одного из них из строя, все остальные без этого функционала просто продолжат работать. Просто часть функционала не работает. Соответственно, если вы все хэлсчеки завяжете друг на друга, то у вас получится, что в инфраструктуре упало что-то одно, а из-за того, что оно упало, все хэлсчеки всех сервисов тоже начинают фэйлиться — и инфраструктуры вообще всей микросервисной архитектуры больше нет. Там все вырубилось.


Поэтому я хочу повторить это еще раз, что проверять нижележащие сервисы нужно те, без которых ваше приложение в ста процентах случаях не может выполнять свою работу. То есть, логично, что если у вас REST API, через который пользователь сохраняет в базу или достает из базы, то в случае отсутствия базы данных, вы не можете работать гарантированно с вашими пользователями.


Но если у вас пользователи, когда вы их достаете из базы данных, дополнительно обогащаются еще какими-нибудь метаданными, еще из одного бэкенда, в который вы входите перед тем, как отдать ответ на фронтенд — и этот бэкенд недоступен, это значит, что вы отдадите ваш ответ без какой-то части метаданных.


Дальше у нас тоже одна из больных тем при запуске приложений.


На самом деле, это не только Kubernetes касается по большому счету, просто так совпало, что культура какая-то разработки массово и DevOps в частности начала распространяться примерно в то же время, что и Kubernetes. Поэтому по большому счету, что оказывается нужно корректно (graceful) завершать работу вашего приложения и без Kubernetes. И до Kubernetes люди так делали, но просто с приходом Kubernetes мы про это начали массово говорить.


Graceful Shutdown


В общем, что такое Graceful Shutdown и вообще для чего это нужно. Это о том, что когда ваше приложение по какой-то причине завершает свою работу, вам нужно выполнить app stop — или вы получаете, например, сигнал операционной системы, ваше приложение его должно понимать и что-то с этим делать. Самый плохой вариант, конечно, это когда ваше приложение получает SIGTERM и такой типа «SIGTERM, висим дальше, работаем, ничего не делаем». Это прям откровенно плохой вариант.



Практически настолько же плохой вариант, это когда ваше приложение получает SIGTERM и такой типа «сказали же сегтерм, все значит завершаемся, не видел, не знаю никаких пользовательских запросов, не знаю что у меня там за запросы сейчас в работе, сказали SIGTERM, значит завершаемся». Это тоже плохой вариант.


А какой вариант хороший? Первым пунктом, учитываем завершение операций. Хороший вариант – это когда ваш сервер все-таки учитывает то, что он делает, если ему приходит SIGTERM.


SIGTERM – это мягкое завершение работы, оно специально такое, его можно перехватить на уровне кода, его можно обработать, сказать о том, что сейчас, подождите, мы сначала закончим ту работу, которая у нас есть, после этого завершимся.


С точки зрения Kubernetes, как это выглядит. Когда мы говорим поду, который работает в кластере Kubernetes, «пожалуйста, остановись, удались» или у рестарт нас происходит, или обновление, когда Kubernetes пересоздает поды — Kubernetes присылает в под сообщение как раз-таки SIGTERM, ждет какое-то время, причем, вот это время, которое он ждет, оно тоже конфигурируется, есть такой специальный параметр в диплойментах и он называется Graceful ShutdownTimeout. Как вы понимаете, неспроста он так называется и неспроста мы об этом сейчас говорим.


Там можно конкретно сказать, сколько нужно подождать между тем, когда мы послали в приложение SIGTERM и когда мы поймем, что приложение кажется от чего-то офигело или, «залипло» и не собирается завершаться — и нужно послать ему SIGKILL, то есть, жестко завершить его работу. То есть, соответственно, у нас работает какой-то демон, он обрабатывает операции. Мы понимаем, что в среднем у нас операции, над которыми работает демон, не длятся больше 30 секунд за раз. Соответственно, когда приходит SIGTERM, мы понимаем, что наш демон максимум может после SIGTERM дорабатывать 30 секунд. Мы его пишем, например, 45 секунд на всякий случай и говорим, что SIGTERM. После этого 45 секунд ждем. По идее, за это время демон должен был завершить свою работу и завершиться сам. Но если вдруг он не смог, значит, он, скорее всего, залип — он больше не обрабатывает наши запросы нормально. И его можно через 45 секунд смело, собственно, прибить.


Причем тут, на самом деле, даже 2 аспекта можно учитывать. Во-первых, понимать, что, если у вас запрос пришел, вы начали с ним как-то работать и не дали ответ пользователю, а вам пришел SIGTERM, например. То имеет смысл доработать и дать ответ пользователю. Это пункт номер раз в этом плане. Пункт номер два тут будет то, что если вы пишите свое приложение, вообще архитектуру строите таким образом, что у вас пришел запрос на ваше приложение, дальше вы запустили какую-то работу, файлики начали откуда-то качать, базу данных перекачивать и еще что-то. В общем, ваш пользователь, ваш запрос висит полчаса и ждет, пока вы ему ответите — то, скорее всего, вам нужно над архитектурой поработать. То есть просто учитывать даже здравый смысл, что если у вас операции короткие, то имеет смысл SIGTERM игнорировать и дорабатывать. Если у вас операции длинные, то не имеет смысла в данном случае игнорировать SIGTERM. Имеет смысл переработать архитектуру так, чтобы таких длинных операций избежать. Чтобы пользователи просто не висели, не ждали. Не знаю, сделать там какой-нибудь websocket, сделать обратные хуки, которые ваш сервер уже будет отправлять клиенту, что угодно другое, но не заставлять пользователя полчаса висеть и ждать просто сессию, пока вы ему ответите. Потому что непредсказуемо, где она может порваться.


Когда ваше приложение завершается, следует отдавать какой-нибудь адекватный exit-код. То есть если ваше приложение попросили закрыться, остановиться, и оно нормально само смогло остановиться, то не нужно возвращать какой-нибудь там типа exit-код 1,5,255 и так далее. Все что не нулевой код, по крайней мере, в Linux системах, я в этом точно уверен, считается неуспешным. То есть считается, что ваше приложение в таком случае завершилось с ошибкой. Соответственно, по-хорошему, если ваше приложение завершилось без ошибки, вы говорите 0 на выходе. Если ваше приложение завершилось с ошибкой по каким-то там поводам, вы говорите не 0 на выходе. И с этой с информацией можно работать.


И последний вариант. Плохо, когда у вас пользователь кидает запрос и полчаса висит, пока вы его обработаете. Но в целом еще хочется сказать о том, что вообще стоит со стороны клиента. Неважно у вас мобильное приложение, фронтенд и так далее. Необходимо учитывать, что вообще сессия пользователя может оборваться, может произойти, что угодно. Может быть запрос послан, например, недообработаться и не вернуться ответ. Ваш фронтенд или ваше мобильное приложение — вообще любой фронтенд, давайте говорить так — должны это учитывать. Если вы работаете с websocket`ами, это вообще самая страшная боль, которая была в моей практике.


Когда разработчики каких-нибудь очередных чатов не знают, что, оказывается, websocket может порваться. У них когда там элементарно на проксе что-нибудь происходит, мы просто конфиг меняем, и она делает reload. Естественно, все долгоживущие сессии рвутся при этом. К нам прибегают разработчики и говорят: «Ребята, вы чего, у нас там у всех клиентов чат поотваливался!». Мы им говорим: «Вы чего? Ваши клиенты не могут переподключаться?». Они говорят: «Нет, нам надо, чтобы сессии не рвались». Короче говоря, это бред на самом деле. Нужно учитывать сторону клиента. Особенно, я же говорю, с долгоживущими сессиями типа websocket, что она может порваться и незаметно для пользователя нужно уметь такие сессии переустановить. И тогда вообще все идеально.


Ресурсы


Вообще, тут я давайте расскажу вам просто прям историю. Опять же из реальной жизни. Самую больную, которую я слышал про ресурсы.


Ресурсы в данном случае, я имею в виду, какие-то реквесты, лимиты, которые вы можете ставить на поды в ваших Kubernetes кластерах. Самое смешное, что я слышал от разработчика… Как-то один из коллег разработчиков на предыдущем месте работы сказал: «У меня приложение в кластере не запускается». Я посмотрел, что оно не запускается, а там оно то ли не влазило по ресурсам, то ли они очень маленькие лимиты поставили. Короче, приложение не может запуститься из-за ресурсов. Я говорю: «Из-за ресурсов не запускается, вы определитесь сколько вам нужно и поставьте адекватное значение». Он говорит: «Что за ресурсы?». Я ему начал объяснять, что вот Kubernetes, лимиты в реквестах и бла, бла, бла их нужно ставить. Человек слушал пять минут, кивал и говорит: «Я пришел сюда работать разработчиком, не хочу знать ничего ни про какие ресурсы. Я пришел типо сюда писать код и все». Это грустно. Это очень грустная концепция с точки зрения разработчика. Особенно в современном мире, так сказать, прогрессирующего девопса.


Для чего вообще нужны ресурсы? Ресурсы в Kubernetes бывают 2 типов. Одни называются реквестами, вторые – лимитами. Под ресурсами мы будем понимать, в основном всегда всего два ограничения базовых. То есть ограничения по процессорному времени и ограничения по оперативной памяти для контейнера, который запущен в Kubernetes.


Лимит ограничивает верхнюю границу для использования ресурса в вашем приложении. То есть, соответственно, если вы говорите 1Гб ОП в лимитах, то больше 1Гб ОП ваше приложение использовать не сможет. А если вдруг захочет и попытается это сделать, то придет процесс, который называется oom killer, out of memory то есть, и убьет ваше приложение — то есть оно просто рестартанет. По CPU приложения не рестартятся. По CPU, если приложение пытается использовать сильно много, больше, чем указано в лимитах, CPU будет просто жёстко отбираться. К рестартам это не приводит. Это вот лимит – это верхняя граница.


А есть реквест. Реквест – это то, благодаря чему Kubernetes понимает, как ноды в вашем кластере Kubernetes заполнены приложениями. То есть реквест – это такой некий commit вашего приложения. Оно говорит, что я хочу использовать: «Я хотело бы, чтобы ты под меня зарезервировал вот столько CPU и вот столько памяти». Такая простая аналогия. Что если у нас есть node, которой всего доступно, не знаю, 8 CPU. И туда приезжает под, у которого в реквестах написано 1 CPU, значит у нода осталось 7 CPU. То есть, соответственно, как только на эту ноду приедет 8 подов, у которых у каждого в реквестах стоит под 1 цпу — на ноде, как бы с точки зрения Kubernetes, CPU кончилось и больше подов с реквестами на эту ноду запустить нельзя. Если на всех нодах CPU кончится, то Kubernetes начнет говорить, что в кластере нет подходящих нод, для того чтобы запустить ваши поды, потому что кончилось CPU.


Для чего нужны реквесты и почему без реквестов, я думаю, что в Kubernetes запускать ничего не нужно? Давайте представим, себе гипотетическую ситуацию. Вы запускаете без реквестов ваше приложениие, Kubernetes не знает, сколько у вас чего, на какие ноды можно запихивать. Ну и пихает, пихает, пихает на ноды. В какой-то момент у вас на ваше приложение начнет приходить трафик. И одно из приложений начинает внезапно вдруг использовать ресурсы до лимитов, которые по лимитам у него есть. Оказывается, что рядом еще одно приложение и ему тоже нужны ресурсы. На ноде начинает реально физически кончаться ресурсы, например, ОП. На ноде начинают реально физически кончаться ресурсы, например, оперативная память (ОП). Когда у ноды кончается ОП, у вас перестанут отвечать в первую очередь докер, потом кублет, потом ОС. Они просто уйдут в несознанку и работать у вас ВСЕ перестанет точно. То есть это приведет к тому, что у вас залипнет нода, и ее нужно будет рестартануть. Короче, ситуация не очень хорошая.


А когда у вас реквесты, лимиты еще не очень сильно отличаются, хотя бы не в разы лимитов больше или реквестов, то у вас может быть такое нормальное рациональное заполнение приложений по нодам кластеров Kubernetes. При этом Kubernetes примерно в курсе, сколько чего он куда положил, сколько где чего используется. То есть это просто такой момент. Его важно понимать. И важно контролировать, что это проставляется.


Хранение данных


Следующий пунктик у нас это про хранение данных. Что с ними делать и вообще, как быть с persistence в Kubernetes?


Я думаю, опять же у нас, в рамках Вечерней Школы, тема про базу данных в Kubernetes была. И мне кажется, что я даже примерно знаю, что сказали вам коллеги на вопрос: «Можно ли запускать в Kubernetes базу данных?». Мне почему-то, кажется, что коллеги вам должны были сказать, что, если вы задаете вопрос, можно ли в Kubernetes запускать базу данных, значит нельзя.


Тут логика простая. На всякий случай еще раз поясню, если вы прям крутой чувак, который может построить достаточно отказоустойчивую систему распределенного сетевого хранения, понять, как подтянуть под это дело базу данных, как вообще cloud native в контейнерах должны в базе данных работать. Скорее всего, у вас нет вопроса, как это запускать. Если у вас такой вопрос есть, и вы хотите сделать так, чтобы это все развернулось и стояло прям насмерть в продакшене и никогда не падало, то такого не бывает. Вот прям прострелите себе ногу гарантировано при таком подходе. Поэтому лучше не надо.


Что делать с данными, которые наше приложение хотело бы хранить, какие-нибудь картинки, которые загружают пользователи, какие-нибудь штуки, которые в процессе работы генерирует наше приложение, при старте, например? Что с ними в Kubernetes делать?


Вообще в идеале, да, конечно, Kubernetes очень хорошо предусмотрен и вообще изначально задумывался под stateless приложения. То есть под те приложения, которые вообще не хранят информацию. Это идеальный вариант.


Но, естественно, идеальный вариант, он как бы существует далеко не всегда. Поэтому ну что? Пункт первый и самый простой – возьмите какой-нибудь S3, только не самопальное, которое тоже непонятно как работает, а от какого-нибудь провайдера. Хорошего нормального провайдера — и научите ваше приложение пользоваться S3. То есть когда у вас пользователь хочет загрузить файлик, скажите «вот сюда, пожалуйста, в S3 его загрузи». Когда он захочет его получить, скажите: «Вот тебе ссылочка на S3 бэк и вот отсюда его возьми». Это идеальный вариант.


Если вдруг по каким-то причинам такой идеальный вариант не подходит, у вас приложение, которое не вы пишите, не вы разрабатываете, или оно какое-нибудь жуткое легаси, оно не может по S3 протоколу, но должно работать с локальными директориями в локальных папочках. Возьмите что-нибудь более-менее простое, разверните это дело Kubernetes. То есть сразу городить Ceph под какие-то минимальные задачи, мне кажется, идея плохая. Потому что Ceph, конечно, хорошо и модно. Но если вы не очень понимаете, что делаете, то сложив что-то на Ceph, можно очень легко и просто это оттуда потом больше никогда не достать. Потому что, как известно, Ceph хранит данные у себя в кластере в бинарном виде, а не в виде простых файликов. Поэтому если вдруг кластер Ceph сломается, то есть полная и большая вероятность оттуда данные свои больше никогда не достать.


У нас будет курс по Ceph, можете ознакомиться с программой и оставить заявку.


Потому лучше что-нибудь простое типа на NFS сервера. Kubernetes умеет с ними работать, вы можете подмонтировать директорию под NFS сервер — ваше приложение просто как локальную директорию. При этом, естественно, нужно понимать, что опять же, с вашим NFS нужно что-то делать, нужно понимать, что иногда, возможно, оно станет недоступным и предусмотреть вопрос, что вы будете делать в таком случае. Возможно, его стоит бэкапить куда-то на отдельный машину.


Следующий момент, о котором я говорил, это что делать, если в процессе работы ваше приложение генерирует какие-нибудь файлики. К примеру, оно, запускаясь, генерирует какой-нибудь статический файлик, который основан на какой-нибудь информации, которое приложение получает только в момент запуска. Тут какой момент. Если таких данных немного, то можно вообще не заморачиваться, просто под себя это приложение положить и работать. Только тут вопрос какой, смотрите. Очень часто легаси всякие системы, типа вордпресса и прочее, особенно с доработанными хитроумными какими-нибудь плагинами, хитроумными пхп-разработчиками, они часто умеют делать так, что они генерят какой-нибудь файлик под себя. Соответственно, один генерирует один файлик, второй генерит второй файлик. Они разные. Балансировка происходит в кластере Kubernetes клиентов просто случайно. Соответственно, вместе получается в instance работать не умеют. Один отдает одну информацию, другой отдает пользователю другую информацию. Вот такого стоит избегать. То есть в Kubernetes все, что вы запускаете, гарантировано должно уметь работать в несколько instances. Потому что Kubernetes штука подвижная. Соответственно, подвинуть он может что угодно, когда угодно, ни у кого не спрашивая в целом. Поэтому на это нужно рассчитывать. Все запущенное в один instance рано или поздно когда-нибудь упадет. Чем больше, соответственно, у вас резервирования, тем лучше. Но опять же, я говорю, если у вас таких файликов немного, то их можно прям под себя положить, они небольшой объем весят. Если их становится чуть больше, внутрь контейнера их, наверное, пихать не стоит.


Я бы посоветовал, есть в Kubernetes такая прекрасная штука, вы можете использовать volume. В частности, есть volume типа empty dir. То есть это просто Kubernetes автоматически на том сервере, где вы запустились, создаст директорию у себя в служебных директориях. И даст вам ее, чтобы вы ей пользовались. Тут только один важный момент. То есть ваши данные будут храниться не внутри контейнера, а как бы на хосте, на котором вы запущены. Kubernetes более того такие empty dirs при нормальной настройке умеет контролировать и умеет контролировать их максимальный размер и не давать его превышать. Единственный момент то, что у вас записано в empty dir, при рестартах пода не теряется. То есть если ваш под по ошибке упадет и поднимется снова, информация в empty dir никуда не денется. Под ею снова при новом старте может воспользоваться — и это хорошо. Если ваш под куда-то уедет, то естественно, он уедет без данных. То есть, как только под с нода, где он был запущен с empty dir, пропадает, empty dir удаляется.


Чем еще хорош empty dir? Например, им можно пользоваться как кэшем. Давайте представим, что у нас приложение генеририт что-то на лету, отдает пользователям и это делает долго. Поэтому приложение, например, генерит и отдает пользователям, и заодно складывает под себя куда-нибудь, чтобы в следующий раз, когда пользователь за тем же самым придет, отдать это сразу сгенерированное быстрее. Empty dir можно попросить Kubernetes создать в памяти. И таким образом, у вас кэши вообще могут просто молниеносно работать — в плане скорости обращения к диску. То есть у вас empty dir в памяти, в ОС он хранится в памяти, а при этом для вас, для пользователя внутри пода это выглядит, как просто локальная директория. Вам не нужно приложение специально учить какой-то магии. Вы просто прям берете и кладете в директорию ваш файлик, но, на самом деле, в память на ОС. Это тоже очень удобная фишка в плане Kubernetes.


Какие проблемы у Minio существуют? Главная проблема Minio – это в том, что для того, чтобы эта штука работала, ей нужно где-то быть запущенной, и по ней должна быть какая-нибудь файловая система, то есть хранилище. А тут мы встречаемся с теми же самыми проблемами, которые есть у Ceph. То есть Minio где-то должно хранить свои файлы. Оно просто HTTP интерфейс к вашим файлам. Причем с функционалом явно победнее, чем у амазоновской S3. Раньше оно не умело нормально авторизовывать пользователя. Сейчас оно, насколько я знаю, оно уже умеет делать buckets с разной авторизацией, но опять же, мне кажется, главная проблема – это, так сказать, нижележащая система хранения под минимум.


Empty dir в памяти как влияет на лимиты? Никак не влияет на лимиты. Он лежит в памяти, получается, хоста, а не в памяти вашего контейнера. То есть ваш контейнер не видит empty dir в памяти, как часть занятой своей памяти. Это видит хост. Соответственно, да, с точки зрения kubernetes, когда вы начинаете такое использовать, хорошо бы понимать, что вы часть своей памяти отдаете под empty dir. И соответственно, понимать, что память может кончиться не только из-за приложений, но и из-за того, кто-то в эти empty dir пишет.


Cloudnativeness


И заключительная подтема, что такое Cloudnative. Зачем оно нужно. Cloudnativeness и так далее.


То есть те приложения, которые умеют и написаны для того, чтобы работать в современной облачной инфраструктуре. Но, на самом деле, у Cloudnative есть еще один такой аспект. Что это не только приложение, которое учитывает все требования современной облачной инфраструктуры, но и умеет с этой современной облачной инфраструктурой работать, использовать плюсы и преимущества того, что оно в этих облаках работает. Не просто перебарывать себя и работать в облаках, а прям использовать плюсы от того, что вы работаете в облаке.



Давайте возьмем для примера просто Kubernetes. У вас приложение запущено в Kubernetes. У вас всегда ваше приложение может, точнее админы для вашего приложения, всегда могут сделать сервис-аккаунт. То есть учетку для авторизации в самом Kubernetes в его сервере. Накрутить туда какие-нибудь права, которые нам нужны. И вы можете обращаться в Kubernetes из вашего приложения. Что таким образом можно делать? Например, из приложения получать данные о том, где находятся другие ваши приложения, другие такие же инстансы и вместе как-нибудь кластеризоваться поверх Kubernetes, если такая необходимость есть.


Опять же, недавно буквально у нас был кейс. У нас есть один контролер, который занимается тем, что мониторит очередь. И когда в этой очереди появляются какие-то новые задачи, он идет в Kubernetes — и внутри Kubernetes создает новый под. Дает этому поду какую-то новую задачу и в рамках этого пода, под выполняет задачу, отправляет ответ в сам контролер, и контролер потом с этой информацией что-то делает. Базу данных, к примеру, складывает. То есть это опять же плюс того, что наше приложение работает в Kubernetes. Мы можем использовать сам встроенный функционал Kubernetes для того, чтобы как-то расширить и сделать более удобным функционал нашего приложения. То есть не городить какую-то магию того, как запускать приложение, как запускать воркер. В Kubernetes просто закинуть запрос в аппе, если приложение написано на питоне.


То же самое касается, если мы выйдем за пределы Kubernetes. У нас где-то наш Kubernetes запущен — хорошо, если в каком-то облаке. Опять же, мы можем использовать, и даже должны, я считаю, использовать возможности самого облака, где мы запущены. Из элементарного, что нам облако предоставляет. Балансировка, то есть мы можем создавать балансировщики облачные, их использовать. Это прям преимущество того, чем мы можем пользоваться. Потому что облачная балансировка, во-первых, просто тупо снимает ответственность с нас за то, как оно работает, как оно настраивается. Плюс очень удобно, потому что обычный Kubernetes с облаками умеет интегрироваться.


Тоже самое касается масштабирования. Обычный Kubernetes умеет интегрироваться с облачными провайдерами. Умеет понимать, что если в кластере закончились ноды, то есть место нода закончилось, то нужно добавить — сам Kubernetes добавит вам новых нод в свой кластер и начнет на них запускать поды. То есть когда у вас нагрузка приходит, у вас подов начинает количество увеличиваться. Когда ноды в кластере заканчиваются для этих подов, то Kubernetes запускает новые ноды и, соответственно, количество подов еще может увеличиваться. И это очень удобно. Это прям возможность на лету масштабировать кластер. Не очень быстро, в плане того, что это не секунда, это скорее минута, для того чтобы добавлять новые ноды.


Но из моего опять же опыта, самое такое клевое, что я видел. Когда на основе времени суток кластер Cloudnative скейлился. Это был такой бэкенд сервис, которым пользовались люди в бэкофисе. То есть они приходят на работу в 9 утра, начинают заходить в систему, соответственно, кластер Cloudnative, где это все запущено, начинает пухнуть, запускать новые поды, для того чтобы все, кто пришел на работу, могли с приложением работать. Когда они в 8 вечера или в 6 вечера уходят с работы, кластеры Kubernetes замечают, что больше никто не пользуется приложением и начинают уменьшаться. Экономия прям процентов 30 гарантированная. Это тогда работало в Амазоне, в России на тот момент не было вообще никого, кто бы так круто умел делать.


Я прям говорю, экономия процентов 30 просто из-за того, что мы пользуемся Kubernetes, пользуемся возможностями облака. Сейчас такое можно делать в России. Я не буду никого рекламировать, конечно, но скажем так, есть провайдеры, которые такое умеют делать, предоставлять прямо с коробки по кнопке.


Последний момент, на который тоже хочу обратить внимание. Для того, чтобы ваше приложение, ваша инфраструктура была Cloudnative, имеет смысл уже начать наконец-то адаптировать подход, который называется Infrastructure as a Code.То есть это о том, что ваше приложение, точнее вашу инфраструктуру нужно точно так же, как и код вашего приложения, вашей бизнес логики описывать в виде кода. И работать с ним как с кодом, то есть его тестировать, раскатывать, хранить в git, CI\CD к нему применять.


И это прям то, что позволяет вам, во-первых, всегда иметь контроль над вашей инфраструктурой, всегда понимать в каком она состоянии. Во-вторых, избегать ручных операций, которые вызывают ошибки. В-третьих, избегать просто того, что называется текучкой, когда вам постоянно нужно выполнять одни и те же ручные операции. В-четвертых, это позволяет гораздо быстрее восстанавливаться в случае сбоя. Вот в России каждый раз, когда я об этом говорю, находятся обязательно огромное количество человек, которые говорят: «Ага, понятно, да у вас подходы, короче, ничего чинить не надо». Но это правда. Если у вас что-то сломалось в инфраструктуре, то с точки зрения Cloudnative подхода и с точки зрения Infrastructure as a Code, чем это починить, пойти на сервер, разобраться, что сломалось и починить, проще сервер удалить и создать заново. И у меня все это восстановится.


Более подробно все эти вопросы рассматриваются на видеокурсах по Kubernetes: Джуниор, Базовый, Мега. По ссылке вы можете ознакомиться с программой и условиями. Удобно то, что можете освоить Kubernetes, занимаясь из дома или с работы по 1-2 часа в день.