Владимир Портнов — DevOps engineer
Зачем?
С древнейших времён для запуска web-приложений использовалась простейшая схема: одиночный web-сервер (как правило, Apache) обрабатывал любые запросы и самостоятельно обращался ко всем необходимым внешним хранилищам данных, будь то DB или простые файлы на диске. Но это приводило к ряду неприятных проблем: надёжность была весьма низкой - если один сервер падал, то всё переставало быть доступно; web-сервер не предоставлял никакой изоляции для соседних приложений и минимальную для DB и других web-серверов, что приводило к рискам безопасности; перенести такое приложение на другие сервера было сложно; продублировать для обеспечения какой-либо устойчивости ещё сложнее. Появлялось множество различных решений этих проблем, и наиболее популярным на сегодняшний день стал переход к принципиально новому подходу к разработке приложений - Cloud Native - а самой популярной платформой для Cloud Native приложений стал Kubernetes.
Предыстория
VM, кластера виртуальных машин
Изоляция приложений в различные виртуальных машины
namespaces, cgroups
Механизмы изоляции процессов ядра Linux
LXC, Docker
Средства контейнеризации на основе Linux
Kubernetes
Кластерное решение по управлению контейнерами
VM, кластера виртуальных машин
Изначально для решения предыдущих проблем популярность набрали различные решения для виртуализации. И, действительно, VM отлично изолированы друг от друга, образа VM можно относительно легко копировать и переносить, а появление решений для облачных вычислений, таких как VMware vSphere или OpenNebula, позволяло достаточно легко управлять целыми кластерами серверов и распределять нагрузки между ними. Но, увы, виртуализация привносит огромные накладные расходы, а высокая степень изоляции нередко избыточна.
namespaces, cgroups
Параллельно с этим в ядре Linux появлялись и совершенствовались механизмы изоляции процессов друг от друга. Основных средств изоляции процессов два: namespaces, который позволяет скрывать от процессов ненужные им сетевые и дисковые ресурсы, а также другие процессы; и cgroups, который позволяет разграничивать вычислительные ресурсы между процессами.
LXC, Docker
Развитие механизмов изоляции породило идею, что, возможно, для решения проблем изоляции и переносимости приложений можно использовать не дорогие VM, а средства ядра Linux. Так появились инструменты контейнеризации: первым стал LXC, который доказал что приложения можно упаковать в изолированные, переносимые и при этом легковесные контейнера, а затем уже знакомый вам Docker, который из простой надстройки для LXC развился в самостоятельный стандарт индустрии.
Kubernetes
Но этого было мало: нужно было решение, которое не просто позволит легко поднять приложение, но которое бы позволило управлять множеством серверов, распределять нагрузку между ними и легко дублировать и делать параллельным выполнение приложений на множестве машин одновременно. Появился целый ряд решений позволяющих именно это, таких как Google Borg и Apache Mesos, но наиболее распространённым из всех стал Kubernetes.
Основные спецификации
OCI — Open Container Initiative
runtime-spec: стандарт конфигурации, жизненного цикла и выполнения контейнеров
image-spec: стандарт образов контейнеров
distribution-spec: стандарт распространения образов контейнеров
CRI — Container Runtime Interface: стандарт инструментария управления контейнерами
CNI — Container Network Interface: стандарт управления сетью и обеспечения сетевой связности контейнеров
CSI — Container Storage Interface: стандарт доступа к системам хранения данных
Устройство и архитектура
Kubernetes components
image/svg+xml
Kubernetes components
Cloud Native Computing Foundation
k-proxy
kubelet
sched
sched
sched
Control Plane
Node
etcd
Kubernetes cluster
api
api
api
c-c-m
c-c-m
c-c-m
c-m
c-m
c-m
Node
Node
k-proxy
kubelet
kubelet
k-proxy
Control plane
Scheduler
sched
Cloud controller manager (optional)
c-c-m
Controller manage r
c-m
kubelet
kubelet
kube-proxy
k-proxy
(persistence store)
etcd
etcd
Node
API server
api
Компоненты кластера Kubernetes подразделяются на два слоя: control plane (выделен на изображении) и data plane. Слоями они именуются, так как в кластере не существует отдельных “управляющих” и “управляемых” физических или виртуальных машин — компоненты могут быть распределены по узлам произвольным образом.
Control plane состоит из четырёх основных компонент:
apiserver: отвечает за API Kubernetes
scheduler: планировщик, отслеживает созданные поды и распределяет их по узлам в зависимости от требований и доступных ресурсов
controller-manager: отслеживает состояние кластера и приводит его к желаемому (объявленному в конфигурации)
etcd: хранилище данных, хранит состояние кластера
Также существует опциональный компонент cloud-controller-manager, который может использоваться в облачных окружениях для отслеживания и обновления состояния кластера через API, предоставляемый облачным провайдером.
Data plane состоит всего из двух компонент:
kubelet: создаёт и отслеживает контейнера; сам функционала контейнеризации не имеет, поэтому для работы требует отдельного ПО, реализующего CRI
proxy: отвечает за сетевую связность между подами, а также за связь подов с внешним миром
Варианты Kubernetes
Кроме основной поставки, существует также множество различных вариаций Kubernetes:
Коммерческие дистрибутивы:
Red Hat OpenShift: дистрибутив со своей встроенной системой сборки и повышенным фокусом на безопасность
VMWare Tanzu Kubernetes Grid: дистрибутив с интеграцией с продуктами виртуализации VMWare
Rancher: дистрибутив с фокусом на работу в гибридных облаках
и т.д.
Managed Kubernetes от облачных провайдеров:
GKE (Google Cloud)
EKS (Amazon Web Services)
AKS (Microsoft Azure)
и т.д., свой вариант встречается у многих крупных облачных провайдеров
Минималистичные дистрибутивы:
Minikube: полноценная, кроссплатформенная, простая в установке поставка от разработчиков Kubernetes, выполняется на VM
k3s: облегчённый вариант с урезанным функционалом для IoT-устройств, выполняется на VM
MicroK8s: облегчённая поставка для IoT-устройств, поставляется в Snap
и т.д.
Объекты Kubernetes
Ожидаемое состояние кластера Kubernetes полностью описано множеством объектов, существующих на нём. Абсолютно всему в кластере, будь то ваше приложение, балансировщик нагрузки, файл конфигурации или узел самого кластера, соответствует некий объект.
Любой объект Kubernetes имеет описание в формате YAML. Для создания нового объекта необходимо передать кластеру его описание, и любой объект может быть запрошен у кластера — тогда вы аналогично получите его YAML-описание. Ниже приведён простейший объект типа Namespace
:
apiVersion : v1
kind : Namespace
metadata :
name : newnamespace
spec : {}
status : {}
В описании объекта существует четыре обязательных поля:
apiVersion
: версия API Kubernetes, которой соответствует описание объекта.
kind
: тип объекта.
metadata
: словарь метаинформации объекта.
spec
: словарь спецификации — ожидаемого состояния объекта.
При запросе объекта с кластера также будет добавлено пятое поле status
, в котором содержится текущее состояние объекта. При создании объекта данное поле не нужно, и даже если оно присутствует, его содержимое будет просто проигнорировано.
Workload
Объекты рабочей нагрузки (Workload) являются наиболее полезными и часто используемыми объектами Kubernetes. Именно они используются для запуска практически любого приложения, которое вы можете захотеть запустить на вашем кластере.
В основе всех объектов рабочей нагрузки лежит под (Pod
). Под — это некоторое множество контейнеров, управляемых Kubernetes и объединённых общей изолированной средой. Все контейнера одного пода всегда запускаются на одном узле, имеют общий жизненный цикл, используют общий сетевой интерфейс и могут использовать общие дисковые ресурсы. Любые другие объекты рабочей нагрузки создают управляемые ими поды и определяют их поведение.
Поды, как и лёгшие в их основу Docker-контейнеры, по определению stateless. С точки зрения кластера поды сами по себе не имеют никакой ценности, и могут быть в любой момент удалены, перезапущены, перенесены на другой узел (без переноса каких-либо хранившихся внутри данных), и т.д. Поэтому никогда нельзя полагаться на хранимое в поде состояние, на любые изменения, сделанные внутри пода в процессе его работы. Для обеспечения персистентности в Kubernetes существуют отдельные специальные типы объектов.
Минимальный под выглядит так:
apiVersion : v1
kind : Pod
metadata :
name : nginx-pod
spec :
containers :
- name : nginx-container
image : nginx
Pod
apiVersion : v1 # версия API
kind : Pod # тип объекта
metadata :
name : nginx-pod # имя пода
namespace : newnamespace # пространство имён
labels : # метки
app : webserver
spec :
containers : # список контейнеров
- name : nginx-container # имя контейнера
image : nginx # образ
imagePullPolicy : Always # когда скачивать образ
envFrom : # объявление переменных
- configMapRef :
name : nginx-config
readinessProbe : # проверка готовности пода
exec :
command :
- cat
- /data/file.txt
livenessProbe : # проверка работоспособности пода
httpGet :
path : /
port : 80
ports : # открытые порты
- containerPort : 80
resources : # доступные ресурсы
requests :
cpu : "0.5"
memory : "100Mi"
limits :
cpu : "1"
memory : "200Mi"
volumeMounts : # монтирование томов в контейнер
- name : data
mountPath : /data
initContainers : # список контейнеров инициализации
- name : init-container
image : busybox
imagePullPolicy : IfNotPresent
env :
- name : STRING
value : "ready ready"
command : [ "sh" , "-c" , "echo $(STRING) > /data/file.txt" ]
resources : {}
volumeMounts :
- name : data
mountPath : /data
restartPolicy : Always # когда перезапускать под
volumes : # тома
- name : data
emptyDir : {}
Так выглядит описание пода с большим количеством параметров. Здесь приведена только часть наиболее используемых параметров.
Рассмотрим присутствующие здесь параметры (в порядке, в котором они видны в файле):
metadata
name
: имя объекта, должно быть уникально среди всех объектов данного типа (здесь это под) в пространстве имён
namespace
: пространство имён, кластер для удобства может быть поделён на произвольное количество пространств имён
labels
: метки, используются для разметки и группировки объектов, многие операции могут быть применены с выбором объектов по меткам
spec
containers
: массив контейнеров внутри пода. Минимальное определение контейнера состоит из его названия и образа
name
: имя контейнера
image
: образ, из которого разворачивается контейнер
imagePullPolicy
: политика получения образа, может быть Always
(всегда пытаться получить последнюю версию образа), IfNotPresent
(не пытаться обновить образ, если уже есть на узле), Never
(никогда не пытаться получить образ, запуск пода завершается ошибкой если подходящего образа нет на узле)
env
,envFrom
: объявление переменных окружения в контейнере. envFrom
позволяет получить сразу все переменные в файле конфигурации
livenessProbe
, readinessProbe
: probes — механизм отслеживания состояния приложения в контейнере. Каждый probe указывает на некоторую проверку, которую необходимо периодически проводить для получения достаточной информации о состоянии. Бывает трёх типов:
readinessProbe
: используется для проверки, что приложение готово принимать трафик, если не пройдено, то трафик на контейнер временно не отправляется
livenessProbe
: используется для проверки, что приложение работает, если не пройдено, контейнер перезапускается
startupProbe
: используется для проверки, что приложение запустилось, пока не пройдено, трафик не отправляется и другие probe не выполняются, проверяется до первой успешной проверки
ports
: определение портов, которые слушает контейнер
resources
: ограничения вычислительных ресурсов. Бывает двух видов: requests
— минимально доступные контейнеру ресурсы, которые должны быть доступны гарантированно — и limits
— максимально доступные ресурсы, не может быть превышено
volumeMounts
: тома, которые будут подключены к данному контейнеру. Сами тома определяются ниже в блоке volumes
command
: произвольная команда, передаваемая контейнеру на запуск вместо определённого в образе entrypoint. Представляет собой массив, аналогичный массиву ENTRYPOINT
в Dockerfile
initContainers
: контейнеры инициализации, запускаются для каких-либо подготовительных операций до запуска основных контейнеров. Основные контейнера не запускаются, пока выполнение всех контейнеров здесь не будет успешно выполнено. Определение контейнеров здесь совпадает с определениями в блоке containers
, но не все параметры могут быть использованы
restartPolicy
: политика перезапуска пода при завершении работы любого из контейнеров, может быть Always
(под всегда будет перезапущен), OnFailure
(под будет перезапущен, только если выполнение любого контейнера завершилось неудачей), Never
(под никогда не будет перезапущен)
volumes
: определение томов, которые могут быть подключены к контейнерам. Может использовать множество различных вариантов определения тома, здесь используется emptyDir
, пустая временная директория, содержимое которой будет удалено при удалении самого пода
ReplicaSet
apiVersion : apps/v1
kind : ReplicaSet
metadata :
name : nginx
labels :
app : webserver
spec :
replicas : 3 # количество реплик пода
selector : # селектор, определяющий управляемые поды
matchLabels :
app : webserver
template : # шаблон пода
metadata :
labels :
app : webserver
spec :
containers :
- name : nginx
image : nginx
ReplicaSet
— следующий уровень управления рабочей нагрузкой после подов. Легко можно заметить, что блок template
в описании ReplicaSet
очень похож на описание самого пода.
Основная задача ReplicaSet
— автоматически создавать копии подов. ReplicaSet
создаёт одинаковые поды, соответствующие описанию в блоке template
, в количестве указанном в блоке replicas
. Названия в метаданных для этого пода можно не указывать, так как они всегда генерируются автоматически самим ReplicaSet
.
ReplicaSet
рассчитан на приложения, которые должны работать постоянно. Существование ReplicaSet
указывает, что всегда должен работать один или более под с вашим приложением, поэтому в случае каких-либо проблем поды будут перезапущены или пересозданы.
Важно обратить внимание на блок selector
: здесь указан селектор — объект, позволяющий Kubernetes выбирать некоторое множество подов, в данном случае по их меткам (matchLabels
). Только этот селектор определяет, какие поды будут учитываться ReplicaSet
. ReplicaSet
, селектор которого не соответствует описанному поду в template
, не является корректным. При этом поды, созданные до ReplicaSet
и соответствующие селектору, попадут под управление ReplicaSet
и могут быть уничтожены, даже если их описание не соответствует написанному в ReplicaSet
шаблону. Поэтому с ReplicaSet
нужно работать аккуратно.
Deployment
apiVersion : apps/v1
kind : Deployment
metadata :
name : nginx
labels :
app : webserver
spec :
replicas : 3 # количество реплик
selector :
matchLabels :
app : webserver
strategy : # стратегия обновления
rollingUpdate :
maxSurge : 1
maxUnavailable : 1
type : RollingUpdate
template :
metadata :
labels :
app : webserver
spec :
containers :
- name : nginx
image : nginx
Хотя ничего не мешает использовать ReplicaSet
напрямую, это обычно не рекомендуется. Как правило, они используются опосредованно через объекты вида Deployment
.
Deployment
— объект, создающий объекты типа ReplicaSet
. Как видно, определение Deployment
похоже на определение ReplicaSet
.
Главная особенность Deployment
состоит в том, что он хранит историю изменений своей конфигурации. В любой момент, если что-то пойдёт не так, можно откатить Deployment
на предыдущую версию. Кроме того, Deployment
поддерживает разные стратегии обновления, которые настраиваются в блоке strategy
. Поддерживается две стратегии обновления: Recreate
указывает, что нужно просто удалить все поды предыдущей версии и создать поды новой; RollingUpdate
же позволяет заменять поды по частям и направлять трафик как на старые, так и на новые поды одновременно, что позволяет поддерживать непрерывную доступность приложения во время обновления.
В отличие от обычного ReplicaSet
, Deployment
(и созданные им ReplicaSet
) никогда не захватывают уже существующие поды — для всех ReplicaSet
автоматически генерируется дополнительная метка — хэш конфигурации пода. Несмотря на это, селектор Deployment
всё ещё должен соответствовать шаблону пода. Благодаря тому, что в качестве метки используется хэш, при изменении конфигурации Deployment
не затрагивающей шаблон пода кластер может сохранить существовавшие до изменения поды и передать их под управление новой версии ReplicaSet
.
StatefulSet
apiVersion : apps/v1
kind : StatefulSet
metadata :
name : nginx
labels :
app : webserver
spec :
serviceName : nginx
replicas : 3
selector :
matchLabels :
app : webserver
template :
metadata :
labels :
app : webserver
spec :
containers :
- name : nginx
image : nginx
StatefulSet
— специальный вид рабочей нагрузки, во многом похожий на Deployment
, но имеющий одну важную особенность: в отличие от любых других видов рабочей нагрузки, StatefulSet
, как понятно из названия, хранит состояние для каждой реплики. Это означает, что у каждого пода, управляемого StatefulSet
, есть собственный номер, который известен внутри пода и может быть использован приложением, постоянное хранилище, уникальное внутреннее доменное имя, по которому можно подключиться к поду с конкретным номером. Также у StatefulSet
чётко определён порядок, в котором поды запускаются и завершают работу: по возрастанию номера при запуске и по убыванию при завершении работы.
Такой вид рабочей нагрузки редко нужен и его использование не рекомендуется, если это явно не необходимо. Но некоторые приложения, например многие из тех, у которых взаимодействие между репликами устроено по принципу “ведущий-ведомый”, могут быть корректно запущены только с таким видом рабочей нагрузки.
Job
apiVersion : batch/v1
kind : Job
metadata :
name : pi
spec :
template :
spec :
containers :
- name : pi
image : perl:5.34.0
command : [ "perl" , "-Mbignum=bpi" , "-wle" , "print bpi(2000)" ]
restartPolicy : Never
completions : 5 # количество успешных выполнений
parallelism : 2 # количество одновременных запусков
backoffLimit : 6 # допустимое количество провалов
Job
— вид рабочей нагрузки, существующий для разовых задач. В отличие от Deployment
или StatefulSet
, он рассчитан на приложения, выполнение которых когда-либо должно завершиться. Job
, как и другие виды рабочей нагрузки, создаёт поды, но эти поды не будут перезапущены после успешного завершения выполнения приложения.
За поведение Job
отвечает несколько специальных параметров:
completions
указывает на то, сколько раз задача должна быть выполнена. Поды будут создаваться, пока не достигнуто указанное здесь количество успешных запусков
parallelism
указывает на то, сколько подов могут выполнять задачу одновременно. Реальное количество созданных подов никогда не превышает количество оставшихся выполнений
backoffLimit
указывает, после какого количества подов, выполнение которых завершилось ошибкой, выполнение задачи будет остановлено
CronJob
apiVersion : batch/v1
kind : CronJob
metadata :
name : date
spec :
schedule : "* * * * *" # расписание выполнения
jobTemplate : # шаблон Job
spec :
template :
spec :
containers :
- name : date
image : busybox
command : [ "/bin/sh" , "-c" , "date" ]
restartPolicy : Never
CronJob
— надстройка над Job
, которая позволяет запускать какие-то задачи по расписанию. В параметре jobTemplate
указывается конфигурация Job
, который должен создаваться с некоторой периодичностью, а в параметре schedule
— расписание выполнения в формате Cron.
Networking
Каждый под — отдельный виртуальный узел со своим сетевым интерфейсом.
Внутри кластера существует единая сеть, и каждый под имеет собственный IP-адрес внутри этой сети.
Каждый под внутри кластера считается отдельной и независимой единицей. Вдобавок, каждый под имеет свои требования к использованию сети. Поэтому чтобы не было конфликтов между разными подами, каждый отдельный под получает свой собственный виртуальный сетевой интерфейс, и может использовать его так, как пожелает. По сути, работу с подами внутри сети кластера можно воспринимать аналогичной работе с физическими узлами в некой реальной сети.
Внутри кластера существует единое пространство IP-адресов, которые выдаются сетевым интерфейсам подов (Pod CIDR). У каждого узла в кластере есть своё подпространство адресов, в котором выдаются адреса подам, запущенным на данном узле, но все поды кластера всегда находятся в единой сети. Благодаря этому любой под может подключаться напрямую к любому другому поду.
Но важно заметить, что эти IP-адреса не статичны — в любой момент под может быть пересоздан, и в этот момент он может получить новый IP-адрес. Вдобавок часто у вас в кластере поднято более одного экземпляра приложения, и вы скорее всего хотите подключаться именно к приложению где бы оно ни было доступно, вместо того чтобы привязываться к конкретному поду который может упасть или исчезнуть. Для решения этой проблемы существуют Service
.
Service
apiVersion : v1
kind : Service
metadata :
name : nginx
spec :
type : ClusterIP # тип Service
selector : # селектор, определяющий целевые поды для трафика
app : webserver
ports : # открытые порты
- name : "http" # название
protocol : TCP # протокол TCP/UDP/SCTP
port : 80 # открытый порт сервиса
targetPort : 80 # целевой порт пода
Service
— объект Kubernetes, существующий для получения трафика и перенаправления его к подам. Каждый Service
, аналогично подам, имеет свой IP-адрес. Для всех Service
кластера существует пространство IP-адресов (Service CIDR), который находится в одной внутренней сети с пространством адресов подов, Благодаря чему любой под также может подключаться напрямую к любому Service
и наоборот. Кроме того, каждый Service
получает своё собственное постоянное внутреннее доменное имя вида [servicename].[namespacename].svc.[clusterdomain.name]
, которое известно всем подам кластера. Подключиться к конкретному поду за Service
можно по адресу [pod-ip-address].[service-name].[namespace-name].svc.[cluster-domain.name]
, в случае подов управляемых StatefulSet
также есть нумерованные доменные имена [statefulset-name]-[pod-index].[service-name].[namespace-name].svc.[cluster-domain.name]
.
Основная задача Service
состоит в перенаправлении трафика. Весь трафик, приходящий на Service
, автоматически перенаправляется на определённые конфигурацией Service
поды. Выбор подов осуществляется через селектор (параметр selector
), который аналогичен тем селекторам, которые можно было увидеть ранее в конфигурации ReplicaSet
или Deployment
. Если селектор соответствует более чем одному поду, трафик распределяется равномерно между всеми подами. Трафик направляется только на те поды, которые могут его принимать — если под по каким-то причинам не запущен, либо проверка его readinessProbe
не была успешной, то трафик на этот под не направляется.
В блоке ports
задаются порты, которые будет слушать Service
. Портов может быть произвольное количество. Основные параметры:
name
: произвольное имя, задаётся в основном для удобства использования
protocol
: TCP / UDP / SCTP
port
: порт, который будет слушать Service
, единственный обязательный параметр
targetPort
: порт пода, на который будет перенаправляться трафик. Если не определён, то используется тот же порт, что и в port
Тип Service
задаётся в параметре type
. Cуществуют Service
четырёх типов:
ClusterIP
: тип по умолчанию, не имеет особых свойств
NodePort
: для каждого порта сервиса также выбирается порт в каком-то диапазоне (по умолчанию 30000-32767), который открывается непосредственно на узлах кластера. Любой трафик, попадающий на этот порт любого узла кластера, попадает внутрь кластера и перенаправляется подам в соответствии с конфигурацией Service
. Открытый порт выбирается случайным образом из свободных, либо может быть определён вручную через дополнительный параметр nodePort
в конфигурации порта.
LoadBalancer
: надстройка над NodePort
, позволяющая использовать для распределения трафика внешний управляемый балансировщик нагрузки. Для использования на кластере должна быть настроена интеграция с этим самым балансировщиком.
ExternalName
: специальный вид Service
, который позволяет направлять трафик не на поды, а на какие-то внешние ресурсы. Для этого типа Service
селектор игнорируется, вместо этого задаётся доменное имя внешнего ресурса (IP-адреса не поддерживаются) через дополнительный параметр externalName
.
Ingress
apiVersion : networking.k8s.io/v1
kind : Ingress
metadata :
name : nginx
labels :
name : webserver
spec :
rules :
- host : webserver.lan
http :
paths :
- pathType : Prefix
path : "/"
backend :
service :
name : nginx
port :
number : 80
Объекты Service
используются для настройки трафика внутри кластера. Хотя получить трафик снаружи кластера можно при помощи NodePort
, это обычно не применимо для продуктивного использования, и NodePort
используются чаще всего только для задач отладки и тестирования. Исторически существовало множество (часть из них всё ещё развиваются и используются) различных надстроек над Kubernetes, позволявших настраивать получение трафика кластером, требовавших различных подходов к использованию и настройке. Сейчас в Kubernetes для таких задач есть общий механизм Ingress
.
Ingress
— унифицированное решение для управления попадающим в кластер снаружи трафиком независимо от того, как настроен кластер и как трафик доходит непосредственно до его узлов. Объекты типа Ingress
позволяют управлять надстройкой IngressController
, которых существует огромное множество под разные виды инфраструктуры, разные нужды и разные личные предпочтения. При наличии в кластере IngressController
объект типа Ingress
позволяет весьма просто получить трафик снаружи и направить его на Service
внутри.
Ingress
рассчитаны исключительно на стандартный HTTP/HTTPS. Хотя некоторые IngressController
могут позволять какие-то более сложные конфигурации, стандартный Ingress
позволяет исключительно получать соединения на стандартные порты 80/443 и распределять трафик на основании доменных имён или путей, аналогично тому, как это может делать классический reverse proxy.
Поведение Ingress
определяется набором правил, который описан в блоке rules
. В типичном правиле для Ingress
указано, на какой backend
(как правило, это Service
) отправлять запросы, пришедшие на определённые доменное имя и путь.
ConfigMap
apiVersion : v1
kind : ConfigMap
metadata :
name : nginx-config
labels :
app : webserver
data :
nginx_entrypoint_quiet_logs : "0"
nginx_conf : |
server {
listen 80;
server_name localhost;
location / {
root /usr/share/nginx/html;
index index.html;
}
}
apiVersion : v1
kind : Pod
metadata :
name : nginx-pod
labels :
name : webserver
spec :
containers :
- name : nginx-container
image : nginx
env :
- name : NGINX_ENTRYPOINT_QUIET_LOGS
valueFrom :
configMapKeyRef :
name : nginx-config
key : nginx_entrypoint_quiet_logs
ports :
- containerPort : 80
volumeMounts :
- name : config
mountPath : "/etc/nginx/conf.d"
readOnly : true
volumes :
- name : config
configMap :
name : nginx-config
items :
- key : "nginx_conf"
path : "default.conf"
Как уже упоминалось, поды в Kubernetes stateless. Они не хранят состояние, поэтому привычный сценарий “запустить приложение и настроить его” обычно малоприменим. Конечно, можно было бы собирать образа контейнеров с уже настроенным приложением внутри, но это было бы довольно неудобно — малейшее изменение конфигурации потребовало бы пересборки и последующей доставки образов. Поэтому в Kubernetes существуют специальные объекты, которые отвечают за хранение конфигурации для подов.
ConfigMap
отвечает за хранение конфигурации внутри кластера. Все данные, необходимые для конфигурации приложения, будь то переменные окружения или файлы конфигурации, задаются непосредственно внутри ConfigMap
.
ConfigMap
крайне прост: блок data
(здесь стоит обратить внимание, что описание ConfigMap
не содержит типичного блока spec
) содержит в себе данные в формате ключ-значение. Эти данные затем могут быть переданы в контейнера как переменные окружения через параметры env
и envFrom
, либо как файлы через volumes
и volumeMounts
.
Secret
apiVersion : v1
kind : Secret
metadata :
name : certs
type : Opaque
data :
ca : >-
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJkakNDQVJ5Z0F3SUJBZ0lSQU5aWXNXb3JrTXFjNzNpQ3phWVZKcEl3Q2dZSUtvWkl6ajBFQXdJ
d0R6RU4KTUFzR0ExVUVDaE1FYm1sc01UQWdGdzB5TXpFeU1qZ3dOakUxTVRGYUdBOHlNVEl6TVRJd05EQTJNVFV4TVZvdwpEekVOTUFzR0ExVUVDaE1F
Ym1sc01UQlpNQk1HQnlxR1NNNDlBZ0VHQ0NxR1NNNDlBd0VIQTBJQUJHaXNUSThGCnNZMExuWGFTOFFKSEh0SUlvRkE2VzVUZmg2ckdvc1A1d1k2bHhx
STNJQjNESTE4b0xWZ2h2cnVLVCtIaDFBK1AKeVVNYmFMLzRmSnFpbVk2alZ6QlZNQTRHQTFVZER3RUIvd1FFQXdJQ0JEQVRCZ05WSFNVRUREQUtCZ2dy
QmdFRgpCUWNEQVRBUEJnTlZIUk1CQWY4RUJUQURBUUgvTUIwR0ExVWREZ1FXQkJRdTJyMWFNbkw3UFl4cU1ybmtSZXdkCmZ0a0hLREFLQmdncWhrak9Q
UVFEQWdOSUFEQkZBaUJFRnlpRUFaY2FKVW96SzZzdFpzYVdOa2N2dVBSRFNXR3QKU2M1TTJ3NXFoUUloQU90NEJxb09uc2JSaWhjQ1FLOE5VNnJRWW80
VmRYRUJQVEgzMTBLZ0s1cXcKLS0tLS1FTkQgQ0VSVElGSUNBVEUtLS0tLQo=
cert : >-
LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUJ1akNDQVdDZ0F3SUJBZ0lRVEZKL3ZtRStuNjlvSy9HQ0xDVGRwekFLQmdncWhrak9QUVFEQWpB
UE1RMHcKQ3dZRFZRUUtFd1J1YVd3eE1DQVhEVEl6TVRJeU9EQTJNVFV4TVZvWUR6SXhNak14TWpBME1EWXhOVEV4V2pBUApNUTB3Q3dZRFZRUUtFd1J1
YVd3eU1Ga3dFd1lIS29aSXpqMENBUVlJS29aSXpqMERBUWNEUWdBRVZHcjE4aXVBCkJBUDE4Wnd4M1NuZURZNEhBZ1NSK3VjQ3lXRGR1dHBFYVI5TURH
bmNjNWwzckZ6WW9CM2FYcWFnclp4aC9BSDYKdERrQzc0Ung3Y3d0VnFPQm16Q0JtREFPQmdOVkhROEJBZjhFQkFNQ0JhQXdFd1lEVlIwbEJBd3dDZ1lJ
S3dZQgpCUVVIQXdFd0RBWURWUjBUQVFIL0JBSXdBREJqQmdOVkhSRUVYREJhZ2lKcGJtZHlaWE56TFc1bmFXNTRMV052CmJuUnliMnhzWlhJdFlXUnRh
WE56YVc5dWdqUnBibWR5WlhOekxXNW5hVzU0TFdOdmJuUnliMnhzWlhJdFlXUnQKYVhOemFXOXVMbWx1WjNKbGMzTXRibWRwYm5ndWMzWmpNQW9HQ0Nx
R1NNNDlCQU1DQTBnQU1FVUNJUUN4Nk5NYQp6TDJ5SXVTZnZLekZRMThqMWdRd0FzaEVnYmdSZ1Myd2ZZc0MrUUlnTFZ0bHJ6NjBIbGhISGt4eERYbFVV
N1RyClU2NkdBcWZMRXUxakl0dVN0c009Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K
key : >-
LS0tLS1CRUdJTiBFQyBQUklWQVRFIEtFWS0tLS0tCk1IY0NBUUVFSUJhWVVQNGZLRExxaFYxUU44aElNNWthMCt2QVVQRTVYRXA6VzgvMGJKUUdvQW9H
Q0NxR2NNNDkKQXdFSG9VUURRZ1FFVkdyMThpdUFCQVAxOFp3eDNTbmVEWTRIQWdTUit1Y0N5V0RkdXRwRWFSOU1EO25jYzVsMwpyRnpZb0IzYVhxYWdy
WnhoL0FINnREa0M3NFJ4N2N3dFZnPT0KLS0tLS1FTkQgRUMgUFJJVkFURSBLRVktLS0tLQo=
Объект типа Secret
является полным аналогом ConfigMap
и используется аналогично, но имеет несколько отличий. В отличие от ConfigMap
, Secret
, как понятно из названия, существует для хранения секретных данных. Самым заметным отличием является то, что в конфигурации Secret
данные закодированы base64, что конечно не даёт особой безопасности, но позволяет защитить данные от “подглядывания из-за спины”. Кроме того, кластер Kubernetes ограничивает доступ к ним для подов, избегает записи данных в них на диск всегда, когда возможно, и поддерживает опциональное шифрование данных Secret
в хранилище etcd.
Существует несколько разных типов Secret
(определяются в блоке type
). В большинстве случаев используется тип Opaque
, не имеющий никаких специфических свойств, то также существует ряд специальных типов, используемых для авторизации SSH, Docker или даже в сам кластер Kubernetes.
При создании Secret
можно заменить блок data
блоком stringData
, и в нем указать все хранимые в Secret
в явном виде, без base64. При сохранении они будут закодированы автоматически.
PersistentVolume
apiVersion : v1
kind : PersistentVolumeClaim
metadata :
name : nginx-stored-data
labels :
name : webserver
spec :
storageClassName : manual
accessModes :
- ReadWriteOnce
resources :
requests :
storage : 3Gi
apiVersion : v1
kind : PersistentVolume
metadata :
name : volume001
labels :
type : local
spec :
storageClassName : manual
capacity :
storage : 10Gi
accessModes :
- ReadWriteOnce
hostPath :
path : "/mnt/data"
apiVersion : v1
kind : Pod
metadata :
name : nginx-pod
labels :
name : webserver
spec :
containers :
- name : nginx-container
image : nginx
ports :
- containerPort : 80
volumeMounts :
- name : uploads
mountPath : "/var/www/upload"
volumes :
- name : uploads
persistentVolumeClaim :
claimName : stored-data
Кроме конфигурации часто нужно хранить и какие-то данные, появившиеся непосредственно во время работы приложения. Для этого существуют PersistentVolume
, во многом схожие с томами Docker.
Объект PersistentVolume
описывает том — некое постоянное хранилище определённого объёма, доступное кластеру. Сам по себе Kubernetes управлять хранилищами не может, вместо него за это отвечают плагины. PersistentVolume
бывают множества разных типов, соответствующих разным видам подлежащих хранилищ, поддержка разных типов зависит от установленных на кластере плагинов. В актуальных версиях Kubernetes доступ к различным видам подлежащих хранилищ предоставляется через общий интерфейс CSI, и потому большинство других плагинов и типов PersistentVolume
объявлены устаревшими и не рекомендуются к использованию без явной необходимости. Как правило, PersistentVolume
в современных кластерах создаются автоматически посредством динамического провизионирования, и создавать их вручную мало где требуется.
PersistentVolume
существуют для объявления некоего хранилища, доступного кластеру — поэтому PersistentVolume
никогда не относятся к никакому пространству имён и любой свободный PersistentVolume
может быть использован кем угодно. Для того, чтобы объявить что PersistentVolume
занят и будет использован приложением, необходимо создать объект PersistentVolumeClaim
.
PersistentVolumeClaim
описывает запрос на постоянное хранилище с определёнными параметрами. При наличии свободных подходящих PersistentVolume
(либо возможности создать такой автоматически) он будет привязан к PersistentVolumeClaim
и может быть немедленно использован приложением, в противном случае PersistentVolumeClaim
будет ожидать появления подходящего хранилища.
PersistentVolumeClaim
занимает ближайший подходящий требованиям PersistentVolume
. Это означает, что если в кластере существуют тома только размером, например, 10GB, то PersistentVolumeClaim
запрашивающий 100MB займёт 10GB, а PersistentVolumeClaim
запрашивающий 10.1GB зависнет в ожидании нужного ему тома.
Основные параметры PersistentVolumeClaim
: - storageClassName
: класс хранилища. В кластере могут быть определены любые произвольные классы. storageClassName
PersistentVolumeClaim
всегда строго соответствует аналогичному параметру PersistentVolume
. Данный параметр может быть опущен — тогда будет выбран класс по умолчанию. При использовании динамического провизионирования класс обязательно должен быть такой, какие выделяются доступным провизионером. - accessModes
: режимы доступа, бывают четырёх типов: - ReadWriteOnce (RWO): доступен для чтения и записи только с одного узла кластера; - ReadOnlyMany (ROX): доступен только для чтения со всех узлов кластера; - ReadWriteMany (RWX): доступен для чтения и записи со всех узлов кластера; - ReadWriteOncePod (RWOP): доступен для чтения и записи только для одного пода, поддерживается только при использовании CSI. - resources
: запрашиваемые объёмы хранилища. Аналогичен параметру resources
в конфигурации пода, но может запрашивать только ресурсы storage
.
Minikube
Простейшей для установки и запуска в целях обучения поставкой Kubernetes является Minikube.
Minikube поставляется в виде одного исполняемого файла, работает на Linux, Windows и macOS, для работы требует только наличия среды виртуализации (KVM, Hyper-V, HyperKit и др.)
Для установки достаточно скачать исполняемый файл.
Часто используемые команды:
minikube start
— запускает Kubernetes
minikube start --driver=virtualbox --cpus=4 --memory=16gb --disk-size=32gb
— + параметры запуска (VM VirtualBox, 4 vCPU, 16GB RAM, 32GB storage)
minikube stop
— останавливает Kubernetes
minikube pause
— приостанавливает Kubernetes
minikube unpause
— продолжает работу после приостановки
minikube kubectl -- <command>
— встроенный клиент kubectl
minikube dashboard
— запускает и даёт доступ к плагину dashboard, позволяющему просматривать состояние кластера через браузер
minikube addons
— управление аддонами Minikube
minikube addons list
— выводит список доступных аддонов
minikube addons enable <name>
— включает аддон
minikube addons disable <name>
— выключает аддон
рекомендуется установить хотя бы следующие аддоны: dashboard
, ingress
, storage-provisioner
minikube config
— управление настройками
minikube config get <name>
— выводит значение параметра настройки
minikube config set <name> <value>
— устанавливает значение параметра настройки
minikube config view
— выводит все установленные нестандартные настройки
minikube ip
— выводит IP-адрес VM Minikube
minikube delete
— удаляет VM Minikube
kubectl
kubectl — CLI-клиент Kubernetes, базовый инструмент для взаимодействия с кластером.
Для установки достаточно скачать исполняемый файл.
Часто используемые команды:
kubectl --help
— вывод встроенной справки, флаг --help
может быть использован с любой командой kubectl для получения справки о ней
kubectl create -f <filename>
— создаёт объект из его YAML-конфигурации в файле
kubectl create <type> <name>
— создаёт объект с определённым типом и названием (поддерживается только небольшое количество объектов, параметры определяются через флаги, свои для каждого типа объектов)
kubectl run <name> --image=<image>
— запускает приложение из докер-образа в простейшем поде с одним контейнером
kubectl expose <type> <name> --port=<port>
— создаёт простейший Service
, открывающий порт для указанного объекта
kubectl rollout
— управление версиями Deployment
и StatefulSet
kubectl scale <type> <name> --replicas=<count>
— изменяет количество реплик для ReplicaSet
, Deployment
и StatefulSet
kubectl get <type>
— выводит все объекты указанного типа
флаг -o
/ --output
позволяет установить формат вывода, некоторые форматы: name
позволяет получить только имя, wide
выдаёт больше информации, yaml
/json
позволяет получить описание объекта целиком в формате YAML/JSON, jsonpath
позволяет запрашивать форматированные и отфильтрованные данные с использованием шаблонов JSONPath
флаг -l
/ --selector
позволяет фильтровать выводимые объекты по меткам, например kubectl get pods -l app=webserver
выведет все поды с меткой app: webserver
kubectl get <type> <name>
— выводит указанный объект
kubectl explain <type>
— выводит документацию по типу объектов
kubectl describe <type> <name>
— выводит подробную информацию по объекту
kubectl edit <type> <name>
— интерактивное редактирование объекта при помощи текстового редактора
редактор можно выбрать, указав его исполняемый файл в переменной окружения KUBE_EDITOR
kubectl delete <type> <name>
— удаляет объект
kubectl exec <pod> -c <container> -- <command>
— запускает произвольную команду в контейнере
kubectl logs <pod> -c <container>
— выводит лог контейнера