Обзор Kubernetes Patterns

Kubernetes давно стал стандартом де-факто как среды для эксплуатации cloud native приложений. А именно такие приложения модно делать в настоящее время. Но для того, чтобы понимать почему он так популярен, надо знать подходы, которые он предлагает для решения стандартных проблем разработки. Например, эти проблемы упоминались еще в манифесте 12 factor app от Heroku.

В свое время мне пришлось знакомиться со всем этим по официальной документации проекта на kubernetes.io. Я справился, прошел сертификацию и получил статус CKA (Certified Kubernetes Administrator). Но теперь есть лучший способ, чтобы познакомиться с функционалом этого оркестратора — а именно прочитав книгу Kubernetes Patterns… Или хотя бы это краткое саммари.

По структуре книга напоминает классическую книгу “Design Patterns” банды четырех, которая содержала creational, structural и behavioral паттерны. Но у нас здесь 5 категорий паттернов:

  • Foundation patterns — базовые блоки k8s, на основе которых строится все остальное
  • Behavioral patterns — поведенческие паттерны, которые позволяют добиться желаемого поведения, например, запуска периодических job или приложения синглтона
  • Structural patterns — структурные паттерны, которые показывают как можно расширить функционал основного контейнера добавив другие контейнеры в pod
  • Configuration patterns — конфигурационные паттерны, которые позволяют эффективнее управлять конфигурацией ваших приложений
  • Advanced patterns — продвинутые паттерны, которые раскрывают темы того, как работает сам k8s и как его можно расширять

Общий список паттернов представлен на рисунке ниже.

Но начнем бы с базы, а именно с

Foundational patterns

Все начинается с паттерна Predictable Demands, который говорит о том, что ваше приложение должно декларировать свои потребности и придерживаться их. Это может относиться как runtime зависимостям (диск, configMap, …), так и к запрошенным ресурсам (cpu, memory), что позволяет как планировать мощности кластера в целом, так и помогает с шедулингом pod’ов на конкретные ноды в кластере.

Дальше идет декларативное развертывание (Declarative Deployment), которое позволяет нам описать как деплоить наше приложение и оркестратор сам позаботиться обо всем:) Среди вариантов развертываний есть: rolling deployment, fixed deployment, blue green, canary release. Здесь есть крутые gif, которые демонстрируют эти стратегии в динамике.

Для того, чтобы k8s мог поддерживать работоспособность приложения ему требуется некоторая помощь от разработчиков, которые должны реализовать health probes. Эти пробы включают Liveness, Readiness and Startup Probes, где каждая имеет свое назначение:

  • liveness — проба, которая позволяет понять живо еще приложение или его надо развернуть заново
  • readiness — проба, которая говорит о том, можно ли роутить запросы в приложение или оно не готово, например, в приложении возникли проблемы и мы не хот
  • startup — проба для стартующих медленно приложений, которые на старте прогреваются (привет java)

Подробнее про настройку этих проб можно почитать в документации.

Следующий паттерн — это Managed Lifecycle, который говорит о том, что хорошие cloud native приложения должны определенным образом организовывать свой жизненный цикл. Фактически, за управление этим жизненным циклом отвечает платформа оркестрации, которая стартует приложения, отправляет при необходимости SIGTERM, SIGKILL и дергает хуки типа PreStop, PostStart и подобные. Важно сделать так, чтобы приложение умело работать с указанными выше событиями, а также использовало при необходимости нужные хуки.

И финальный паттерн в этом разделе Automated Placement, который относится к ключевому функционалу k8s, который планирует размещение подов на конкретные ноды кластера с учетом

  • запросов конкретных контейнеров
  • доступных нод и ресурсов в рамках них (cpu, memory, volumes, …)
  • политик размещения (placement policies)
  • правил affinity и antiaffinity, а также taints и tolerations

В общем, это ключевой функционал, который работает под капотом оркестратора, чтобы обеспечить наш декларативно оформленный deployment.

Behavioral patterns

Паттерны из этой категории посвящены взаимодействию между подами и платформой оркестрации. Многие из этих паттернов напоминают старых знакомых из мира Linux, которые просто переехали в мир распределенных систем.

Начинается все с паттерна Batch Job, который описывает изолированную часть работы, которая работает до выполнения. Следующий паттерн Periodic Job напоминает работу cron job и заключается в выполнении какого-то объема работы, привязанное к определенному моменту во времени. Кстати, точных гарантий, что работа начнется ровно в это время нет.

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

Следующий паттерн singleton service, который позволяет гарантировать запуск только одной копии сервиса, но все еще в режиме высокой доступности (high availability). Тут интересно, что решение в лоб, через ReplicaSet с фактором репликации 1 не проходит, так как

