Kubernetes, сокращённо k8s, k8s - это система управления контейнерами, о которой знают все. Тем не менее, не все имеют точное представление о том, что именно это такое и как работает. А иногда и не очень представляют как вообще работать с системой со стороны конечного пользователя. В этой заметке я постараюсь описать то, как воспринимать k8s, и немного вокруг.

Что такое Kubernetes?#

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

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

Неизбежно меняется набор инструментов для деплоя, мониторинга, управления приложениями и их жизненным циклом. При наличии хотя бы двух команд - подходы к организации этой кодовой части будут различными. Обвес для деплоя тоже будет различным - кто-то имеет сверх простое приложение, и считает, что его можно отправить на сервер через rsync(к сожалению, в современном мире такое еще есть), кто-то стащил с прошлого проекта Ansible Playbook и разворачивает свои супервизоры для питона, а те, кто съел собаку на Cloud Providers - используют Terraform. Эти различия неизбежны, но неужели ничего нельзя поделать? Мы только начали проект, нас всего десять человек. Как же работают большие компании типа гугла? Давайте представим, что нашей задачей является причесывание этого маленького зоопарка. Но перед началом - немного истории.

Откуда вобще взялся k8s?#

Для наглядности стоит начать издалека.

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

Далее - новый виток с ростом интереса пользователей и как следствие развитием пропускной способности сети, языками программирования, фреймворками и СУБД. Появляются провайдеры хостинга, PHP для решения задач, подобных тем, что были ранее, и что немаловажно - сервисы, которые начинают зарабатывать деньги. Зарождаются соц сети, завлекающие большее количество пользователей в интернет, развивается ИТ индустрия.

Большее количество пользователей - больший рост, больше однотипных задач. В популярных на то время ЯП появляются сообщества, которые создают ПО для решения повседневных задач - CMS, фреймворки. Так как вычислительные мощности компьютеров растут - появляется возможность выделения одинаковых частей приложений в отдельные приложения. Ярким примером может служить Nginx (или Apache httpd). Теперь нет проблемы обслужить большое кол-во соединений или медленных клиентов - все это, и многое другое делает за разработчика Nginx. Но за это необходимо заплатить тем, что нужно знать процесс установки, конфигурирования и анализа проблем Nginx.

Дальше - больше. Подключаются СУБД, появляются различные хардварные новшества - гибридные ПЗУ, SSD, развиваются процессоры, появляются новые архитектуры. Все это влияет на особенности инсталляции ПО и конфигурацию этого самого ПО.

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

