Основа успешного развертывания и сосуществования приложений в общем облачном окружении зависит от определения и объявления требований приложений к ресурсам и зависимостям времени выполнения. Паттерн Предсказуемые требования определяет, как должны объявляться требования приложений, будь то жесткие зависимости времени выполнения или требования к ресурсам. Объявление требований крайне важно для Kubernetes — это позволит фреймворку подобрать для вашего приложения правильное место в кластере.
Kubernetes может управлять приложениями, написанными на разных языках программирования, если эти приложения можно запускать в контейнере. Однако разные языки имеют разные требования к ресурсам. Как правило, программы, написанные на компилируемых языках, работают быстрее и часто требуют меньше памяти по сравнению с динамически компилируемыми программами или программами, выполняющимися под управлением интерпретатора. Учитывая, что многие современные языки программирования из одной и той же категории имеют схожие требования к ресурсам, с точки зрения потребления ресурсов более важными аспектами являются предметная область, бизнес-логика приложения и фактические детали реализации.
Трудно предсказать количество ресурсов, которое может понадобиться контейнеру для оптимального функционирования, и именно разработчик знает ожидаемый объем ресурсов, необходимый для реализации службы (выявленный в ходе тестирования). Некоторые службы имеют постоянный профиль использования процессора и памяти, а некоторые — переменчивый. Некоторые службы нуждаются в долговременном хранилище для хранения данных; некоторые устаревшие службы требуют доступа к фиксированным портам в хост-системе для корректной работы. Описание всех этих характеристик приложений и передача их управляющей платформе – фундаментальное условие для нормального функционирования облачных приложений.
Помимо требований к ресурсам, среда времени выполнения приложений также зависит от возможностей платформы, таких как хранение данных или конфигурация приложения.
Знать требования контейнера к окружению времени выполнения важно по двум основным причинам. Во-первых, зная все зависимости времени выполнения и потребности в ресурсах, Kubernetes сможет принимать разумные решения о том, где в кластере разместить контейнер, чтобы максимально эффективно использовать оборудование. В окружении с общими ресурсами, используемыми большим числом процессов с разным приоритетом, знание требований каждого процесса является залогом их успешного сосуществования. Однако эффективное размещение — это только одна сторона медали.
Вторая причина важности профилей ресурсов контейнеров — это планирование вычислительных мощностей. Исходя из потребности в каждой конкретной службе и общего количества служб, можно оценить необходимые вычислительные мощности для разных окружений и определить экономически наиболее эффективные профили хостов для удовлетворения потребностей всего кластера. Профили ресурсов служб и планирование вычислительной мощности вместе являются залогом долгого и успешного управления кластером.
Прежде чем углубиться в профили ресурсов, посмотрим, как объявлять зависимости времени выполнения.
Одной из наиболее типичных зависимостей времени выполнения является наличие хранилища файлов для сохранения состояния приложения. Контейнерные файловые системы эфемерны и исчезают после остановки контейнера. Для долговременного хранения файлов в подах (группах контейнеров) Kubernetes предлагает использовать тома.
Самый простой тип томов — emptyDir, который существует, пока существует использующий его под (Pod), то есть группа контейнеров, а после остановки пода его содержимое теряется. Том должен основываться на каком-то другом механизме хранения, чтобы данные в нем сохранялись после перезапуска пода. Если приложение нуждается в сохранении файлов в таком долговременном хранилище, нужно явно объявить эту зависимость в определении контейнера, как показано в листинге 2.1.
Листинг 2.1. Зависимость от PersistentVolume
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
volumeMounts:
- mountPath: "/logs"
name: log-volume
volumes:
- name: log-volume
persistentVolumeClaim:
claimName: random-generator-log
Зависимость от наличия и привязки PVC
Планировщик проверит тип тома, который влияет на выбор места для размещения пода. Если поду нужен том, которого нет ни на одном узле кластера, тогда он вообще не будет планироваться для выполнения. Тома — это пример зависимости времени выполнения, которая влияет на выбор инфраструктуры для запуска пода и определения возможности запланировать его.
Аналогичная зависимость возникает, когда вы требуете от Kubernetes предоставить контейнеру определенный порт в хост-системе через hostPort. Объявление hostPort создает еще одну зависимость времени выполнения и ограничивает круг хостов, на которые может планироваться под. hostPort резервирует порт на каждом узле в кластере, из-за чего на каждом узле может быть запущено не более одного экземпляра пода. Из-за конфликтов портов возможности масштабирования таких подов ограничиваются количеством узлов в кластере Kubernetes.
Другой тип зависимости — конфигурации. Почти каждому приложению требуется некоторая информация о конфигурации, и для этой цели рекомендуется использовать карты конфигураций (ConfigMap), предлагаемые фреймворком Kubernetes. Ваши службы должны определить стратегию использования конфигураций — либо через переменные среды, либо через файловую систему. В любом случае для вашего контейнера появляется зависимость времени выполнения от именованных карт конфигураций. Если будут созданы не все требуемые карты, контейнеры будут планироваться для выполнения на узле, но не будут запускаться. Карты конфигураций (ConfigMap) и секреты (Secret) более подробно описываются в главе 19 «Конфигурация в ресурсах», а в листинге 2.2 показано, как эти ресурсы используются в качестве зависимостей времени выполнения.
Листинг 2.2. Зависимость от ConfigMap
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
env:
- name: PATTERN
valueFrom:
configMapKeyRef:
name: random-generator-config
key: pattern
Зависимость от наличия ConfigMap.
Похожими на карты конфигураций являются секреты (Secret), предлагающие более безопасный способ передачи в контейнер специализированных конфигураций. Секреты используются точно так же, как карты конфигураций, и вводят такую же зависимость контейнера от пространства имен.
Создание объектов ConfigMap и Secret — это простые административные задачи, но хранилища и порты должны предоставляться узлами кластера. Некоторые из этих зависимостей сужают круг узлов, на которые может планироваться под (если вообще может), а другие могут препятствовать запуску пода. При разработке контейнерных приложений с такими зависимостями всегда учитывайте ограничения времени выполнения, которые они создадут позже.
Объявить зависимость контейнера от карт конфигураций, секретов и томов несложно. Но чтобы выяснить требования контейнера к ресурсам, необходимо поразмышлять и поэкспериментировать. Вычислительные ресурсы в контексте Kubernetes — это все, что можно запросить, получить и использовать из контейнера. Ресурсы делятся на две группы: сжимаемые (могут регулироваться, например процессорное время или пропускная способность сети) и несжимаемые (нерегулируемые, например объем памяти).
Важно различать сжимаемые и несжимаемые ресурсы. Если контейнеры потребляют слишком много сжимаемых ресурсов, таких как процессор, их аппетиты урезаются, но если они используют слишком много несжимаемых ресурсов (например, памяти), они останавливаются (потому что нет другого способа попросить приложение освободить выделенную память).
Исходя из характера приложения и особенностей его реализации, нужно указать минимально необходимые объемы ресурсов (так называемые запросы) и максимально возможные — до которых потребление может вырасти (лимиты). Определение контейнера может задавать необходимую долю процессорного времени и объем памяти в форме запроса и лимита. В общих чертах идея запросов/лимитов напоминает мягкие/жесткие лимиты. Например, аналогичным образом мы определяем размер кучи для приложения Java с помощью параметров командной строки -Xms и -Xmx.
При размещении подов на узлах планировщик ориентируется на величину запросов (а не лимитов). Планируя каждый конкретный под, планировщик рассматривает только узлы, имеющие достаточный объем ресурсов для размещения этого пода и всех его контейнеров, складывая запрашиваемые объемы ресурсов. В этом смысле поле запросов requests в определении контейнера влияет на возможность планирования пода. В листинге 2.3 показано, как такие ограничения определяются для подов.
Листинг 2.3. Ограничения ресурсов
apiVersion: v1
kind: Pod
metadata:
name: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
resources:
requests:
cpu: 100m
memory: 100Mi
limits:
cpu: 200m
memory: 200Mi
Минимальные запросы процессорного времени и памяти.
Верхние пределы, до которых может вырасти потребление приложения.
В зависимости от того, что вы определили — запросы, лимиты или и то и другое, — платформа предлагает разные уровни качества обслуживания (Quality of Service, QoS).
Под, не определяющий запросы и лимиты для своих контейнеров, считается самым низкоприоритетным и уничтожается первым, когда на узле, где он располагается, заканчиваются несжимаемые ресурсы.
Под, определяющий неравные значения в запросах и лимитах (когда лимиты превышают запросы), гарантированно получает минимальный объем ресурсов, но при наличии свободных ресурсов может получить их больше, вплоть до объявленного предела. Когда узел испытывает нехватку ресурсов, эти поды могут останавливаться, если не останется подов, обслуживаемых без гарантий качества.
Под, определяющий равные значения в запросах и лимитах, получает наивысший приоритет и гарантированно будут останавливаться, только если на узле не останется подов, обслуживаемых без гарантий или с переменным качеством.
Как видите, характеристики потребления ресурсов контейнерами, которые вы объявляете или опускаете, напрямую влияют на качество обслуживания и определяют относительную важность пода в случае истощения ресурса. Определяйте требования подов к ресурсам с учетом этого.
Мы выяснили, как объявление потребностей в ресурсах влияет на качество обслуживания и на очередность, в которой Kubelet останавливает контейнеры в случае нехватки ресурсов. На момент написания этих строк уже велись работы по реализации еще одной возможности, имеющей отношение к качеству обслуживания и позволяющей определить приоритет пода и возможность его вытеснения. Настройка приоритета дает возможность указать важность пода относительно других подов и повлиять на порядок, в котором они будут планироваться. Давайте посмотрим на эти настройки, показанные в листинге 2.4.
Листинг 2.4. Приоритет пода
apiVersion: scheduling.k8s.io/v1beta1
kind: PriorityClass
metadata:
name: high-priority
value: 1000
globalDefault: false
description: Это класс подов с очень высоким приоритетом
---
apiVersion: v1
kind: Pod
metadata:
name: random-generator
labels:
env: random-generator
spec:
containers:
- image: k8spatterns/random-generator:1.0
name: random-generator
priorityClassName: high-priority
Имя объекта, определяющего класс приоритета.
Значение приоритета в объекте.
Класс приоритета, присвоенный этому поду, как определено в ресурсе PriorityClass.
Мы создали PriorityClass, объект вне пространства имен, чтобы определить целочисленный приоритет. Присвоили объекту PriorityClass имя high-priority и уровень приоритета 1000. Имея такое определение, мы можем назначить этот приоритет подам, указав имя объекта в параметре: priorityClassName: high-priority. PriorityClass — это механизм объявления важности подов по отношению друг к другу, где более высокое значение соответствует большей важности.
Когда возможность определения приоритетов будет полностью реализована, с ее помощью можно будет влиять на порядок размещения планировщиком подов на узлах. Контроллер учета приоритета сначала установит значения приоритетов в новых подах, используя поле priorityClassName. Когда появится несколько подов, ожидающих размещения, планировщик отсортирует очередь по убыванию приоритетов, благодаря чему более важные ожидающие поды будут выбираться из очереди планирования раньше менее важных и, в отсутствие ограничений, препятствующих планированию, размещаться на узлах.
Здесь есть один важный момент. В отсутствие узлов с достаточным объемом ресурсов для размещения пода планировщик может выгрузить (удалить) поды с более низким приоритетом, чтобы освободить ресурсы и разместить модули с более высоким приоритетом. В результате под с более высоким приоритетом может быть запланирован раньше, чем под с более низким приоритетом, если выполнятся все другие требования планирования. Этот алгоритм позволяет администраторам кластера определять важность подов и гарантировать их первоочередное размещение, давая планировщику возможность удалять поды с более низким приоритетом, чтобы освободить место для подов с более высоким приоритетом. Если под не может быть запланирован, планировщик продолжит размещать другие поды, с более низким приоритетом.
Качество обслуживания (обсуждалось выше) и приоритет пода — это две независимые возможности. Управление качеством обслуживания используется главным образом агентами Kubelet для сохранения стабильности узла, когда доступные вычислительные ресурсы невелики. Выбирая, какой под остановить, Kubelet сначала оценивает его уровень качества обслуживания, а затем PriorityClass. С другой стороны, логика вытеснения в планировщике полностью игнорирует качество обслуживания при выборе подов для остановки. Планировщик пытается выбирать поды с наименьшим приоритетом, чтобы удовлетворить потребности подов с более высоким приоритетом, ожидающих размещения.
Объявление приоритета пода может оказать нежелательное влияние на другие поды, которые будут вытесняться. Например, несмотря на соблюдение политик правильного завершения пода, выполнение требований бюджета неработоспособности пода PodDisruptionBudget, о котором рассказывается в главе 10 «Служба-одиночка», не гарантируется, что может привести к нарушению работоспособности кластерного приложения с низким приоритетом, которое опирается на коллективную работу подов.
Другая проблема — пользователь, который по незнанию или по злому умыслу создает поды с наибольшим возможным приоритетом, мешающие выполнению всех остальных подов. Для предотвращения такой ситуации в механизм квотирования ресурсов ResourceQuota была добавлена поддержка PriorityClass и теперь большие значения приоритета зарезервированы для критических системных подов, которые обычно не должны вытесняться или останавливаться.
В заключение следует отметить, что приоритеты следует использовать с большой осторожностью, поскольку значения приоритетов, указанные пользователем и используемые планировщиком и агентами Kubelet, чтобы выяснить, какие поды разместить или остановить, часто выбираются экспериментально. Любые изменения могут повлиять на многие поды и помешать платформе соблюсти соглашение об уровне обслуживания.
Kubernetes — это платформа самообслуживания, которая позволяет разработчикам запускать приложения в предопределенных изолированных окружениях. Однако для нормальной работы многопользовательской платформе необходимы также определенные границы и средства управления, чтобы отдельные пользователи не смогли захватить все ресурсы платформы. Одним из таких инструментов является квотирование ресурсов ResourceQuota. С его помощью можно ограничить совокупное потребление ресурсов в пространстве имен. С помощью квот ResourceQuota администраторы кластера могут ограничить общую сумму используемых вычислительных ресурсов (процессор, память) и хранилища. С их помощью также можно ограничить общее количество объектов (карт конфигураций, секретов, подов и служб), создаваемых в пространстве имен.
Еще одним полезным инструментом в этой области является диапазон ограничений LimitRange, который позволяет установить границы использования ресурсов каждого типа. Помимо минимальных и максимальных значений и значений по умолчанию, этот механизм также позволяет контролировать отношение между запросами и лимитами, которое также известно как уровень перерасхода. В табл. 2.1 показан пример, как можно выбирать возможные значения для запросов и лимитов.
Таблица 2.1. Границы запросов и лимитов
Тип | Ресурс | Мин | Макс | Лимит по умолчанию | Запрос по умолчанию | Отношение лимит/запрос |
Контейнер | Процессор | 500 мс | 2 | 500 мс | 250 мс | 4 |
Контейнер | Память | 250 Мбайт | 2 Гбайт | 500 Мбайт | 250 Мбайт | 4 |
Диапазоны ограничений LimitRange удобно использовать для управления профилями ресурсов контейнеров, чтобы не допустить появления контейнеров, которым требуется больше ресурсов, чем может дать узел кластера. С его помощью также можно помешать пользователям создавать контейнеры, потребляющие большое количество ресурсов и делающие узлы недоступными для других контейнеров. Учитывая, что запросы (но не лимиты) являются основной характеристикой контейнера, которую планировщик использует для размещения, отношение лимит/запрос LimitRequestRatio позволяет контролировать разницу между запросами и лимитами. Большой разрыв между запросами и лимитами увеличивает вероятность чрезмерной нагрузки на узел и может снизить производительность приложения, когда многим контейнерам одновременно потребуется больше ресурсов, чем запрашивалось первоначально.
Учитывая, что в разных окружениях контейнеры могут иметь разные профили ресурсов и разное количество экземпляров, очевидно, что планирование вычислительной мощности для многоцелевой среды — сложная задача. Например, для оптимального использования оборудования в непромышленном кластере можно использовать в основном контейнеры с уровнями качества обслуживания «без гарантий» и «с переменным качеством». В таком динамическом окружении одновременно может запускаться и останавливаться большое число контейнеров, и даже если какой-то контейнер будет остановлен платформой из-за нехватки ресурсов, это не будет иметь серьезных последствий. В промышленном кластере, где требуется высокая стабильность и предсказуемость, могут преобладать контейнеры с гарантированным уровнем качества обслуживания и в меньшем числе — с переменным качеством обслуживания. Принудительная остановка контейнера в таком окружении почти наверняка является признаком необходимости увеличить вычислительную мощность кластера.
В табл. 2.2 перечислено несколько служб с их требованиями к процессорному времени и памяти.
Таблица 2.2. Пример планирования вычислительной мощности
Под | Запрос процессора | Лимит процессора | Запрос памяти | Лимит памяти | Экземпляров |
A | 500 мс | 500 мс | 500 Мбайт | 500 Мбайт | 4 |
B | 250 мс | 500 мс | 250 Мбайт | 1000 Мбайт | 2 |
С | 500 мс | 1000 мс | 1000 Мбайт | 2000 Мбайт | 2 |
D | 500 мс | 500 мс | 500 Мбайт | 500 Мбайт | 1 |
Всего | 4000 мс | 5500 мс | 5000 Мбайт | 8500 Мбайт | 9 |
В реальной жизни такая платформа, как Kubernetes, обычно используется потому, что существует множество служб, часть из которых предполагается вывести из эксплуатации, а часть все еще находится на стадии проектирования и разработки. Но даже в таком постоянно меняющемся окружении можно рассчитать общий объем ресурсов, необходимых для всех служб.
Не забывайте также, что в разных окружениях может действовать разное число контейнеров, а еще нужно оставить место для автоматического масштабирования, заданий сборки, инфраструктурных контейнеров и многого другого. Опираясь на эту информацию и сведения о поставщике инфраструктуры, вы сможете выбрать наиболее экономичные вычислительные экземпляры, обладающие необходимыми ресурсами.
Контейнеры удобно использовать не только для изоляции процессов, но и как формат упаковки. С соответствующими профилями ресурсов они также помогают успешно планировать вычислительные мощности. Проведите несколько начальных экспериментов, чтобы выяснить потребности в ресурсах для каждого контейнера, и используйте эту информацию как основу для будущего планирования и прогнозирования.
Однако, что более важно, профили ресурсов дают приложениям возможность сообщить платформе Kubernetes информацию, необходимую для планирования и управления решениями. Если приложение не определяет никаких запросов или лимитов, тогда Kubernetes будет интерпретировать его контейнеры как черные ящики, которые можно останавливать при заполнении кластера. Поэтому для каждого приложения важно продумать и определить требования к ресурсам.
Теперь, узнав, как определять размеры приложений, перейдем к главе 3 «Декларативное развертывание», где познакомимся с некоторыми стратегиями установки и обновления приложений в Kubernetes.
• Пример предсказуемых требований (http://bit.ly/2CrT8FJ).
• Порядок использования карт конфигураций ConfigMap (http://kubernetes.io/docs/user-guide/configmap/).
• Квоты ресурсов (http://kubernetes.io/docs/admin/resourcequota/).
• Статья «Kubernetes Best Practices: Resource Requests and Limits» (http://bit.ly/2ueNUc0).
• Настройка пределов процессорного времени и памяти для пода (http://kubernetes.io/docs/admin/limitrange/).
• Дополнительные возможности управления потреблением ресурсами (http://bit.ly/2TKEYKz).
• О приоритетах подов и возможности их вытеснения (http://bit.ly/2OdBcU6).
• Об уровнях качества обслуживания в Kubernetes (http://bit.ly/2HGimUq).
О защищенности секретов (Secret) мы подробно поговорим в главе 19 «Конфигурация в ресурсах».
Агент кластера Kubernetes, который выполняется на каждом узле. — Примеч. пер.