Kubernetes primitives such as ReplicaSet, favor availability over consistency — a deliberate decision for achieving highly available and scalable distributed systems. That means a ReplicaSet applies “at least” rather than “at most” semantics for its replicas

Но для реализации поведения singleton service мы должны придерживаться подхода, что consistency важнее availability, и такой примитив в Kubernetes есть и это StatefulSet. Но если требуется действительно надежное поведение, то придется реализовывать in-app locking на уровне приложения.

Для реализации stateful приложений внутри k8s используется паттерн Stateful Service, который обеспечивает такие фичи как постоянные identity, сеть, диск, ординальность. Все это реализуется при помощи использования описания сервиса в виде StatefulSet. Помимо указанных выше есть и другие фичи, такие как partitioned updates, parallel deployments и at-most-one guarantee. Ну и напоследок цитата авторов книги насчет StatefulSet

It provides a good set of building blocks for managing stateful applications in an automated fashion, making them first-class citizens in the cloud-native world.

Следующий паттерн относится к тому, как клиенты получают доступ к информации о том, где развернуты инстансы, предоставляющие сервисы приложений. Суть в том, что оркестратор в любой момент может сделать rescheduling конкретных подов и они переедут на другую ноду, но трафик должен все так же успешно доходить до приложений. За это отвечает Service Discovery. Механизмы отличаются для service discovery внутри кластера и для внешнего трафика. Среди разных механизмов есть

  • внутренние — ClusterIP, Manual IP, Manual FQDN, Headless Service
  • внешние — NodePort, LoadBalancer, Ingress

Ну и последним паттерном из этой категории является Self Awareness, который позволяет приложению воспользоваться интроспекцией и получить метаданные о себе для того, чтобы вставить их в приложение. Данные можно получить через Env Variable и посредством Downward API, например, мы можем узнать про имя ноды, ip ноды, имя пода, ip пода, данные по request и limit пода и т.д. Эти данные можно использовать для того, чтобы подтюнить приложение или для расширения информации по логам и метрикам, которые отгружаются приложением.

Structural patterns

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

Ну и все начинается с паттерна Init Container, который добавляет отдельный жизненный цикл для задач инициализации основного приложения, состоящего из группы контейнеров. Суть в том, что подготовительная работа выполняется init контейнером, после успеха которой стартует основные контейнеры.

Продолжается все с того, как использовать паттерн Sidecar для расширения функционала основного приложения без его изменения. По факту, это реализация подхода композиции из объектно-ориентированного мира.

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

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

Configuration patterns

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

Простейший подход — это EnvVar Configuration, в которой для конфигурирования приложения используются переменные окружения (environment variables). В Kubernetes их можно подтянуть из файла, из ConfigMap или из Secret. Этот подход хорош для небольших приложений и плохо масштабируется на большое количество параметров конфигурации.

Если параметров много, то удобнее использовать Configuration Resource для хранения и передачу параметров в приложения. Такими ресурсами являются ConfigMaps и Secrets. Эти ресурсы можно подключать в качестве environment variables или подключенных volumes.

Еще один вариант Immutable Configuration — это хранение конфигурации в отдельном контейнере, который подключается внутрь пода в качестве sidecar к нашему основному приложению.

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

Advanced patterns

В последней части рассматриваются более сложные паттерны, которые не попали в предыдущие категории. Первый паттерн Controller является базовым блоком Kubernetes, который отслеживает состояние и поддерживает ресурсы в нужном состоянии, например, количество реплик приложения для ReplicaSet.

Второй паттерн Operator объединяет концепцию Controller с кастомным определением ресурсов (CRD, Custom Resource Definitions). По-факту, этот паттерн позволяет реализовать эксплуатацию кастомного приложения в автоматической форме, напоминающей подход самого Kubernetes. Подробнее можно прочитать в документации Operator Framework.

Третий паттерн Elastic Scale очень интересен, так как именно он является killer фичей для заезжающих в kubernetes приложений. По-факту, мы можем декларативно описать правила масштабирования среди которых

  • Horizontal Pod Autoscaling — увеличение количества инстансов приложения путем увеличения количества подов
  • Vertical Pod Autoscaling — увеличение ресурсов, выделенных для конкретных подов
  • Cluster Autoscaling — увеличение размеров кластера, а именно доступных нод

И последний паттерн Image Builder переносит аспект билда образов приложений прямо внутрь кластера.

Итого

Книга вышла крайне интересной и полезной для архитекторов. Современные приложения обычно реализуется в cloud native стиле, где постоянно приходится использовать паттерны, приведенные в данной книге. Правда, сама книга вышла в 2019 году, что уже достаточно давно для мира cloud native, поэтому помимо книги читайте официальную документацию с kubernetes.io

Director of digital ecosystem development department at Tinkoff. Bachelor at applied math, Master at system analysis, Postgraduate studies at economics.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store