Первая часть серии: Kubernetes просто, часть 1: зачем Kubernetes, устройство кластера
https://habr.com/p/1082088/

В первой части мы успели разобрать архитектуру Kubernetes: кластер, ноды, поды, Control Plane, worker Node и так далее.

Как вы помните, главная идея следующая: «Kubernetes постоянно пытается приводить действительное состояние кластера к желаемому»

К примеру, мы хотим видеть три экземпляра приложения:

Desired State: 3 Pods
Actual State: 2 Pods 

Заметив несоответствие Kubernetes самостоятельно пытается создать еще 1 экземпляр, соответственно получить

Desired State: 3 Pods
Actual State: 3 Pods 

Однако, вопрос того, как же сообщить Kubernetes то самое желаемое (desired) состояние, мы не затрагивали.

Как раз этим сегодня и займемся! В статье разберем YAML-манифесты, Deployment, ReplicaSet, labels и selectors. Как итог, уже сегодня вы сможете самостоятельно проследить весь путь от kubectl apply до работающих контейнеров.

Императивный и декларативный подходы

Этот вопрос мы уже косвенно рассмотрели в первой статье, просто не зацикливайтесь на названиях.

Так, должно быть, вы помните, в управлении всякой системой есть два подхода.Говорить ей:

1) Подойди к двери
2) Возьмись за ручку
3) Нажми на неё 
4) Открой дверь

Называется императивным подходом - здесь мы конкретно руководим и описываем, что именно нужно сделать.

В Kubernetes мы пользуемся другим подходом - декларативным, в нем мы просто сообщаем системе:

«Открой дверь»

А она уже сама решает, как ей это действие удобно сделать. То есть, мы просто описываем результат, который желаем получить.

В Kubernetes преимущества декларативного подхода ощущаются неимоверно. Мы не говорим:

Создай Pod A
Создай Pod B
Создай Pod C

Вместо этого, он предлагает просто обозначить:

replicas: 3 

Что значит:

Я хочу, чтобы приложение было развернуто в трех экземплярах.

Если 2 экземпляра уже есть, то система сама решит, что ей нужно развернуть 1 Pod, если их и так 3, она не будет делать ничего.

Это и есть тот reconciliation loop, о котором мы говорили в первой части.

Kubernetes-манифест

Желаемое состояние Kubernetes-кластера часто описывается в YAML-файлах.К примеру:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.27

Обычно такие файлы называют manifest.

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

YAML просто

Для начала - что же такое YAML?

YAML - формат представления структурированных данных.

К примеру:

name: Boris
age: 27

Здесь есть две пары вида Ключ : Значение, которые показывают имя человека и его возраст, соответственно.

Можно объединить их чем-то общим - создать вложенную структуру:

person:
    name: Boris
    age: 27

Ну и конечно, можно делать списки, давайте укажем какие языки знает наш Борис:

person:
    name: Boris
    age: 27 
    languages:
        - english
        - russian 

Дефис здесь означает пункт списка.

Главное, что вам нужно сейчас:

В YAML отступы имеют значение

Так в уже разобранном примере:

person:
    name: Boris
    age: 27 

Означает, что name и age находятся (принадлежат) в структуре person.

Четыре поля, которые встречаются постоянно

Давайте ознакомимся с началом нашего manifest:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3

Здесь есть сразу 4 важных понятия:

1. apiVersion
2. kind
3. metadata
4. spec

Поочередно обсудим каждый из них:

apiVersion

apiVersion: apps/v1

Показывает group/version в котором определен тот или иной объект, к примеру объект Deployment, о котором мы подробно поговорим чуть позже. Определен в apps/v1, а Pod, о котором вы уже знаете определен просто в v1.

Запоминать все версии не нужно. Вам достаточно понимать назначение этого поля.

kind

kind: Deployment

Отвечает на вопрос: «Какой Kubernetes-объект мы описываем?»

Это может быть:

kind: Pod
kind:Deployment
kind: Service
kind: ConfigMap

и множество других объектов, которые мы рассмотрим позднее.

metadata

metadata:
    name: nginx-deployment

Содержит информацию об объекте, который мы создаем, в данном случае его имя - nginx-deployment. Позднее это поле будет нам полезно и другими параметрами, которые можно в нем указать, мы к нему еще вернемся.

