Kubernetes II: Декларативное управление Kubernetes. Объекты Kubernetes. Описание в YAML

November 10, 2018
cloud kubernetes kubectl yaml api service deployment create delete apply metadata label selector
Share on:

Подробное описание разработки облачных приложений Cloud Native в целом, и роль в этом Kubernetes, можно узнать в моей новой (бесплатной) книге “Программирование в эпоху облака Cloud”

Предыдущая статья из этой серии - введение в Kubernetes, основные термины, первые развертывания и сервисы.

Примеры для этой статьи можно найти на Github в репозитории ivanporty/cloud-docker-k8s

Декларативное управление Kubernetes. Объекты Kubernetes. Описание в YAML

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

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

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

Kubernetes не стал бы столь популярным без модели управления кластером с помощью объектов своего программного интерфейса (API). Желаемое состояние (desired state) кластера Kubernetes в каждый момент описывается набором объектов, являющихся частью программного интерфейса Kubernetes. У каждого из этих объектов есть набор полей, допустимых значений и вложенных объектов, однозначно описывающих, что именно требуется от управляющих компонентов кластера для того, чтобы текущее состояние кластера пришло к желаемому состоянию, описанному объектами.

Когда мы создавали свое первое развертывание, вызывая команду kubectl run, за кулисами для нас создали объект Kubernetes под названием Deployment (развертывание). После этого мы открыли порт для доступа и взаимодействия со своим приложением в контейнере (kubectl expose) - то есть создали сервис, и нетрудно догадаться, эта привело к созданию объекта Service.

Основной способ управления кластером Kubernetes - именно описание объектов его программного интерфейса и последующая работа с ними. Вместо запуска хотя и простых и понятных команд kubectl, гораздо более гибким и легким для последующих изменений способом управления является прямое описание этих объектов. Для описание обычно используется язык YAML, хотя в итоге, перед отправкой на управляющий сервер Kubernetes, он преобразуется в JSON. Файлы формата YAML хранятся в системе контроля версий, зачастую рядом с исходным кодом и данными приложения, и изменение способа развертывания приложения в кластере Kubernetes легко обновлять и отслеживать.

Объект описания развертываний Deployment. Использование kubectl для получения заготовок описания объектов.

Перейти от простого вызова команды kubectl run в одну строчку к описанию всех необходимых полей объекта для развертывания Deployment довольно непросто, тем более что у языка описания YAML есть свои особенности описания полей, списков и вложенных объектов. Здесь к нашей радости поможет все та же самая команда для создания развертывания приложения kubectl run, но используем мы ее с сочетанием пары флагов --dry-run -o yaml, что означает не что иное, как запуск в “холостом режиме” без запроса к кластеру, и вывод созданного объекта Kubernetes на консоль. Формат вывода мы запрашиваем как раз YAML. Итак, развернем сервис time-service из предыдущего раздела в холостом режиме.

$ kubectl run time-service --image=ivanporty/time-service --dry-run -o yaml

Мы легко получаем описание объекта Deployment, которое команда kubectl run отправила бы на сервер программного интерфейса Kubernetes apiserver в случае не холостого, а реального вызова:

apiVersion: apps/v1
kind: Deployment
metadata:
  creationTimestamp: null
  labels:
    run: time-service
  name: time-service
spec:
  replicas: 1
  selector:
    matchLabels:
      run: time-service
  strategy: {}
  template:
    metadata:
      creationTimestamp: null
      labels:
        run: time-service
    spec:
      containers:
      - image: ivanporty/time-service
        name: time-service
        resources: {}
status: {}

Зная теперь, какой объект создается для описания развертывания, мы можем разобрать его подробнее, и затем уже самостоятельно создавать и описывать объекты для своих новых развертываний с нуля. Для начала давайте проигнорируем все поля, которым присвоены пустые значение null, или пустые вложенные объекты ({}) - обычно это означает, что для них используются значения по умолчанию.

Итак, у каждого объекта Kubernetes, описанного в формате YAML, есть два обязательных поля, идущих самыми первыми в описании, а также описание вспомогательной информации об объекте, так называемые метаданные, в виде дополнительного вложенного объекта (metadata):

Описанные два поля и объект с мета-данными будут использоваться для описания объектов Kubernetes любого типа.

Основным объектом, описывающим развертывание Deployment, будет его спецификация DeploymentSpec, в поле под названием spec. Давайте кратко разберем поля этого вложенного объекта:

Итак, у нас теперь есть полное понимание того, как описывать развертывание в терминах объекта Deployment, хранить его в формате YAML, и производить любые изменения именно с помощью изменений данного объекта.