Сообщество создает новый ряд инструментов с гордым названием Configuration Management System. Они обещают, что возьмут на себя все особенности конфигурации каждого приложения, предоставив наружу единый интерфейс для управления зоопарком ПО. Ansible, SaltStack, Chef и прочие выглядят как спасение, но и они не могут справиться с особенностями окружения, в которых они выполняют свою работу. Различия между дистрибутивами критически сказываются на самих Ansible Playbook`ах, что превращает их в еще больший ад, чем был до этого.

Пока прикладные разработчики ковырялись с пакетами и менеджерами конфигурации - бородатые дядьки пилили lxc - систему легковестной виртуализации на уровне хоста. Поверх lxc сообщество нарисовало Docker, который создал много хайпа, но смог удержаться и зарекомендовать себя как обязательная система для каждой домохозяйки.

Если бы развитие ИТ остановилось, то Ansible + Docker захватили бы мир, так как виртуализация сгладила бы различия между ОС, а Ansible плейбуки упростились бы до набора шаблонов конфигов.

Однако гиганты мира ИТ двигали собщество далее и буквально разгоняли разработку во всем мире, привнося больше практик и техник, которые драйвили сообщество. Самым устоявшимся из них был паттерн микросервисов, решающих проблемы организации кода и привносящий боль их конфигурации и управления.

В какой-то момент все стало восприниматься как микросервис (речь не про размер, а про изолированость сервисов друг от друга), и их стало еще больше. Один сервис собирает метрики (Prometheus), другой - отображает эти метрики (Grafana), третий - создает алерты на их основе (Alert Manager), а для изменения паттерна Prometheus с пуш на пулл - нужен еще один прокси сервис. Для автоматической регистрации инстансов в Prometheus нужен еще один компонент, и это только базовая система мониторинга.

Вслед за спросом на микросервисы и инструменты управления - Google представил Kubernetes - систему управления кластерами, которая якобы работала внутри самого гугла для окрестрации контейнерами (скорее виртуальными машинами, будучи в гугле). Так и появился Kubernetes, но что бы стать таким, каким мы его знаем сейчас ему пришлось пережить три очень серьезных рефакторинга - читай переписывания.

И как изменилась жизнь?#

k8s привнес новый слой абстракции и то, чего нехватало при работе с такими инструментами как Ansible - полный контроль жизненного цикла ПО. Доставка ПО, развертывание, изоляция, единая среда выполнения, контроль над ресурсами, проверки доступности сервисов, масштабирование, отказоутойчивость, обновление и останов сервисов. Теперь у разработчиков есть универсальная “операционная система” для оперирования сервисами.

Kubernetes так же решил еще одну немаловажную проблему - сглаживание углов между разработчиками и Ops`ами, которые поддерживают ПО в продакшн. Теперь разработчик может писать меньше документации по особенностям своего ПО. Как минимум - не описывать процесс установки, нюансы зависимостей и порядок запуска сервисов, а так же не расписывать то, как мониторить сервис на предмет его доступности и от каких компонент зависит очередной сервис. Все это видно из формализованного описания объектов (сущностей, которыми оперирует kubernetes), необходимых для запуска сервиса. Любой человек, который знаком с kubernetes, знает как установить и мониторить сервис и от чего он зависит - необходимо просто почитать описание создаваемых объектов. Механизм, концептуально похожий на Dockerfile, где любой, кто знает синтаксис и логику работы Docker может сходу понять, из чего состоит данный образ.

Помимо того, что разработчики и opsы могут тратить меньше времени на разногласия и банальное недопонимание, они так же могут друг другу давать советы и создавать MR для улучшения качества продукта. Имея даже небольшой опыт, каждая из сторон может безболезненно (зачастую) вносить изменения в эту точку соприкосновения, улучшая продукт посредством формализованного языка.

Еще одна возможность, которая распространяется на разработку и опсов - возможность заходить на чужую территорию. Разработчик может больше вникать в администрирование (или наоборот меньше, перекладывая всю работу на опсов), а опс может вникать в разработку и вносить изменения в автоматизацию процессов деплоя и сопровождения ПО путем расширения k8s (или переложить это на разработчика).

Разработка и опсы могут рассуждать “квадратами”, рисуя простые схемы, без вникания в детали реализации. Нет страха установки проекта с нуля, переноса на более мощные сервера, нет страха забыть что-то важно, не задокументированное. Это достигается путем того, что любое малейшее изменение в кластере k8s достигается только за счет того, что в него отправляется полное описание нового состояния объекта. Даже простейшее переименование или добавление кастомного тега на какой-то объект - новый файл с полным описанием этого объекта и измененным тегом/названием. Подобная практика привносит то, что пропагандировали Ansible - подобные системы - Инфраструктура как код или Iac в полной мере. Сеть, политики безопасности, пользователи, роли, запуск, обновление, мониторинг и все прочее теперь полностью находятся в одной гетерогенной инфраструктуре, изменения в которую можно внести только посредством изменения кода(описания) желаемого объекта.

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