spec

spec:  replicas: 3

Здесь начинается самое интересное. spec описывает желаемую конфигурацию создаваемого объекта. В нашем случае говорится:

«Я хочу три replicas (экземпляра)»

Иными словами, spec связан напрямую с уже знакомым нам Desired State.

Зачем Deployment?

Должно быть, у вас уже возник вполне логичный вопрос:«Почему в kind мы указываем Deployment? Почему нам просто не создать 3 пода.»

К примеру:

Pod A
Pod B
Pod C

Проблема появляется тогда, когда один из подов, созданных с помощью kind: Pod исчезает.

Допустим:

Pod A работает
Pod B работает
Pod C перестал работать

Дело в том , что если мы создаем три отдельных пода, само по себе это не выражает мысль:

Мне всегда нужны три экземпляра

Мы описали три конкретных объекта, а не желаемое количество экземпляров приложения. Поэтому обычно управление происходит с помощью контроллеры более высоких уровней. Один из самых распространенных как раз - Deployment.

Deployment - ReplicaSet - Pod - Container

Когда мы создаем Deployment автоматически создается следующая иерархия:

Deployment -> ReplicaSet -> PodsА

внутри каждого Pod, как мы и говорили, работают контейнеры.

Важно не смешивать уровни:

Deployment управляет обновлением приложений и ReplicaSet.

ReplicaSet поддерживает необходимое количество подов.

Pod - минимальная единица развертывания в Kubernetes.

Container - непосредственная единица, в которой работает процесс приложения.

Если один из Pod исчезнет - контроллер ReplicaSet увидит то, что теперь желаемое состояние не совпадает с действительным и система начнет создавать новый Pod. Снова знакомый нам reconciliation (цикл согласования).

Зачем тогда Deployment, если его задачу выполняет ReplicaSet?

Еще один вопрос, зачем нужен Deployment, если ReplicaSet и так поддерживает необходимое количество подов?Дело в том, что в процессе эксплуатации, не только необходимо поддерживать заданное количество экземпляров приложения, но и обновлять его.Допустим, что сейчас работает 3 экземпляра соответствующих версий:

nginx:1.26
nginx:1.26
nginx:1.26

И теперь мы хотим перейти на nginx:1.27. Как это сделать правильно, ведь одновременная остановка всех действующих экземпляров приведет к временной недоступности приложения, а как следствие, к потере трафика. Deployment позволяет организовать rolling update приложения.

Вместо одновременного уничтожения всех старых экземпляров и запуска новых, Kubernetes может заменить старые поды новыми постепенно.

Концептуально, получается что-то в духе:

Механизм обновления затронем отдельно, пока что, достаточно запомнить:

Deployment управления rollout update и ReplicaSet.

ReplicaSet поддерживает заданное количество приложений.

Разбираем Deployment дальше

Вернемся к нашему манифесту:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.27

Начало нам уже ясно: создаем Deployment с именем nginx-deployment - хотим получить 3 экземпляра

Но возникает вопрос:

«Три экземпляра чего?»

template

template - шаблон будущего пода.

Обратимся только к нижней части:

template:
  metadata:
    labels:
      app: nginx
  spec:
    containers:
      - name: nginx
        image: nginx:1.27

Template описывает, какие поды необходимо создать. Мысленно, Deployment можно представлять так:

Deployment
replicas: 3
template:
    "вот такой Pod"

Именно поэтому внутри template снова появляются metadata и spec - они относятся не к Deployment, а к внутрилежащим подам.

Что внутри подов?

Внутри template.spec (параметре spec в template) находятся

containers:
  - name: nginx
    image: nginx:1.27

Это можно читать, как:

В каждом созданном Pod должен работать контейнер с именем nginx, созданный на образе nginx:1.27

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

Labels и selectors

Осталось самое неочевидное:

selector:
  matchLabels:
    app: nginx

и

template:
  metadata:
    labels:
      app: nginx

Чтобы понять это, сначала нужно разобраться, что такое labels (метки)

По сути, это обычная пара - Ключ: Значение
К примеру:

app=backend
environment=production
team=payments 

В нашем Deployment каждому созданному поду присваивается:

labels:
  app: nginx

Поэтому получается:

Pod A [app=nginx]
Pod B [app=nginx]
Pod C [app=nginx]