Давайте уберем лишние поля, и вместо метки run, которая используется командой kubectl run, используем свою собственную метку, чтобы обозначить свое развертывание. Пусть это будет просто описание приложения app[lication], довольно популярная метка для простых развертываний.

Вот что у нас получится за объект:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: time-service
  name: time-service
spec:
  replicas: 1
  selector:
    matchLabels:
      app: time-service
  template:
    metadata:
      labels:
        app: time-service
    spec:
      containers:
      - image: ivanporty/time-service
        name: time-service

Сохраним его в отдельном файле YAML и назовем k8s-time-service-deploy.yaml. Остается только создать (create) свое развертывание с помощью объекта Kubernetes. Если у вас еще остался запущенным сервис и развертывание time-service из предыдущего раздела, не забудьте их удалить с помощью команд kubectl delete. Передать команде kubectl описание объекта из файла YAML нужно с помощью флага -f.

$ kubectl create -f k8s-time-service-deploy.yaml

Убедиться в том, что развертывание создано, можно с помощью уже хорошо знакомых команд:

$ kubectl get deployments
NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
time-service   1         1         1            1           9m

Как видно, описание и понимание полей объекта Kubernetes для развертывания Deployment требует некоторого времени, но это усилие тем больше оправдает себя, чем сложнее и более распределена по небольшим микросервисам будет ваша система и приложение в целом. Если развернуть и обновлять весь процесс развертывания, имена отсеков, и масштабирование для монолитного веб-приложения или приложения из двух-трех сервисов простыми вызовами команд kubectl run еще представляется возможным, то более сложные системы гораздо проще описывать с помощью множества объектов в легко доступном для понимания и изменения формате YAML.

Объект для описания сервисов Kubernetes - Service

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

Вновь воспользуемся любезно предоставленной нам командой kubectl возможностью получить заготовку объекта Kubernetes, выполнив уже знакомую нам по прошлому разделу команду expose для открытия портов и создания сервиса, но в “холостом” режиме:

$ kubectl expose deployment time-service --port=8080 --type=NodePort --dry-run -o yaml

Вот что у нас получится в результате. Структура объекта будет очень похожа на развертывание Deployment и для краткости здесь удалены лишние поля, не имеющие значений или использующие значения по умолчанию:

apiVersion: v1
kind: Service
metadata:
  labels:
    run: time-service
  name: time-service
spec:
  ports:
  - port: 8080
    protocol: TCP
    targetPort: 8080
  selector:
    run: time-service
  type: NodePort

Поля apiVersion, kind, и metadata будут совершенно аналогичны тем, что мы только что описали для объектов развертывания Deployment, только версия API используется без подраздела apps (это означает что объекты-сервисы находятся в корневом разделе core, и его можно не указывать явно), тип объекта будет Service, а что касается метаданных объекта, то мы видим что команда expose использует тот же самый набор меток, что был использован и командой kubectl run, что логично, потому что они должны правильно взаимодействовать друг с другом. Имя также совпадает, и это разрешено, так как сервис и развертывание являются объектами разного типа, а имя должно быть уникально для объектов одного типа.

Основным же полем будет спецификация сервиса ServiceSpec, описываемая полем spec:

Поменяв метку по умолчанию на свою метку app, и убрав некоторые лишние поля, имеющие хорошие значения по умолчанию, создадим файл YAML с описанием своего сервиса:

apiVersion: v1
kind: Service
metadata:
  labels:
    app: time-service
  name: time-service
spec:
  ports:
  - port: 8080
  selector:
    app: time-service
  type: NodePort

Назовем файл по аналогии с развертыванием, намекнув в его названии что он содержим описание сервиса: k8s-time-svc.yaml. Создадим новый сервис (если у вас запущен предыдущий вариант сервиса, лучше будет его удалить):

$ kubectl create -f k8s-time-svc.yaml

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

$ kubectl get services
NAME           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes     ClusterIP   10.96.0.1      <none>        443/TCP          17d
time-service   NodePort    10.98.92.168   <none>        8080:30721/TCP   5s

В прошлом разделе мы посмотрели, как в локальных кластерах Minikube или Docker обратиться к сервисам типа NodePort, это будет хорошее повторение материала.

Если вы пользуетесь публичным полноценным кластером, таким как Google Cloud Kubernetes Engine, вы сможете получить доступ к сервису из Интернета, поменяв в описании объекта тип сервиса на LoadBalancer, а затем получив его открытый IP-адрес из описания сервиса, полученного такой же командой kubectl get services.