Разработчики теперь не думают о том, где находятся инстансы зависимых приложений(и свои инстансы), так же могут не думать о том, сколько их и в какой они конфигурации (однако, могут предоставлять рекомендации по требуемым мощностям все в том же формализованном представлении в конфигурации объектов). Опсы меньше заботятся о том, как расширить кластер. В любой момент можно купить доп сервер, подключить его к кластеру и k8s автоматически перенесет (с остановкой инстанса на одной ноде и запуском на другой) сервисы разработчиков. Дополнительных манипуляций производить не нужно (в большинстве случаев, конечно же). Точно то же самое касается и доп оборудования на сервере. Если добавляется новый диск в hotswap - он будет доступен в кластере k8s для использования сервисами, которые запрашивают дополнительное дисковое пространство.

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

Что насчет реальных проблем и задач?#

В промышленной разработке существует ряд проблем и задач, которые возникают постоянно. Нашлась бага в определенной версии приложения и нужно ее отыскать на реальной инсталляции? Создайте новое окружение (полностью изолированное от других окружений или наоборот, связанное с другими) и запустите там все инстансы тех версий, которые вам необходимы.

Нужно обойти часть абстракций конечной системы (например сложный сервис авторизации или другой механизм защиты/модификации данных) - пробросьте порт вашего сервиса или конкретного инстанса на локальную машину с port-forward. При помощи Telepresence можно совершить и обратное действие - заставить все сервисы в определенном окружении ходить не в сервис, запущенный в k8s а на вашу локальную машину, где вы ждете плохой запрос с дебагером.

Поменять одну строчку в конфиге любого сервиса? Можно, и для этого есть команда и даже целый инструмент от стороннего разработчика - k9s (не только конфиги меняет, но и позволяет следить на всем, что происходит в кластере).

На проде запущен ingress с ssl, на локальных окружениях как угодно - можно отключить ssl или включить с самоподписанным сертификатом. Или воспользоваться LetsEncrypt.

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

Попахивает шаблонизацией. Helm.#

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

Helm - инструмент для работы с объектами k8s как с некими приложениями. Он позволяет описать весь набор объектов k8s как набор шаблонов, в которых можно использовать переменные, переиспользовать (подключать) другие шаблоны и использовать базовые операторы (ветвление, циклы и ряд кастомных функций) для создания результирующего конфига объекта.

Набор файлов, необходимых для оформления любого приложения можно свести к трем группам: манифест приложения, шаблоны объектов приложения и переменные для шаблонов приложения.

Манифест описывает само ваше приложение. В этом файле содержится информация о названии приложения, его версии, описании, изображении и завимисостях от других приложений (из репозиториев Helm Charts или ваших собственных). В общем - базовая информация, для отбражения в условном “хранилище приложений helm”.

Шаблоны объектов - в базовом случае обычный конфиг объекта. Однако, при рендеринге этого файла, helm воспринимает его как шаблон и может подставлять в него значения переменных, переданных в контексте или исполнять простые конструкции типа циклов, условий, а так же делать все, что можно делать в шаблонах Go.

Переменные хранятся в отдельном файле и содержат структурированную (yaml) информацию о парах ключ/значение или о более сложных структурах, которые можно описать в yaml. Эти переменные передаются в рендер шаблонов вместе с самим шаблоном. На выходе получается готовый конфиг объекта kubernetes.

Все вместе это называется Helm Chart - директория с файлами, необходимая для рендеринга конечного набора конфигураций объектов kubernetes.

Типичная команда для установки конкретного helm chart выгдядит как команда с переданным местоположением (директорией) чарта и указанием на файл с переменными. Это как раз то, что нам нужно в случае установки приложения в различные окружения с различными параметрами. Единый шаблон конфигурации объектов, различные параметры для его рендеринга.

Больше деталей#