Теперь Kubernetes требуется способ для того, чтобы выбрать объекты с определенной меткой:

Для этого нужен selector:

selector:
  matchLabels:
    app: nginx

Прочитать его можно так:

Выбрать pods с меткой nginx

Поэтому в Deployment две части связаны:

selector:
  matchLabels:
    app: nginx

и

template:
  metadata:
    labels:
      app: nginx

Удобно представить:

selector: то, какими pods я управляю

labels: какие метки получают создаваемые pods

Для Deployment selector должен соответствовать labels для создаваемых подов.

YAML человеческим языком

Наконец, мы можем прочитать наш манифест по-человечески:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: nginx-deployment
spec:
  replicas: 3
  selector:
    matchLabels:
      app: nginx
  template:
    metadata:
      labels:
        app: nginx
    spec:
      containers:
        - name: nginx
          image: nginx:1.27

Читаем, как обычный текст:

Создай объект типа Deployment с именем nginx-deployment.

Я хочу три replicas.Deployment должен управлять Pods с меткой app=nginx.

Создаваемые Pods должны получать метку app=nginx.

В каждом Pod должен работать контейнер nginx из образа nginx:1.27.

То, что в начале выглядело как набор непонятных ключей, теперь превратилось в простую структуру

Что происходит после kubectl apply?

Сохраним манифест в файл

deployment.yaml

И выполняем:

kubectl apply -f deployment.yaml

Теперь можно соединить материал этой статьи с предыдущей:

Упрощенно, происходит следующее

Сначала kubectl отправляет описание объекта в API Server.

Kubernetes сохраняет новое желаемое состояние.

Controllers замечают появившийся Deployment.

Deployment controller работает над соответствующим ReplicaSet.

ReplicaSet controller обеспечивает нужное количество Pods.

Мы попросили:

replicas: 3

Значит должны появиться 3 пода.

Но они еще не знают, где запускаться (они не назначены ни на одну Node).

Scheduler выбирает соответствующие NodeПредставим:

Pod A → Node 1
Pod B → Node 2
Pod C → Node 2

После этого kubelet на соответствующих Nodes обеспечивает выполнение назначенных Pods через container runtime.

Обратите внимание: в YAML мы нигде не указывали, на какой Node поставить каждый Pod. Мы описали что хотим получить. Kubernetes сам решил, как этого добиться. И здесь декларативная модель наконец соединяется со всей архитектурой из первой части.

Что будет, если удалить Pod?

В конце, давайте разберем сценарий, который хорошо показывает всю суть Kubernetes.

Теперь у нас есть Deployment, который поддерживает 3 запущенных экземпляра приложения.

Pod A, Pod B и Pod C работают исправно, но мы в ручную удаляем Pod C.

Теперь:

Desired: 3
Actual:  2

Через некоторое время Kubernetes сам создаст новый Pod.

Это происходит благодаря тому, что мы прописали

replicas: 3

Теперь система сама старается поддерживать это состояние.

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

Это важный момент.

Мы управляем не судьбой конкретного экземпляра приложения, а желаемым состоянием системы.

Что стоит запомнить?

В голове должны остаться две следующие модели.

Первая:

YAML
 ↓
Desired State
 ↓
Kubernetes
 ↓
Actual State

Мы описываем результат, а Kubernetes сам пытается его поддерживать.

Вторая:

Deployment
    ↓
ReplicaSet
    ↓
Pod
    ↓
Container

Deployment управляет rollout приложения и ReplicaSets. ReplicaSet поддерживает количество Pods. Pod является минимальной deployable-единицей Kubernetes. Container непосредственно содержит работающий процесс приложения.

А Kubernetes manifest обычно начинается с знакомой структуры:

apiVersion: ...
kind: ...
metadata:
  ...
spec:
  ...

Проверьте себя

1. Чем декларативный подход отличается от императивного?

2. Что означает:kind: Deployment

3. Что означает:

spec:
  replicas: 5

4. В чём разница между Deployment, ReplicaSet, Pod и Container?

5. Что описывает template внутри Deployment?

6. Попробуйте прочитать по-человечески:

spec:
  replicas: 3
  selector:
    matchLabels:
      app: backend
  template:
    metadata:
      labels:
        app: backend
    spec:
      containers:
        - name: api
          image: my-api:2.4