Подробное описание деталей всех объектов Kubernetes

Подробное описание полей и вложенных объектов для всех объектов Kubernetes можно найти в документации программного интерфейса Kubernetes, https://kubernetes.io/docs/reference/generated/kubernetes-api/v[x.xx] где после последнего символа v надо подставить версию интерфейса. Версии меняются и обновляются довольно часто, появляются новые объекты Kubernetes, меняются и переименуются старые. Прежние версии программных интерфейсов API все равно поддерживаются и ваши объекты будут приниматься к обработке, но всегда следует отслеживать рекомендованный способ работы с кластером Kubernetes, и следовать передовой практике в новых версиях, учитывая насколько динамично развивается вся система Kubernetes.

Использование kubectl get для получения деталей всех объектов кластера

Для начала работы с кластером Kubernetes и развертывания приложений основными объектами являются только что рассмотренные нами Deployment и Service. Именно с их помощью мы сможем быстро перенести любые сервисы и даже монолитные приложения в контейнеры, находящиеся в отсеках (pods) под управлением Kubernetes. Как мы уже знаем, получить информацию о развертывании и сервисах нам помогала команда kubectl get. Она же работает и для получения информации абсолютно обо всех объектах управляющей системы Kubernetes.

Чтобы получить список поддерживаемых объектов программного интерфейса API вашего кластера Kubernetes, удобно воспользоваться встроенной справочной командой kubectl api-resources (удостоверьтесь, что вы используете максимально близкую к последней версию команды, чтобы получить наиболее точную информацию):

$ kubectl api-resources
NAME		SHORTNAMES		KIND
configmaps	cm		         ConfigMap
endpoints	ep                             Endpoints
events		ev                             Event
limitranges	limits                         LimitRange
namespaces	ns                            Namespace
nodes		no                            Node
pods		po                            Pod
services	svc                          Service
daemonsets	ds                            DaemonSet
deployments	deploy                     Deployment
cronjobs	batch                       CronJob
jobs		batch                       Job
ingresses	ing                           Ingress

Вывод команды немного больше, чем показано выше, но самые основные объекты системы Kubernetes показаны. Как видно у них есть имя (первый столбец, если вы получаете список всех объектов, его пишут во множественном числе), удобное сокращение для ускорения набора команд, а также представлен сам тип объекта, которые найдет команда kubectl get по данному имени или краткому имени.

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

Пример команд с сокращениями, в случае если используется тестовый кластер Kubernetes от инструментов Docker:

$ kubectl get no
NAME                 STATUS    ROLES     AGE       VERSION
docker-for-desktop   Ready     master    22d       v1.10.3
$ kubectl get po
NAME                            READY     STATUS    RESTARTS   AGE
time-service-77d9656579-7wm6h   1/1       Running   0          5d
$ kubectl get svc
NAME           TYPE        CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
kubernetes     ClusterIP   10.96.0.1      <none>        443/TCP          22d
time-service   NodePort    10.98.92.168   <none>        8080:30721/TCP   5d

Как видно, мы снова получили исчерпывающую информацию о нашем кластере, узлах, отсеках и сервисе. Интересный вариант команды get - получение информации об объектах непосредственно в формате YAML (или JSON, если вам понадобится этот формат):

$ kubectl get deploy time-service -o yaml

$ kubectl get svc time-service -o yaml

Если вы запустите этот вариант, вы сможете увидеть наши объекты для сервиса и развертывания time-service, с чуть большими подробностями, чем указывали мы, вместе со всеми полями и значениями по умолчанию, добавленными управляющим сервером Kubernetes. Эта команда может быть особенно полезна при проверке того, в каком статусе находится объект кластера в данный момент, и насколько он отличается от того, что мы ожидаем от него, или от последнего файла YAML, использованного для его создания.

Прямое управление объектами Kubernetes - create и delete

После описания объектов Kubernetes в формате YAML остается их создать (kubectl create -f), а потом удалить (kubectl delete -f). Подобная работа с объектами в программировании называется императивной (imperative), или процедурной - мы напрямую создаем объекты и управляем их жизненным циклом в “приказном порядке”, при необходимости обновляем, в конце концов удаляем, и затем создаем заново с обновленными настройками. Именно так мы пока и поступали с созданными нами развертыванием и сервисом для своего приложения time-service. Система лишь исполняет наши инструкции и в управлении непосредственно объектами не участвует - она лишь использует их для определения, каким должно быть “желаемое состояние” (desired state) нашего кластера.