Для начала определимся, что сейчас мы рассмотрим только ту часть, которая отвечает за унификацию процесса деплоя наших приложений. Каждый сервис, если смотреть “издалека” представляет собой некий бинарь, который должен где-то запуститься и иметь доступ к некоторым другим сетевым ресурсам. Такими ресурсами являются традиционные СУБД, другие сервисы в кластере или за его пределами. Так же каждый сервис имеет ряд требований к окружению, в котором он работает. Например требования к объему оперативной памяти, процессору, видеокарте, дисковому пространству, пропускной способности сети, доступ к ресурсам интернет. Для работы приложения нужно иметь доступ к некоторым ресурсам, однако приложение может так же сообщать о том, что оно предоставляет наружу. Как и база данных, имеющая порт для подключения, так и ваш сервис возможно предоставляет доступ к себе на некотором порту с некоторым протоколом. А возможно, приложение запускается один раз ( прим. миграция) и, после выполнения работы завершается без перезапуска.

Для описания простейшего приложения в k8s существует отдельный формат описания объекта под названием Pod. Забегая наперед, k8s имеет описание на каждый объект, который вообще может работать в k8s. На инстанс приложения, на доступ к диску, на группу приложений, на load balancer, конфиги, ключи, секреты и другие типы объектов (даже описание на кастомное описание объекта - Custom Resource Definition).

В k8s все является объектом, а для их создания используются описания (спецификации - spec) конечного состояния этих самых объектов. Если вы хотите что-то “запустить” в k8s, то для начала нужно представить как это будет выглядеть на уровне простейшей схемы. Например для запуска простейшего приложения нужно описать объект типа Pod, с указанием ссылки на образ и названия этого Pod. Если приложение имеет конфигурацию - для нее необходимо создать отдельный объект Configmap и связать Configmap с Pod, соотв изменив спецификацию Pod`а. Если ваш сервис обслуживает некий tcp порт - необходимо так же расширить спецификацию Pod`а и описать это явно - порт и протокол. Ваше приложение способно горизонтально масштабироваться, а все его инстансы равнозначны в обработке трафика? Опишите объект Deployment, который будет клонировать столько Pod`ов, сколько укажете в параметре replicas. А для балансировки запросов между всеми Pod` ами - опишите объект Service, который объединит все Pod`ы и будет раскидывать запросы между инстансами. Пора опубликовать Service в интернет? Объект Ingress может обслуживать домены и ssl снаружи (запросы из внешней сети) и пробрасывать запросы в соотв Service. Для других задач существуют свои объекты. Ознакомиться с масштабом и разнообразием стандартных объектов, входящих в поставку k8s, можно здесь https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.19/

Что стоит за всеми объектами и их описанием? В первом приближении кластер k8s состоит из двух частей - хранилище объектов и, собственно набор компонент (операторов), которые могут создавать описанные объекты. В стандартной поставке k8s есть ряд предопределенных объектов и операторов, которые работают с каждым конкретным объектом. Помимо стандартного набора операторов в поставке k8s, в кастомных инсталляциях (google cloud, aws и других) существуют кастомные ресурсы или модификации стандартных ресурсов. Каждый из ресурсов обрабатывается отдельным оператором, а особенности архитектуры k8s позволяют создавать кастомные операторы, чем и пользуются провайдеры k8s в облаке.

Для хранения описания объектов и их состояния, k8s использует etcd как хранилище объектов. В это хранилище так же отправляются все изменения состояния или самого описания объектов. Операторы подписываются на изменения интересующих их объектов и выполняют соотв действия. В самом простом случае, оператор, ответственный за управление данным типом объекта может его создать, изменить или удалить. Однако, есть случаи, когда операторы слушают изменения не своих объектов, но используют полученную информацию для модификации своих объектов. Например, Service слушает изменения объектов типа Pod для получения состояния готовности Pod`а. Как только Pod становится доступным для обслуживания запросов, Service добавляет соотв маршрут в свой балансировщик.

Вы можете создавать свои операторы для автоматизации некоторых действий. Например, если у вас есть Prometheus, и много сервисов предоставляющих метрики, то ваш оператор может получать изменения всех объектов с определенными метками ( аннотациями/тегами) и добавлять в Prometheus те сервисы, которые имеют метку prometheus-metrics: enabled (к примеру). Так вам не нужно будет каждый раз добавлять/удалять руками новые инстансы приложений.

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