Интересно (и удобно), что инструмент kubectl поддерживает создание и удаление объектов не только из одного файла (а в одном файле могут содержаться множество объектов разного типа, например развертывание и сервисы вместе), а множества файлов YAML, находящихся в директории. Учитывая, что мы поместили наши декларации Kubernetes (два файла с объектами развертывания и сервиса) в директорию k8s, создать и удалить все необходимое для микросервиса time-service можно и так:

$ kubectl delete -f k8s/
deployment.apps "time-service" deleted
service "time-service" deleted

$ kubectl create -f k8s/
deployment.apps "time-service" created
service "time-service" created

Если после предыдущих примеров развертывание и сервис уже существуют, сначала удалите их, а затем попробуйте эти команды. Для множества микросервисов вероятно появление и множества деклараций с объектами кластера Kubernetes, и такая команда становится весьма кстати, если вы развертываете все сервисы разом.

Что же дальше? Управлять созданием и удалением объектов кластера и менять таким образом его желаемое состояние понятно и прямолинейно. Однако дополнительные преимущества Kubernetes использовать будет непросто - например, что если вы автоматически масштабируете приложение, или хотите обновить контейнеры только на части отсеков (pods), или меняете версию контейнера только для одного отсека? Удалять и создавать под-множества объектов отдельными командами будет неудобно, также как очень тяжело отследить историю внесенных изменений. Для больших кластеров, сложных приложений и сервисов, почти всегда применяется декларативное описание.

Декларативное описание объектов кластера Kubernetes. Команда apply.

Декларативное описание объектов Kubernetes не требует от нас прямого вмешательства в дела кластера и оставляет управление объектами, включая их создание и удаление, в руках системы управления кластером. Девиз декларативного описания - неважно в каком порядке, в течение какого времени и как часто система получает описание объектов кластера. Управление кластера обязуется преобразовать простое, понятное и легко отслеживаемое описание объекта в желаемое состояние кластера. Если мы передали системе объект с развертыванием Deployment, и такого развертывания в кластере еще нет, он будет создан, как если бы была вызвана команда create. Если мы передали обновленный сервис Service, в котором например добавили еще один порт, система динамически обновит соответствующий объект (или же удалит и создаст заново), и приведет кластер в его желаемое состояние.

Получить описание объектов и сделать на их основе необходимые действия позвляет команда kubectl apply. Учитывая, что основы объектов развертывания и сервисов мы уже знаем, и создали файлы YAML с их описанием, передача объектов в управление кластера теперь происходит в одну строчку

$ kubectl apply -f k8s/
deployment.apps "time-service" created
service "time-service" created

Как мы видим, при отсутствии объектов в системе они будут созданы. Если бы они там уже существовали, изменений в системе не было бы.

Предположим, что мы получили первых пользователей для микросервиса и одного экземпляра сервиса нам мало, а использовать автоматическое масштабирование мы пока не хотим. Отредактируем объект развертывания и укажем, что нам требуется две копии (replicas) контейнера при развертывании:

apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: time-service
  name: time-service
spec:
  replicas: 2
  selector:
    matchLabels:
      app: time-service
  template:
    metadata:
      labels:
        app: time-service
    spec:
      containers:
      - image: ivanporty/time-service
        name: time-service

Декларативное описание позволит не волноваться о том, удалено ли старое развертывание и проверять, в скольких экземпляров оно находится сейчас (а может быть, оно вообще еще не создано). Передадим объекты в управление кластера и получим свое желаемое состояние без дополнительных усилий:

$ kubectl apply -f k8s/
deployment.apps "time-service" configured
service "time-service" unchanged

Как мы видим, изменений в сервисе не было, а вот конфигурация развертывания была изменена - причем команда apply описывает, что именно было сделано с объектами - создание, удаление или конфигурация.

Проверим, в каком состоянии и в скольких экземплярах работает развертывание:

$ kubectl get deploy
NAME           DESIRED   CURRENT   UP-TO-DATE   AVAILABLE   AGE
time-service   2         2         2            2           2m

Состояние кластера верно. Декларативное описание развертываний и сервисов с помощью объектов Kubernetes помогло нам достичь своего желаемого состояния без дополнительных проверок командой get, создания и удаления объектов, масштабирования scale, и множества других “ручных” команд. Если мы управляем кластером исключительно декларативным способом, то в случае отсутствия ошибок всегда можем быть уверены в том, что его состояние совпадает с тем, что описано нашими объектами Kubernetes. Управление сложными развертываниями, их обновлениями, и масштабированием становится намного проще.

comments powered by Disqus