Kubernetes - artemovsergey/ASP GitHub Wiki

Практическая организация микрослужб с помощью Kubernetes

Эта глава посвящена одному из основных компонентов приложений микрослужб: оркестраторам! Основное внимание уделяется Kubernetes, но концепции, изученные здесь, являются основополагающими для понимания других вариантов оркестрации. В частности, Azure Container Apps — это бессерверная альтернатива Kubernetes, реализованная с помощью самого Kubernetes и использующая упрощенные параметры конфигурации, но объекты для настройки и используемые концепции остаются точно такими же. Azure Container Apps описан в главе 9 «Упрощение контейнеров и Kubernetes: Azure Container Apps и другие инструменты».

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

Альтернативой для тестирования приложения микрослужб на этапе разработки, специфичной для .NET, является .NET Aspire, который будет описан в главе 12 «Упрощение микрослужб с помощью .NET Aspire».

В частности, в этой главе рассматриваются следующие темы:

  • Введение в оркестраторы и их настройку.
  • Основы Kubernetes.
  • Взаимодействие с Kubernetes: Kubectl и Minikube.
  • Настройка приложения в Kubernetes.
  • Запуск микрослужб в Kubernetes.
  • Расширенная настройка Kubernetes.

Minikube: Самый простой способ установить Minikube — использовать установщик для Windows, который можно найти на официальной странице установки: https://minikube.sigs.k8s.io/docs/start/. Во время установки вам будет предложено выбрать тип инструмента виртуализации — укажите Docker. По предыдущей ссылке также приведена команда PowerShell для добавления minicube.exe в путь Windows.

Kubectl: Прежде всего, проверьте, установлен ли он, открыв консоль Windows и выполнив следующую команду: Kubectl -h. Если в ответ вы получите список всех команд Kubectl, значит, он уже установлен. В противном случае, самый простой способ установить его — через установщик пакетов Chocolatey : choco install kubernetes-cli

Если Chocolatey еще не установлен, вы можете установить его, запустив PowerShell в административном режиме, а затем выполнив команду PowerShell, предложенную на официальной странице Chocolatey: https://chocolatey.org/install#individual. Вы можете запустить PowerShell в административном режиме следующим образом: a. Найдите PowerShell в поле поиска Windows. b. Щелкните правой кнопкой мыши ссылку PowerShell и выберите выполнить ее от имени администратора.

Введение в оркестраторы и их настройку

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

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

  1. Прием высокоуровневых спецификаций и их преобразование в фактическое распределение реплик микросервисов на разных серверах данного кластера.

  2. Предоставление уникального виртуального адреса для всех реплик одних и тех же микросервисов и автоматическое распределение трафика между ними. Таким образом, код каждого микросервиса может ссылаться только на этот уникальный виртуальный адрес, не заботясь о том, где находится каждая реплика.

  3. Распознавание неисправных реплик, их удаление и замена на вновь созданные реплики.

  4. Загрузка образов контейнеров микросервисов из реестров контейнеров.

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

  1. Дисковое хранилище

  2. Переменные среды

  3. Порты связи

На самом деле, каждый оркестратор также предлагает другие услуги, но семь услуг, перечисленных выше, являются отправной точкой для изучения и оценки любого оркестратора. Поведение оркестратора контролируется с помощью древовидных настроек, поступающих из различных источников: конфигурационных файлов, аргументов команд и т. д. За кулисами все источники упаковываются клиентом, который взаимодействует с веб-API оркестратора.

Все возможные настройки оркестратора организованы как настройки конфигурации .NET в древовидной структуре данных. Поэтому, аналогично настройкам .NET, они могут быть предоставлены в формате JSON или других эквивалентных форматах. Фактически, все оркестраторы принимают настройки либо в формате JSON, либо в другом эквивалентном формате, называемом .yaml. Некоторые оркестраторы принимают оба формата, другие могут принимать только один из них. Формат .yaml описан в следующем подразделе.

Файлы .yaml

Файлы .yaml, как и файлы JSON, могут использоваться для описания вложенных объектов и коллекций в удобочитаемом для человека виде, но они используют другой синтаксис. В них есть объекты и списки, но свойства объектов не заключаются в {}, а списки не заключаются в []. Вместо этого вложенные объекты объявляются простым отступом их содержимого с помощью пробелов. Количество пробелов можно выбирать свободно, но однажды выбрав их, их необходимо использовать последовательно. Элементы списка можно отличить от свойств объекта, поместив перед ними дефис (-). Ниже приведен пример с вложенными объектами и коллекциями:

Name: John
Surname: Smith
Spouse:
 Name: Mary
 Surname: Smith
Addresses:
- Type: home
 Country: England # I am a comment
 Town: London
 Street: My home street
- Type: office
 Country: England
 Town: London
 Street: My home street

В каждой строке все символы, следующие за символом #, считаются комментариями. Предыдущий объект Person имеет вложенный объект Spouse и вложенную коллекцию адресов. Тот же пример в JSON будет выглядеть следующим образом:

{
Name: John
Surname: Smith
Spouse:
{
 Name: Mary
 Surname: Smith
}
Addresses:
[
 {
 Type: home
 Country: England
 Town: London
 Street: My home street
 },
 {
 Type: office
 Country: England
 Town: London
 Street: My home street
 }
]
}

Как видите, синтаксис .yaml более читаем, поскольку в нем нет лишних скобок. Файлы .yaml могут содержать несколько разделов, каждый из которых определяет отдельный объект. Разделы разделены строкой, содержащей строку ---. Комментарии предваряются символом #, который должен повторяться в каждой строке комментария.

Поскольку пробелы/табуляции влияют на семантику объектов, YAML чувствителен к пробелам/табуляциям, поэтому необходимо обращать внимание на добавление правильного количества пробелов.

Небольшие коллекции или небольшие объекты также можно указывать в строке с помощью обычного синтаксиса [] и {}, то есть после двоеточия в той же строке свойства, значением которого они являются.

Изучив основы оркестраторов и файлов .yaml, мы готовы перейти к изучению самого распространенного оркестратора: Kubernetes. На данный момент он также является самым полным. Поэтому, после его изучения, изучение других оркестраторов должно быть очень простым.

Основы Kubernetes

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

Фактически, все узлы также должны запускать среду выполнения контейнеров, чтобы иметь возможность запускать контейнеры. Как мы увидим позже, все узлы также запускают программное обеспечение, которое обрабатывает виртуальную адресацию. Единицы конфигурации Kubernetes — это абстрактные объекты со свойствами, подкомпонентами и ссылками на другие объекты. Их называют ресурсами Kubernetes. У нас есть ресурсы, которые описывают одну копию микрослужбы, и другие ресурсы, которые описывают набор копий. Ресурсы описывают настройки связи, дисковое хранилище, пользователей, роли и различные виды ограничений безопасности.

Узлы кластера и все ресурсы, которые они размещают, управляются главными узлами, которые взаимодействуют с администраторами кластера через сервер API, как показано на следующей схеме:

image Figure 8.1: Kubernetes cluster

Kubectl — это клиент, который обычно используется для отправки команд и данных конфигурации на сервер API. Планировщик распределяет ресурсы между узлами в соответствии с ограничениями, установленными администратором, а менеджер контроллеров группирует несколько deamons, которые отслеживают фактическое состояние кластера и пытаются перевести его в желаемое состояние, заданное через сервер API. Существуют контроллеры для нескольких ресурсов Kubernetes, от реплик микросервисов до средств связи. Фактически, каждый ресурс имеет определенные целевые показатели, которые должны поддерживаться во время работы приложения, и контроллер проверяет, что эти показатели действительно достигаются, при необходимости запуская корректирующие действия, такие как перемещение некоторых слишком медленно работающих под-программ на менее загруженные узлы.

Развертываемая единица, то есть единица, которая может быть развернута на сервере, запущена, завершена и/или перемещена на другой сервер, представляет собой не один контейнер, а набор контейнеров, называемый Pod.

Pod — это набор контейнеров, которые должны выполняться все вместе на одном сервере.

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

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

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

В Kubernetes обмен данными между Pods осуществляется с помощью ресурсов, называемых Services, которым инфраструктура Kubernetes присваивает виртуальные адреса и которые перенаправляют их сообщения на наборы Pods, удовлетворяющие определенным ограничениям. Короче говоря, Services — это способ Kubernetes назначать постоянные виртуальные адреса наборам Pods. Всем ресурсам Kubernetes могут быть назначены пары «имя-значение», называемые метками, которые используются для ссылки на них через механизм сопоставления шаблонов. Так, например, все Pods, которые получают трафик от одного и того же Service, выбираются путем указания меток, которые они должны иметь в определении Service.

Кластеры Kubernetes могут быть локальными, то есть Kubernetes может быть установлен в любой частной сети. Но чаще всего они предлагаются в качестве облачных сервисов. Например, Azure предлагает Azure Kubernetes Service (AKS).

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

Начнем с взаимодействия с кластером Kubernetes.

Взаимодействие с Kubernetes: Kubectl, Minikube и

AKS

Прежде чем взаимодействовать с кластером Kubernetes с помощью клиента Kubectl, необходимо настроить Kubectl и предоставить ему URL-адрес кластера и необходимые учетные данные.

После установки Kubectl создает отдельный файл конфигурации JSON для каждого пользователя компьютера, который будет содержать информацию о конфигурации всех кластеров Kubernetes и их пользователей. Kubectl имеет команды для вставки новых конфигураций кластера Kubernetes и для установки конфигурации кластера в качестве текущей.

Каждая пара, состоящая из URL API кластера Kubernetes и учетных данных пользователя, называется контекстом. Контексты, учетные данные и подключения к кластерам можно определить с помощью различных подкоманд kubectl config.

Ниже приведены наиболее полезные из них:

  • Просмотр общего файла конфигурации: kubectl config view

  • Добавление нового кластера Kubernetes: kubectl config set-cluster my-cluster --server=https://<URL API-сервера вашего кластера>

  • Учетные данные пользователя основаны на клиентских сертификатах. Действительный сертификат можно получить, создав запрос на сертификат и отправив его в кластер Kubernetes, который создаст утвержденный сертификат. Подробная процедура будет показана в главе 10 «Безопасность и наблюдаемость для бессерверных приложений и микросервисов». После получения утвержденного сертификата пользователь может быть создан с помощью:

Kubectl config set-credentials newusername --client-key= newusername.key --client-certificate=poweruser.crt --embedcerts=true где newusername.key — полный путь к закрытому ключу, который вы использовали для создания запроса на сертификат, а newusername.crt — полный путь к файлу утвержденного сертификата.

  • После того, как у вас есть и сервер, и пользователь, вы можете создать контекст для подключения этого пользователя к этому серверу с помощью: kubectl config set-context newcontext --cluster= my-cluster --user= newusername

  • Once all the contexts you need have been properly defined, you can switch to a given context with: kubectl config use-context newcontext

  • After having set a new current context, all Kubectl commands will use both the cluster and the user defined in that context.

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

Minikube поставляется с пользователем по умолчанию, именем кластера по умолчанию и контекстом по умолчанию, которые все называются minikube. Когда вы запускаете кластер Minikube с помощью minikube start, если они еще не определены, все вышеуказанные объекты будут добавлены в ваш файл конфигурации Kubectl. Кроме того, контекст minikube будет автоматически установлен в качестве текущего, поэтому после запуска кластера не требуется никаких дополнительных действий. Конечно, вы можете определить других пользователей и другие контексты. Minikube можно остановить с помощью minikube stop и приостановить с помощью minikube pause. Остановка и приостановка не удаляют данные и конфигурацию кластера. Другие полезные команды будут показаны позже при использовании Minikube в наших примерах. Давайте попробуем некоторые команды Kubectl на Minikube (убедитесь, что Minikube запущен):

kubectl get nodes

Должны отобразиться все виртуальные сетевые узлы Kubernetes. По умолчанию Minikube создает кластер с одним узлом под названием minikube, поэтому вы должны увидеть примерно следующее:

NAME STATUS ROLES AGE VERSION
minikube Ready control-plane,master 35m v1.22.3

Поскольку мы указали Docker в качестве инструмента виртуализации, весь кластер будет встроен в контейнер Docker, что можно проверить, перечислив все запущенные контейнеры с помощью команды docker ps (помните, что все команды Docker должны вводиться в оболочке Linux).

По умолчанию этот уникальный узел содержит 2 процессора и 4 гигабайта оперативной памяти, но мы можем изменить все эти параметры, а также создать кластеры с несколькими узлами, передавая некоторые опции команде minikube start:

  • --nodes : указывает количество узлов в кластере. Учтите, что узлы — это виртуальные машины, которые будут работать одновременно, поэтому большое количество узлов можно установить только на мощной рабочей станции с несколькими ядрами и, скажем, 32–64 гигабайтами оперативной памяти. По умолчанию — 1.
  • --cpus <n или no-limits>: количество процессоров, выделенных для Kubernetes, или no-limits, чтобы Minikube мог выделить столько процессоров, сколько необходимо. По умолчанию — 2.
  • --memory <строка>: объем оперативной памяти, выделяемый Kubernetes (формат: <число>[<единица>], где единица = b, k, m или g). Используйте «max», чтобы использовать максимальный объем памяти. Используйте «no-limit», чтобы не указывать ограничение.
  • --profile <строка>: имя виртуальной машины Minikube (по умолчанию minikube). Полезно, если у вас более одной виртуальной машины Minikube — например, одна с одним узлом, а другая с двумя узлами.
  • --disk-size <строка>: размер диска, выделенный для виртуальной машины Minikube (формат: <число>[<единица>], где единица = b, k, m или g). По умолчанию — «20000mb».

Если вы хотите изменить один из вышеуказанных параметров после создания контейнера Minikube при первом запуске minikube, вам необходимо либо удалить предыдущий контейнер с помощью minikube delete, либо создать новый контейнер Minikube с пользовательским именем с помощью опции --profile.

После этого небольшого отступления вернёмся к Kubectl! Напечатаем:

kubectl get all

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

image

Он является частью инфраструктуры Kubernetes. В общем случае, kubectl get <тип ресурса> выводит список всех ресурсов данного типа. Так, например, kubectl get pods выводит список всех Pods, а kubectl get services — список всех сервисов. Если же нам нужна более подробная информация о данном объекте, мы можем использовать команду kubectl describe <тип объекта> <имя объекта>. Так, например, если нам нужна дополнительная информация о единственном узле Minikube под названием minikube, мы можем выполнить следующую команду:

kubectl describe node minikube

Попробуйте! Вы увидите другие команды Kubectl, когда будете изучать, как определять Pods, Services и другие ресурсы Kubernetes в других разделах этой главы.

Настройка приложения в Kubernetes

Как уже упоминалось, самым простым ресурсом Kubernetes является Pod. Мы никогда не будем создавать отдельный Pod, поскольку всегда будем создавать несколько реплик каждого микросервиса, но умение настраивать Pod также имеет основополагающее значение для создания более сложных ресурсов, поэтому давайте начнем с создания отдельного Pod. Pod можно определить с помощью файла .yaml со следующим содержанием:

apiVersion: v1
kind: Pod
metadata:
name: my-podname
 namespace: mypodnamespace
 labels:
 labenname1: labelvalue1
 labelname2: labelvalue2
spec:
 restartPolicy: Always #Optional. Possible values: Always (default),
OnFailure. Never.
 containers:
 
 initContainers:
 

Все конфигурационные файлы Kubernetes начинаются с имени API, в котором определены настраиваемые ресурсы, и его версии. В случае с Pods у нас есть только версия, поскольку они определены в основном API. Затем kind определяет тип настраиваемого ресурса — в нашем случае это Pod. Как и типы в C#, ресурсы Kubernetes также организованы в пространствах имен. Поэтому вместе с любым именем ресурса мы также должны указать пространство имен. Если пространство имен не указано, предполагается пространство имен с именем default.

Обратите внимание! Хотя назначение пространств имен Kubernetes и C# одинаково, между ними есть существенные различия. А именно, пространства имен C# являются иерархическими, а пространства имен Kubernetes — нет. Кроме того, пространства имен применимы не ко всем ресурсам Kubernetes, поскольку существуют ресурсы кластера, которые не принадлежат ни к одному конкретному пространству имен.

Если пространство имен, используемое в определении ресурса, еще не существует, его необходимо определить с помощью нижеприведенного фрагмента кода:

apiVersion: v1
kind: Namespace
metadata:
 name: my-namespace

Вышеуказанный фрагмент кода можно поместить в отдельный файл или в тот же файл перед определением ресурса и отделить строкой ---.

Имя и пространство имен указываются как подсвойства метаданных вместе с дополнительными метками. Метки — это свободные пары «имя-значение», которые можно использовать для классификации объекта. Обычно они указывают такую информацию, как роль ресурса в приложении и уровень или модуль, к которому он принадлежит. Как уже упоминалось в предыдущем разделе, другие ресурсы могут использовать метки для выбора набора ресурсов. Свойство spec определяет фактическое содержимое Pod, то есть его контейнеры и политику перезапуска (restartPolicy). Политика перезапуска определяет, когда перезапускать Pod:

  • restartPolicy: Always: это значение по умолчанию. Pod перезапускается, когда все контейнеры завершают работу или один из контейнеров завершает работу с ошибкой.
  • restartPolicy: OnFailure: Pod перезапускается, когда хотя бы один контейнер завершается с ошибкой
  • restartPolicy: Never: Pod никогда не перезапускается. Контейнеры разделены на два списка: containers и initContainers. Контейнеры в списке containers запускаются только после успешного запуска всех контейнеров в списке initContainers, а каждый контейнер в списке initContainers запускается только после успешного запуска предыдущего контейнера. В свою очередь, контейнер в списке initContainers считается успешным в двух случаях:
  1. Если в конфигурации контейнера свойство restartPolicy установлено в значение Always, то контейнер считается успешным, если он был успешно запущен. Этот параметр полезен для реализации контейнеров-сайдкаров. Таким образом, мы гарантируем, что сайдкары будут готовы до того, как будут запущены контейнеры, которые они улучшают. Описание того, что такое сайдкары, см. в определении Pod в начале раздела «Основы Kubernetes».
  2. Если в конфигурации контейнера свойство restartPolicy не установлено в значение Always, то контейнер считается успешным, если он успешно завершен. Этот вариант полезен для выполнения некоторых начальных действий при запуске, например, для ожидания готовности базы данных или брокера сообщений. В подобной ситуации код контейнера представляет собой цикл, который непрерывно пытается установить соединение с базой данных/брокером сообщений и завершается, как только это удается.

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

Каждый контейнер в любом из двух вышеуказанных списков выглядит примерно так:

- name: <container name>
 image: <container image URL>
 command: […] # square bracket contains all strings that compose the OS
command
 resources:
 requests:
 cpu: 100m
 memory: 128Mi
 limits:
 cpu: 250m
 memory: 256Mi
 ports:
 - containerPort: 80
 - containerPort: 
 
 env:
 -name: env-name1
 value: env-value1
 
 volumeMounts:
 - name: data
 mountPath: /mypath/mysubpath….
 subPath: /vsubpath #optional. If provided the path of data mounted
in mountPath
 

Мы указываем как имя контейнера, так и URL его образа в реестре контейнеров, что соответствует пункту 4 минимального набора услуг, которые должен предоставлять любой оркестратор (см. начало раздела «Введение в оркестраторы и их настройку»). Эти два свойства являются обязательными, а все остальные — опциональными. Свойство command, если оно указано, перезаписывает инструкцию CMD образа Docker-файла.

Затем мы также учитываем пункты 5, 6 и 7 минимальных услуг, которые должен предлагать любой оркестратор, а именно: дисковое хранилище, переменные среды и порты связи. Более конкретно, у нас есть:

  • volumeMount указывает, как виртуальный том хранилища, указанный по имени, сопоставляется с путем, указанным mountPath в файловой системе контейнера. Если указан необязательный subPath, то монтируется только этот подпуть тома, указанного по имени. Виртуальные тома хранения описаны далее в этой главе (в подразделе «Динамическое предоставление постоянного дискового пространства ») вместе с другими свойствами volumeMounts.
  • env указывает все переменные среды контейнера в виде списка пар «имя-значение».
  • ports указывает список всех портов, открытых контейнером, которые мы хотим использовать в нашем приложении. Эти порты могут быть сопоставлены другим портам в фактической коммуникации между Pods. Однако сопоставление портов указывается в других ресурсах, называемых services, которые предоставляют виртуальные адреса Pod и другие параметры, связанные с коммуникацией. Наконец, раздел resources указывает как минимальные вычислительные ресурсы, необходимые для запуска контейнера (requests), так и максимальные вычислительные ресурсы, которые он может потратить (limits). Ограничения в свойстве requests используются для выбора виртуальной машины, на которой будет размещен Pod. Ограничения limits, напротив, принудительно выполняются ядром операционной системы следующим образом:
  • Ограничения CPU принудительно выполняются с помощью дросселирования. То есть контейнеры, превышающие ограничение CPU, задерживаются, переходя в спящий режим на достаточное время.
  • Ограничения памяти принудительно выполняются путем генерации исключения при их превышении. В свою очередь, исключение вызывает применение политики перезапуска Pod, что обычно приводит к перезапуску Pod. Что касается единиц измерения, типичными единицами измерения памяти являются Ti (терабайты), Gi (гигабайты), Mi (мегабайты) и Ki (килобайты). Время процессора, напротив, может измеряться либо в милликорах (mi), либо в виде дробного числа ядер (без единицы измерения после значения).

Давайте попробуем Pod с контейнером-сайдкаром, который демонстрирует как практическое использование описанного синтаксиса, так и то, как сайдкара может помочь в построении мониторинга на уровне приложения. Основным контейнером будет поддельный микросервис на основе образа Docker дистрибутива Alpine Linux, который просто помещает сообщения журнала в файл, расположенный в каталоге, общий с сайдкаром. В реальном приложении журнал будет организован в виде нескольких файлов (например, по одному на каждый день), а старые файлы будут периодически удаляться. Кроме того, sidecar будет считывать эти файлы и отправлять их содержимое в API журнала. Наш учебный sidecar, напротив, будет просто периодически считывать последние 10 строк файла и отображать их в своей консоли. Код довольно прост. Прежде всего, мы определяем пространство имен, которое охватывает наш пример:

apiVersion: v1
kind: Namespace
metadata:
 name: basic-examples

Затем, после строки ---, мы помещаем фактическое определение Pod:

---
apiVersion: v1
kind: Pod
metadata:
 name: pod-demo
 namespace: basic-examples
 labels:
 app: myapp
spec:
 containers:
 - name: myapp
 image: alpine:latest
 command: ['sh', '-c', 'while true; do echo $(date) >> /opt/logs.txt;
sleep 1; done']
 volumeMounts:
 - name: data
 mountPath: /opt
 initContainers:
 - name: logshipper
 image: alpine:latest
 restartPolicy: Always
command: ['sh', '-c', 'tail -F /opt/logs.txt']
 volumeMounts:
 - name: data
 mountPath: /opt
 volumes:
 - name: data
 emptyDir: {}

Оба контейнера используют простой образ Docker дистрибутива Alpine Linux и ограничивают код, специфичный для приложения, в команде, которая является скриптом Linux. Эта техника используется для адаптации существующих образов или для очень простых задач, таких как те, которые часто выполняются сайдкаром. Мы также использовали ту же технику для основного контейнера, потому что основной контейнер ничего не делает и имеет чисто дидактическую цель. Соответственно, с помощью ранее описанного синтаксиса сайдкара определяется в списке initContaines с restartPolicy: Always. Команда основного контейнера выполняет бесконечный цикл, в котором просто записывает текущую дату и время в файл /opt/logs.txt, а затем переходит в режим ожидания на одну секунду. Команда контейнера sidecar использует sh -c для выполнения одной команды оболочки, команды tail с опцией -f для файла /opt/logs.txt. Эта команда отображает последние 10 строк файла в консоли контейнера и обновляет их при добавлении новых строк, так что консоль всегда содержит последние 10 строк файла. Файл, обрабатываемый обоими контейнерами, одинаков, поскольку оба контейнера монтируют один и тот же том данных в одном и том же каталоге /opt в своих файловых системах с помощью:

volumeMounts:
 - name: data
 mountPath: /opt

Объем данных определяется в списке объемов, который является прямым потомком свойства spec, следующим образом:

- name: data
 emptyDir: {}

emptyDir определяет и выделяет том, который является специфическим для Pod, в котором он определен. Это означает, что к нему не может получить доступ никакой другой Pod. Том реализуется с помощью дисковой памяти узла, на котором размещен Pod. Это означает, что если Pod удаляется или перемещается на другой узел, том уничтожается, а его содержимое теряется. EmptyDir — это предпочтительный способ предоставления временного дискового хранилища, которое каким-либо образом используется в вычислениях Pod. Он имеет опциональное свойство sizeLimit, которое указывает максимальный объем дискового пространства, который может использовать Pod. Например, мы можем установить sizeLimit: 500Mi, чтобы указать 500 мегабайт максимального дискового пространства. Поскольку мы не указали никаких ограничений по размеру, объект emptyDir не имеет свойств, поэтому мы вынуждены добавить пустое значение объекта {} для получения правильного синтаксиса .yaml (мы не можем иметь двоеточие, за которым ничего не следует). Давайте создадим папку для экспериментов с файлами .yaml в Minikube и поместим весь пример кода в файл с именем SimplePOD.yaml в этой папке. Этот файл также доступен в папке ch08 репозитория GitHub книги. Теперь щелкните правой кнопкой мыши по вновь созданной папке и откройте консоль Windows в этом каталоге. После проверки, что Minikube запущен, с помощью команды kubectl get all, мы можем применить все наши определения с помощью команды kubectl apply:

kubectl apply -f SimplePOD.yaml

Теперь, если мы выполним команду kubectl get pods, мы не увидим новый Pod! Это правильно, потому что эта команда просто выводит список ресурсов, определенных в пространстве имен по умолчанию, а наш Pod был определен в новом пространстве имен под названием basic-examples, поэтому, если мы хотим работать с ресурсом в этом пространстве имен, мы должны добавить к нашим командам опцию -n basic-examples:

kubectl get pods -n basic-examples

Чтобы получить доступ к консоли sidecar, мы можем использовать команду Kubectl logs. Фактически, весь вывод консоли всех контейнеров Pod автоматически собирается Kubernetes и может быть проверен с помощью этой команды. Команде необходимо имя Pod и его пространство имен, если оно отличается от стандартного. Кроме того, если Pod содержит несколько контейнеров, также необходимо указать имя контейнера, который мы хотим просматривать, с помощью опции -c. Итак, наша команда выглядит следующим образом:

kubectl logs -n basic-examples pod-demo -c logshipper

Вышеуказанная команда отобразит только текущее содержимое консоли, а затем завершит работу. Если мы хотим, чтобы содержимое обновлялось автоматически по мере изменения содержимого консоли, мы должны добавить опцию -f:

kubectl logs -f -n basic-examples pod-demo -c logshipper

Таким образом, наше окно зависает на команде и автоматически обновляется. Команду можно завершить с помощью ctrl-c.

Мы также можем подключить консоль к контейнеру logshipper с помощью команды Kubectl exec. Для этого необходимы имена пространства имен, Pod и контейнера, а после символов – – Linux-команда, которая будет выполняться в файловой системе контейнера. Если вам нужна консоль, Linux-команда будет sh, а если мы хотим взаимодействовать с этой консолью, нам также необходимо указать опции -it, которые означают «интерактивный tty». Подводя итог, мы имеем:

kubectl exec -it -n basic-examples pod-demo -c logshipper -- sh

Оказавшись в контейнере, мы можем перейти в каталог /opt с помощью команды cd /opt и проверить, есть ли там файл logs. txt, с помощью команды ls. По завершении вы можете выйти из консоли контейнера, выполнив команду exit.

Команда kubectl exec очень полезна для отладки приложений, особенно когда они уже находятся в производственной или промежуточной среде.

Когда вы закончите работу со всеми ресурсами, созданными файлом .yaml, вы можете удалить их все с помощью команды kubectl deleted <имя файла>.yaml. Таким образом, в нашем случае мы можем удалить все наши примеры сущностей с помощью команды:

kubectl delete -f SimplePOD.yaml

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

Мы рассмотрели, как создать временное дисковое пространство с помощью emptyDir. Теперь давайте посмотрим на типичный способ выделения постоянного сетевого дискового пространства и его совместного использования различными Pod.

Динамическое предоставление постоянного дискового пространства

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

На самом деле, совместное использование дискового пространства можно также реализовать с помощью определений в дереве, правильно настроив устройство, которое предоставляет дисковое пространство. Например, предположим, что мы используем сервер NFS, подключенный к нашему кластеру Kubernetes, для предоставления сетевого дискового пространства. Мы можем подключить к нему Pod с помощью следующей инструкции:

volumes
- nfs:
 server: my-nfs-server.example.com
 path: /my-nfs-volume
 readOnly: true # optional. If provided the volume is accessible as
read-only

Где server — это имя сервера или IP-адрес, а path — каталог для общего доступа. Чтобы разделить одно и то же дисковое пространство между PodS, достаточно указать один и тот же сервер и путь. Однако у этого метода есть два недостатка:

  • Общий доступ не объявляется явно, а является косвенным, что снижает удобство обслуживания и читаемость кода.
  • Kubernetes не получает информацию о Pods, которые используют общий ресурс, поэтому ему нельзя дать команду освободить общий ресурс, когда он больше не нужен. Поэтому определения в дереве более подходят для временного дискового пространства, которое не используется совместно Pods. К счастью, проблема заключается не в самом протоколе NFS, а только в синтаксисе в дереве. По этой причине Kubernetes также предлагает синтаксис вне дерева, основанный на двух отдельных объектах: Persistent Volume Claims (PVC), которые представляют потребности в дисковом пространстве, и Persistent Volumes (PV), которые представляют фактическое дисковое пространство. Вся техника работает следующим образом:
  1. Мы определяем спецификацию дискового пространства в PVC.
  2. Все поды, которым необходимо совместно использовать одно и то же дисковое пространство, ссылаются на один и тот же PVC.
  3. Kubernetes каким-то образом пытается удовлетворить каждый PVC с помощью совместимого PV, который затем монтируется на всех поды, совместно использующих этот PVC. Когда все поды, которые используют один и тот же PV, уничтожены, мы можем дать Kubernetes команду сохранить выделенное дисковое пространство или удалить его. Способ, которым PVC захватывает необходимое дисковое пространство и возвращает PV, зависит от драйвера, используемого для обслуживания PVC. Драйверы должны быть установлены в кластере Kubernetes, но все облачные провайдеры предоставляют предопределенные драйверы.

Имена драйверов и связанные с ними настройки организованы в ресурсах, называемых классами хранения (тип: StorageClass). Наряду с предопределенными драйверами все поставщики облачных услуг также предлагают предопределенные классы хранения, основанные на этих драйверах. Однако вы можете определить новые классы хранения на основе того же драйвера, но с другими настройками. Вы также можете установить драйверы и классы хранения на основе этих драйверов в локальных кластерах Kubernetes (существует множество драйверов с открытым исходным кодом). Minikube имеет надстройки, которые устанавливают различные драйверы хранилища и связанные с ними классы хранилища. Драйверы, которые просто сопоставляют PVC с PV, которые вручную предопределены пользователем, называются статическими. В то время как драйверы, которые динамически создают ресурсы PV, беря необходимое дисковое пространство из общего пула доступного дискового пространства, называются динамическими. В этом разделе мы сосредоточимся только на динамическом распределении хранилища, поскольку оно наиболее актуально в реальных приложениях микросервисов. Более подробную информацию о классах хранилища и о том, как их определять, можно найти в официальной документации Kubernetes: https://kubernetes.io/docs/concepts/ storage/storage-classes/. Первым шагом при создании PVC является проверка доступных классов хранилища:

Kubectl get storageclasses

Затем с помощью kubectl describe можно получить подробную информацию о конкретном классе. В Minikube мы получаем:

image

«По умолчанию» после имени класса сообщает нам, что стандартный класс является классом хранения по умолчанию, то есть тем, который используется, когда класс хранения не указан. При использовании динамического выделения ресурсов PVC необходимо указать только: • Необходимый объем хранилища • Класс хранения • Режим доступа: ReadWriteOnce (только один узел может читать и записывать в хранилище), ReadOnlyMany (несколько узлов могут читать), ReadWriteMany (несколько узлов могут как читать, так и записывать), ReadWriteOncePod (только один Pod может читать и записывать в хранилище)

Фактически, вся информация, необходимая для получения PV, содержится в классе хранения. Поскольку PVC описывает потребность Pod, а не конкретный PV, выделенное хранилище будет обеспечивать как минимум требуемый режим доступа, но может поддерживать и другие режимы. Если драйвер, используемый классом хранения, не поддерживает требуемый режим, операция завершается сбоем. Поэтому перед использованием класса хранения необходимо проверить операции, поддерживаемые его драйвером. ReadOnlyMany не имеет смысла при динамическом предоставлении, поскольку выделенное хранилище всегда приходит чистым, поэтому читать нечего. На практике драйверы, поддерживающие динамическое предоставление, всегда поддерживают ReadWriteOnce, а некоторые из них также поддерживают ReadWriteMany. Поэтому, если вам нужен том, который будет совместно использоваться несколькими Pod, необходимо убедиться, что выбранный драйвер поддерживает ReadWriteMany; в противном случае все Pod, которые совместно используют том, будут выделены на одном узле, чтобы обеспечить всем из них доступ к запрошенному хранилищу ReadWriteOnce. PVC определяется, как показано ниже:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
 name: myclaim
 namespace: a-namespace
spec:
 accessModes:
 - ReadWriteOnce # ReadWriteOnce, ReadOnlyMany, ReadWriteMany,
ReadWriteOncePod
 resources:
 requests:
 storage: 8Gi
 storageClassName: <my storage classname>

Необходимый объем хранилища указывается с помощью того же синтаксиса, что и объем ОЗУ, требуемый контейнером. Если класс хранилища не указан, Kubernetes использует класс хранилища, который был помечен как класс хранилища по умолчанию, если таковой имеется. После определения PVC свойство volume Pod должно ссылаться на него:

volumes:
- name: myvolume
 persistentVolumeClaim:
 claimNam

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

ReplicaSets, Deployments и их службы

ReplicaSets — это ресурсы, которые автоматически создают N реплик Pod. Однако они редко используются, поскольку более удобно использовать Deployments, которые построены на основе ReplicaSets и автоматически обеспечивают плавный переход при изменении количества реплик или других параметров.

Определение Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
 name: my-deployment-name
 namespace: my-namespace
 labels:
 app: my-app
spec:
 replicas: 3
 selector:
 matchLabels:
 my-pod-label-name: my-pod-label-value
 ...
 templ

Развертывания не содержатся в ядре API, поэтому необходимо указать их имя API (apps). Раздел метаданных идентичен разделу Pod. Раздел spec содержит желаемое количество реплик (replicas) и селектор, который определяет условие, при котором Pod будет принадлежать к развертыванию: он должен иметь все метки с указанными значениями. Шаблон определяет, как создавать Pod для развертывания. Если кластер уже содержит некоторые Pod, которые удовлетворяют условиям селектора, то шаблон используется для создания только тех Pod, которые необходимы для достижения целевого числа реплик.

Шаблон представляет собой полное определение Pod, синтаксис которого идентичен тому, который мы используем для указания отдельного Pod. Единственные различия заключаются в следующем:

  • Определению Pod не предшествует никакая спецификация API
  • Раздел метаданных Pod не содержит имени Pod, поскольку мы предоставляем шаблон для создания реплик Pod. Имена Pod автоматически создаются Deployment.
  • Раздел метаданных Pod не содержит пространство имен Pod, поскольку Pod наследуют то же пространство имен, что и Deployment. Разумеется, шаблон Pod должен указывать метки, соответствующие условиям селектора. Ниже приведен полный пример:
apiVersion: apps/v1
kind: Deployment
metadata:
 name: nginx
 namespace: basic-examples
 labels:
 app: webservers
spec:
 selector:
 matchLabels:
 app: webservers
 replicas: 2
 template:
 metadata:
 labels:
 app: webservers
 spec:
 containers:
 - image: nginx
 name: nginx
 ports:
 - containerPort: 80
 name: web
 volumeMounts:
 - mountPath: /usr/share/nginx/html
 name: website
 volumes
- name: website
 persistentVolumeClaim:
 claimName: web

Развертывание создает две копии веб-сервера nginx, которые используют общее дисковое пространство. Точнее, они используют общий путь /usr/share/nginx/html, который сопоставлен общему PVC. /usr/share/nginx/html — это папка, в которой nginx ищет статический веб-контент, поэтому, если мы поместим туда файл index.html, он будет доступен обоим веб-серверам. Вышеуказанный код реализует два веб-сервера с балансировкой нагрузки, которые обслуживают один и тот же контент. Давайте разместим развертывание в файле WebServers.yaml. Мы будем использовать его через некоторое время, после добавления недостающего кода, то есть определения PVC и службы, которая перенаправляет трафик извне кластера Kubernetes и балансирует его между репликами. Развертывания могут быть подключены к трем типам сервисов:

  • ClusterIP, который перенаправляет трафик изнутри сети на развертывание
  • LoadBalancer, который перенаправляет трафик извне кластера на развертывание
  • NodePort, который не является фундаментальным для разработчиков приложений и не будет описан Определение ClusterIP:
apiVersion: v1
kind: Service
metadata:
 name: my-service
 namespace: my-namespace
spec:
 selector:
 my-selector-label: my-selector-value
 ...
 ports:
 - name: http
 protocol: TCP
 port: 80
 targetPort: 80
 - name: https
 protocol: TCP
 port: 443
 targetPort: 443

Селектор определяет поды, которые будут получать трафик от службы. Поды должны принадлежать к тому же пространству имен, что и служба. Список портов определяет сопоставление внешних портов (port) с портами внутри контейнеров поды (targetPort). Каждое сопоставление может также указывать опциональное имя и опциональный протокол. Если протокол не указан, все протоколы будут перенаправлены в поды. Службе ClusterIP назначается доменное имя <имя службы>.<пространство имен>.svc.cluster.local, но к ней также можно получить доступ с помощью <имя службы>.<пространство имен> (или просто <имя службы>, если пространство имен является пространством по умолчанию). Подводя итог, весь трафик, отправленный либо на <service name>.<namespace>.svc.cluster.local, либо на <service name>.<namespace>, перенаправляется на поды, выбранные селектором. Служба LoadBalancer полностью аналогична, единственное отличие заключается в двух подсвойствах spec ниже:

spec:
 type: LoadBalancer
 loadBalancerIP: <yourpublic ip>
 selector:
 …

Если вы указываете IP-адрес, он должен быть статическим IP-адресом, который вы приобрели каким-либо образом; в противном случае, в случае облачных кластеров Kubernetes, вы можете опустить свойство loadBalancerIP, и инфраструктура автоматически присвоит службе динамический IP-адрес. В AKS вы также должны указать группу ресурсов, в которой был выделен IP-адрес, в аннотации:

apiVersion: v1
kind: Service
metadata:
 annotations:
 service.beta.kubernetes.io/azure-load-balancer-resource-group: <IP
resource group name>

Кроме того, вы должны присвоить роль «Сетевой участник» в группе ресурсов, где вы определили статический IP-адрес, управляемой идентичности, связанной с кластером AKS (по умолчанию управляемая идентичность автоматически назначается любому вновь созданному кластеру AKS). Подробную процедуру выполнения этой операции см. здесь: https://learn.microsoft.com/en-us/azure/aks/static-ip. Вы также можете указать аннотацию с меткой:

service.beta.kubernetes.io/azure-dns-label-name: <label >

В этом случае Azure автоматически свяжет доменное имя <label>.<location>.cloudapp.azure.com с LoadBalancer. Если вы хотите опубликовать службу на пользовательском доменном имени, вам необходимо приобрести доменное имя, а затем создать зону Azure DNS с соответствующими записями DNS. Однако в этом случае лучше использовать Ingress вместо простого LoadBalancer (см. подраздел «Ingresses»).

Свойство loadBalancerIP объявлено устаревшим и будет удалено в будущих версиях Kubernetes. Его следует заменить аннотацией, зависящей от платформы. В случае AKS аннотация выглядит следующим образом: service.beta.kubernetes.io/azure-pipname: <ваш статический IP-адрес>

Вернёмся к нашему примеру с nginx и создадим службу LoadBalancer, чтобы сделать наши веб-серверы с балансировкой нагрузки доступными в Интернете:

apiVersion: v1
kind: Service
metadata:
 name: webservers-service
 namespace: basic-examples
spec:
 type: LoadBalancer
 selector:
 app: webservers
 ports:
 - name: http
 protocol: TCP
 port: 80
 targetPort: 80

Мы не указываем IP-адрес, поскольку будем тестировать пример в Minikube, симуляторе, который использует особую процедуру для раскрытия служб LoadBalancer. Разместим определение службы в файле с именем WebServersService.yaml. В файле WebServersPVC.yaml также разместим недостающий PVC:

apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: website
 namespace: basic-examples
spec:
 accessModes:
 - ReadWriteMany
 resources:
 requests:
 storage: 1Gi

Мы не указали класс хранения, поскольку будем использовать класс по умолчанию. Давайте также создадим файл BasicExamples.yaml для определения пространства имен basic-examples:

apiVersion: v1
kind: Namespace
metadata:
 name: basic-examples

Теперь скопируем файл index.html, содержащийся в папке ch08 репозитория GitHub книги, или любую другую автономную HTML-страницу без внешних ссылок на другие изображения/контент, в ту же папку, которая содержит все вышеуказанные файлы .yaml. Мы будем использовать эту страницу в качестве экспериментального контента, который будет отображаться веб-серверами. Начнем наш эксперимент:

  1. Откройте консоль в папке, содержащей все файлы .yaml (щелкните правой кнопкой мыши по папке и выберите опцию консоли).
  2. Убедитесь, что Minikube запущен, а если нет, запустите его с помощью minikube start.
  3. Разверните все файлы в правильной последовательности, то есть убедитесь, что все ресурсы, на которые ссылается файл, уже созданы.
image
  1. Теперь нам нужно скопировать файлы index.html в папку /usr/share/nginx/html одного из двух созданных Pod. Они будут видны и другому Pod, поскольку они используют одно и то же дисковое хранилище. Для этой операции нам понадобится имя Pod. Получим его с помощью команды: kubectl get pods -n Basic-Examples
  2. Файл можно скопировать в Kubernetes Pod с помощью команды kubectl cp: kubectl cp <исходный путь> <пространство имен>/<имя pod>:<папка назначения
  1. В нашем случае команда cp будет выглядеть так: kubectl cp Index.html basic-examples/<имя pod>:/usr/share/nginx/ html
  2. В Minikube вы можете получить доступ к кластеру через службу LoadBalancer, создав туннель. Сделайте следующее: a. Откройте новое окно консоли. b. В этом новом окне выполните команду minikube tunnel. c. Окно застынет на этой команде. Пока окно остается открытым, LoadBalancer доступен через localhost. В любом случае, вы можете проверить внешний IP-адрес, назначенный LoadBalancer, выполнив команду kubectl get services -n Basic-Examples в предыдущем окне.
  3. Откройте ваш любимый браузер и перейдите по адресу http://localhost. Вы должны увидеть содержимое страницы index.html. После завершения эксперимента удалите все ресурсы в обратном порядке (в порядке, противоположном порядку их создания): kubectl delete -f WebServersService.yaml kubectl delete -f WebServers.yaml kubectl delete -f WebServersPVC.yaml Вы можете сохранить определение пространства имен, так как мы будем использовать его в следующем примере.

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

Стоит отметить, что как только Kubernetes обнаруживает сбой узла, он воссоздает все поды, размещенные на этом узле, в другом месте. Однако эта операция может занять некоторое время, поскольку сбой может быть обнаружен не сразу после его возникновения. В то же время, приложения могут работать с перебоями, если под, размещенный на неисправном узле, является незаменимым, поэтому по возможности следует отдавать предпочтение развертываниям.

К сожалению, бывают ситуации, когда идентичные копии не могут обеспечить необходимый параллелизм, но нам нужны неидентичные фрагментированные копии. Если вы не помните, что такое фрагментирование и почему оно необходимо в некоторых ситуациях, обратитесь к разделу «Обеспечение обработки сообщений в правильном порядке» главы 7 «Микросервисы на практике». StatefulSets обеспечивают тип репликации, необходимый для фрагментирования.

StatefulSets и Headless Services

Всем репликам StatefulSet назначаются индексы от 0 до N-1, где N — количество реплик. Имена их Pod также предсказуемы, поскольку они построены как <имя StatefulSet>- <индекс реплики>. Их доменные имена также содержат имена Pod, так что каждый Pod имеет собственное доменное имя: <имя POD>. <имя службы>.<пространство имен>.svc.cluster.local или просто <POD имя>.<имя службы>.<пространство имен>. При создании StatefulSet все реплики создаются в порядке возрастания индекса, а при удалении все реплики удаляются в порядке убывания индекса. То же самое происходит при изменении количества реплик. Каждый StatefulSet должен иметь связанную службу, которая должна быть объявлена в свойстве serviceName StatefulSet. Определение StatefulSet почти идентично определению Deployment; единственное отличие заключается в том, что kind является StatefulSet и есть свойство serviceName:”“ непосредственно под разделом spec. Сервис, связанный с StatefulSet, должен быть так называемым Headless-сервисом, который определяется как сервис ClusterIP, но со свойством ClusterIP: None в разделе spec:

...
spec:
clusterIP: None
 selector:
 ...

Также стоит отметить, что, как правило, каждая реплика имеет собственное частное хранилище, поэтому обычно определения StatefulSet не имеют ссылки на PVC, а вместо этого используют шаблон PVC, который привязывает разные PVC к каждому созданному Pod:

volumeClaimTemplates:
- metadata
 ...
 spec:
 ..

Где метаданные и свойства спецификации идентичны свойствам ресурса PVC. Ниже приведен пример StatefulSet с связанной с ним службой Headless Service. Имя Pod передается каждому контейнеру через переменную среды, чтобы код знал свой индекс и свою возможную роль в алгоритме шардинга:

apiVersion: v1
kind: Service
metadata:
 name: podname
 namespace: basic-examples
 labels:
 app: podname
spec:
 ports:
 - port: 80
 clusterIP: None
 selector:
 app: podname
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
 name: podname
 namespace: basic-examples
spec:
selector:
 matchLabels:
 app: podname
 serviceName: "podname"
 replicas: 3
 template:
 metadata:
 labels:
 app: podname
 spec:
 containers:
 - name: test
 image: alpine:latest
 command: ['sh', '-c', 'while true; do echo $(MY_POD_NAME); sleep
3; done']
 ports:
 - containerPort: 80
 env:
 - name: MY_POD_NAME
 valueFrom:
 fieldRef:
 fieldPath: metadata.name
 volumeClaimTemplates:
 - metadata:
 name: volumetest
 spec:
 accessModes: [ "ReadWriteOnce" ]
 resources:
 requests:
 storage: 1Gi

Каждый Pod содержит только дистрибутив Alpine Linux, а фактический код предоставляется в команде, которая просто выводит переменную среды MY_POD_NAME в бесконечном цикле. В свою очередь, переменная среды MY_POD_ NAME устанавливается с помощью:

- name: MY_POD_NAME
 valueFrom:
 fieldRef:
 fieldPath: metadata.name

Этот код берет значение из поля metadata.name Pod. Фактически, если мы не указали имя в разделе метаданных шаблона Pod, имя будет автоматически создано StatefulSet и добавлено к внутреннему представлению ресурса Pod. Компонент Kubernetes, который делает поля Pod доступными для определения переменных среды, называется downward API. Вышеуказанный StatefulSet не выполняет никаких полезных действий, а просто показывает, как передать имя Pod вашим контейнерам. Поместите вышеуказанный код в файл StateFulSetExample.yaml и примените его! Если вы выполните команду kubectl get pods -n basic-examples, вы сможете убедиться, что все 3 реплики были созданы с правильными именами на основе имени StatefulSet и ваших индексов. Теперь давайте проверим, что podname-1 правильно получил свое имя, отобразив его журнал: kubectl logs podname-1 -n basic-examples Вы должны увидеть несколько строк с правильным именем Pod. Отлично! Теперь давайте проверим, что наш код создал 3 разных PVC: kubectl get persistentvolume -n basic-examples Вы должны увидеть три разных заявки. Когда вы закончите экспериментировать с примером, вы можете удалить все с помощью kubectl delete -f StateFulSetExample.yaml. К сожалению, удаление всего не удаляет PVC, созданные шаблонами, как вы можете убедиться на этом этапе. Самый простой способ удалить их — удалить пространство имен basic-examples с помощью: kubectl delete namespace basic-exampleswhole Затем, если хотите, вы можете воссоздать его с помощью: kubectl create namespace basic-examples Statefulsets используются для развертывания кластеров RabbitMQ и кластеров баз данных в Kubernetes. Если требуется мастер-узел, то один из них с определенным индексом (обычно 0) выбирает себя в качестве мастера. Каждая реплика использует свое собственное дисковое хранилище, чтобы можно было применять стратегии как фрагментации данных, так и репликации данных. Скорее всего, вам не понадобится делать это самостоятельно, поскольку код для развертывания кластеров самых известных кластеров брокерских сообщений и баз данных уже доступен в Интернете. Изучив, как создавать и поддерживать несколько реплик микрослужбы, мы должны научиться устанавливать и обновлять количество реплик, то есть масштабировать наши микрослужбы.

Масштабирование и автоматическое масштабирование

Масштабирование имеет основополагающее значение для настройки производительности приложений. Необходимо различать масштабирование количества реплик каждого микросервиса и масштабирование количества узлов всего кластера Kubernetes. Количество узлов обычно настраивается в соответствии со средним процентом загрузки ЦП. Например, можно начать с 50 % при низком начальном трафике приложения. Затем, по мере увеличения трафика приложения, мы сохраняем то же количество узлов, пока не сможем поддерживать хорошее время отклика, возможно, настраивая количество реплик микрослужб. Предположим, что производительность начинает снижаться, когда процент загрузки ЦП составляет 80%. Тогда мы можем поставить цель, скажем, 75% времени загрузки ЦП. Автоматическое масштабирование кластера возможно только с облачными кластерами, и каждый облачный провайдер предлагает какой-либо вид автомасштабирования. Что касается AKS, в разделе «Создание кластера Azure Kubernetes» мы видели, что можно указать как минимальное, так и максимальное количество узлов, и AKS попытается оптимизировать производительность для нас. Вы также можете точно настроить, как AKS определяет количество узлов. Более подробная информация об этой настройке приведена в справочных материалах в разделе «Дополнительная литература». Существуют также автоматические автомасштабаторы, которые интегрируются с различными поставщиками облачных услуг (https:// kubernetes.io/docs/concepts/cluster-administration/cluster-autoscaling/). По умолчанию автомасштабаторы увеличивают количество узлов, когда Kubernetes не может удовлетворить потребности в ресурсах, требуемых Pod, которые представляют собой сумму полей resource->request всех контейнеров Pod. Масштабирование реплик микросервисов, напротив, является более сложной задачей. Вы можете рассчитать его, измерив среднее время отклика реплики, а затем вычислив:

= <target throughput (requests per second)>

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

Затем необходимо отслеживать производительность системы, выявляя узкие места, в соответствии со следующей процедурой:

  1. Найдите микрослужбу, которая является узким местом.
  2. Увеличьте количество ее реплик, пока она не перестанет быть узким местом.
  3. Повторяйте пункт 1, пока не исчезнут все очевидные узкие места.
  4. Затем оптимизируйте количество узлов кластера для достижения хорошей производительности
  5. Сохраните среднее использование ЦП, занятость памяти всех Deployments и StatefulSets, а также среднее количество запросов, поступающих во все приложение. Вы можете использовать эти данные для настройки автомасштабировщиков. В то время как StatefulSets трудно масштабировать автоматически, Deployments можно масштабировать автоматически без каких-либо проблем. Поэтому вы можете использовать автомасштабировщик Kubernetes Pod для их автоматического масштабирования.

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

Автоматический масштабатор можно определить следующим образом:

apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
 name: myautoscalername
 namespace: mynamespace
spec:
 scaleTargetRef:
 apiVersion: apps/v1
 kind: Deployment
 name: mydeploymentname
 minReplicas: 1
 maxReplicas: 1
metrics:
 - type: <resource or pod or object>
 

Мы указываем тип ресурса, который нужно контролировать, API, в котором он определен, и его имя. Как контролируемый ресурс, так и автомасштабировщик должны быть определены в одном и том же пространстве имен. Вы можете установить scaleTargetRef->kind также в StatefulSet, но вам нужно убедиться, что изменение количества реплик не нарушит ваш алгоритм шардинга, как в долгосрочной перспективе, так и во время перехода между разным количеством реплик. Затем мы указываем максимальное и минимальное количество реплик. Если вычисленное количество реплик превышает этот интервал, оно сокращается до minReplicas или maxReplicas. Наконец, у нас есть список критериев, где каждый критерий может относиться к трем типам метрик: ресурс, под или объект. Мы опишем каждый из них в отдельном подразделе.

Показатели ресурсов

Показатели ресурсов основаны на среднем объеме памяти и ресурсов ЦП, расходуемых каждым Pod. Целевое потребление может быть абсолютным значением, например 100 Мб или 20 ми (милликоров), и в этом случае количество реплик рассчитывается как <фактическое среднее потребление>/<целевое потребление>. Показатели ресурсов, основанные на абсолютных значениях, объявляются следующим образом:

- type: Resource
 resource:
 name: <memory or cpu>
 target:
 type: AverageValue
 averageValue: <target memory or cpu>

Цель также может быть указана в виде процента от общего объявления ресурса Pod->request (сумма всех контейнеров Pod). В этом случае Kubernetes сначала вычисляет:

= 100*/

Затем количество реплик рассчитывается как <использование>/<целевое использование>. Например, если целевое использование ЦП в среднем составляет 50, каждый Pod должен тратить 50% милликоров ЦП, заявленных в запросе. Таким образом, если среднее количество CPU, растрачиваемое всеми Pods развертывания, составляет 30Mi, а количество CPU, необходимое каждому Pod, составляет 20mi, мы рассчитываем использование как 100*30/20= 150. Таким образом, количество реплик составляет 150/50 = 3.

В данном случае код выглядит следующим образом:

- type: Resource
 resource:
 name: <memory or cpu>
 target:
 type: Utilization
 averageUtilization: <target memory or cpu utilization>

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

- type: Pods
 pods:
 metric:
 name: packets-per-second
 target:
 type: AverageValue
 averageValue: 1

Предположим, что метрика «пакетов в секунду» существует на платформе и вычисляет среднее количество пакетов связи, получаемых в секунду подсистемой. Расчет количества реплик выполняется так же, как и в случае среднего значения для метрик ресурсов.

Метрики объектов Метрики объектов — это метрики, вычисляемые для объектов за пределами контролируемых подсистем, но внутри кластера Kubernetes. Как и метрики подсистем, метрики объектов также не являются стандартными, а зависят от метрик, фактически вычисляемых каждой конкретной платформой. В разделе «Расширенная конфигурация Kubernetes» мы опишем ресурсы Kubernetes, называемые Ingresses, которые обеспечивают взаимодействие кластера Kubernetes с внешним миром. Обычно весь входящий трафик Kubernetes проходит через один Ingress, поэтому мы можем измерить общий входящий трафик, измерив трафик внутри этого Ingress. После того, как кластер был эмпирически оптимизирован, и нам нужно просто адаптировать его к временным пикам, самый простой способ сделать это — подключить количество

реплики каждого фронтенд-микросервиса, а также некоторых рабочих микросервисов к общему входному трафику приложения. Это можно сделать с помощью ограничений метрик объектов, которые ссылаются на уникальный вход приложения:

- type: Object
 object:
 metric:
 name: requests-per-second
 describedObject:
 apiVersion: networking.k8s.io/v1
 kind: Ingress
 name: application-ingress
 target:
 type: Value
 value

В данном случае у нас есть значение, поскольку мы не усредняем по нескольким объектам, а количество реплик вычисляется так же, как и для метрик Pod. Кроме того, в данном случае мы должны быть уверены, что метрика requestsper-second действительно вычисляется инфраструктурой на всех Ingresses. Лично я всегда использую метрики CPU и памяти, поскольку они доступны на всех платформах, и поскольку с помощью процедуры, описанной в этом подразделе, довольно легко найти хорошие целевые значения для них. Хотя все поставщики облачных услуг предлагают полезные метрики Kubernetes, существуют серверы метрик с открытым исходным кодом, которые также можно установить на локальных кластерах Kubernetes с помощью файлов .yaml. Пример см. в разделе «Дополнительная литература». Minikube имеет дополнение metrics-server, которое можно установить с помощью minikube addons enable metrics-server. Оно также необходимо для использования стандартных метрик ресурсов, таких как CPU и память. В следующем разделе мы проанализируем, как тестировать и развертывать микросервисное приложение, и применим эти концепции на практике, запустив и отладив микросервис Worker, который мы реализовали в главе 7 «Микросервисы на практике» на Minikube.

Запуск микросервисов на Kubernetes

В этом разделе мы протестируем микросервис routes-matching worker в Minikube, но также опишем, как организовать различные среды, в которых будет развернуто ваше приложение микросервисов: разработка, тестирование и производство. Каждая среда имеет свои особенности, такие как простой способ тестирования каждого изменения в разработке и максимизация производительности в производстве.

Организация всех сред развертывания

Стоит также отметить, что даже самый простой тест в Minikube требует немалого времени на настройку. Поэтому в большинстве случаев при разработке используется Docker, то есть несколько контейнерных микрослужб, организованных в единое решение Visual Studio, которое запускает все микрослужбы при запуске решения. На этом этапе мы не тестируем все приложение, а только несколько тесно взаимодействующих микросервисов, возможно, моделируя остальную часть приложения с помощью заглушек. Если коммуникация осуществляется через брокер сообщений, достаточно запустить все микросервисы и брокер сообщений, чтобы протестировать все; в противном случае, если мы полагаемся на прямую коммуникацию между микросервисами, мы должны подключить все микросервисы в виртуальной сети. Docker предлагает возможность как создать виртуальную сеть, так и подключить к ней запущенные контейнеры. Виртуальная сеть, созданная Docker, также включает вашу машину разработки, которая получает имя хоста host.docker.internal. Таким образом, все микросервисы могут использовать различные службы, запущенные на машине разработки, такие как RabbitMQ, SQL Server и Redis. Вы можете создать тестовую виртуальную сеть в Docker с помощью: docker network create myvirtualnet Затем подключить все запущенные микросервисы к этой сети очень просто. Достаточно изменить их проектные файлы следующим образом:

<PropertyGroup>
 <TargetFramework>net9.0</TargetFramework>
 …
 <DockerfileRunArguments>--net myvirtualnet --name myhostname</
DockerfileRunArguments>
</PropertyGroup>

Затем вы также можете добавить другие аргументы запуска Docker, такие как подключение тома. Тестирование на Minikube можно выполнять в конце рабочего дня или просто после полной реализации функции. В следующих подразделах мы сравним все среды развертывания по следующим параметрам:

  1. Движок базы данных и установка базы данных
  2. Реестры контейнеров
  3. Установка брокера сообщений
  4. Методы отладки

Установка движка базы данных и базы данных

Тесты разработки с Docker или Minikube могут использовать движок базы данных, работающий непосредственно на машине разработчика. Вы можете использовать либо фактическую установку, либо движок, работающий в качестве контейнера Docker. Преимущество заключается в том, что база данных также доступна из Visual Studio, поэтому вы можете проходить все миграции во время их разработки. Вы также можете использовать новые контейнеры Docker, на которых работает движок базы данных, чтобы запустить базы данных с нуля и выполнить модульные тесты или протестировать весь набор миграций.

Если вы установили Minikube с драйвером Docker, к базе данных, работающей на вашей машине для разработки, можно получить доступ из контейнеров Minikube, используя либо имя хоста host.minikube.internal, либо host.docker.internal. Таким образом, если вы используете host.docker.internal, вы сможете получить доступ к своей хост-машине как из Minikube, так и из контейнерных приложений, запущенных непосредственно из Visual Studio.

Как на стадии подготовки, так и в производственной среде вы можете использовать облачные службы баз данных, которые обеспечивают хорошую производительность, масштабируемость, кластеризацию, репликацию, географическую избыточность и т. д. Также можно развернуть базу данных внутри кластера Kubernetes, но в этом случае необходимо приобрести лицензию, выделить специальные узлы Kubernetes для базы данных (виртуальные машины, которые обеспечивают оптимальную производительность базы данных) и точно настроить конфигурацию базы данных. Поэтому, если нет веских причин для другого выбора, удобнее выбрать облачные сервисы. Кроме того, как в производственной, так и в тестовой среде вы не можете настроить свои развертывания на автоматическое применение миграций при запуске, иначе все реплики будут пытаться их применить. Лучше извлечь скрипт базы данных из ваших миграций и применить его с правами пользователя DBO базы данных, оставив реплики микрослужб с менее привилегированным пользователем базы данных. Скрипт базы данных можно извлечь из всех миграций с помощью команды миграции ниже:

Script-Migration -From <initial migration> -To <final migration> -Output <name of output file>

Перейдем к реестрам контейнеров.

Реестры контейнеров

Что касается тестовой и производственной среды, то они могут использовать один и тот же реестр контейнеров, поскольку контейнеры имеют версии. Так, например, производственная среда может использовать v1.0, а тестовая — v2.0-beta1. Лучше, если реестры принадлежат одной и той же подписке на облачные услуги кластера Kubernetes, чтобы упростить обработку учетных данных. Например, в случае AKS достаточно один раз связать реестр с кластером AKS, чтобы предоставить ему доступ к кластеру (см. подраздел «Создание кластера Azure Kubernetes» в этой главе). Что касается разработки, каждый разработчик может использовать тот же реестр, что и среда подготовки для контейнеров, над которыми он не работает, но каждый разработчик должен иметь частный реестр для контейнеров, над которыми он работает, чтобы он мог экспериментировать без риска загрязнения реестров «официальных образов». Поэтому самым простым решением является установка локального реестра в вашем Docker Desktop. Вы можете сделать это с помощью:

docker run -d -p 5000:5000 --name registry registry:2 После создания контейнера с помощью приведенной выше команды вы можете остановить и перезапустить его из графического интерфейса пользователя Docker Desktop. К сожалению, по умолчанию и Docker, и Minikube не допускают взаимодействия с небезопасными реестрами, то есть с реестрами, которые не поддерживают HTTPS с сертификатом, подписанным государственным органом, поэтому мы должны настроить и Docker, и Minikube на допуск небезопасного взаимодействия с локальным реестром. Откроем графический интерфейс пользователя Docker Desktop и щелкнем по значку настроек в правом верхнем углу:

image Figure 8.5: Docker settings

Затем выберите Docker Engine в левом меню, отредактируйте большое текстовое поле, содержащее информацию о конфигурации Docker, и добавьте приведенную ниже запись к существующему содержимому JSON:

…….,
 "insecure-registries": [
 "host.docker.internal:5000",
 "host.minikube.internal:5000"

Вышеуказанные настройки добавляют 5000 портов обоих хост-имен, которые указывают на ваш хост-компьютер, в разрешенные небезопасные реестры. Результат должен быть примерно таким:

image Figure 8.6: Adding a local registry to Docker allowed insecure registries

Что касается Minikube, вам необходимо удалить текущую виртуальную машину Minikube с помощью команды: minikube delete Затем вам нужно создать новый образ виртуальной машины с правильными настройками небезопасного реестра: minikube start --insecure-registry=«host.docker.internal:5000» --insecureregistry=«host.minikube.internal:5000» Выполните все вышеуказанные шаги, так как нам понадобится локальный реестр для тестирования микрослужбы планирования маршрутов. Если Minikube также должен получить доступ к другим реестрам, защищенным паролем, необходимо настроить и включить надстройку registry-creds: minikube addons configure registry-creds После выполнения вышеуказанной команды вам будет предложено настроить частные реестры Google, AWS, Azure или Docker и ввести свои учетные данные. После успешной настройки вы можете включить использование учетных данных с помощью: minikube addons enable registry-creds

Перейдем к брокеру сообщений

Установка брокер сообщений

RabbitMQ можно установить как локально, так и в облаке, и он работает на всех облаках, поэтому это действительно хороший вариант. Вы можете запустить один сервер RabbitMQ или кластер серверов. Кластер RabbitMQ также можно установить на самом кластере Kubernetes. Во время разработки вы можете установить его на Minikube, но удобнее запускать его вне Minikube, чтобы к нему могли легко обращаться приложения, запущенные вне Minikube, что, в свою очередь, облегчает отладку приложений, как мы увидим в следующем подразделе. В тестовой и производственной среде самый простой способ установить кластер RabbitMQ — это установить так называемый оператор кластера RabbitMQ с помощью: kubectl apply -f https://raw.githubusercontent.com/rabbitmq/clusteroperator/main/docs/examples/hello-world/rabbitmq.yaml Оператор RabbitMQ определяет пользовательский ресурс RabbitmqCluster, который представляет кластер RabbitMQ. Вы можете создать и настроить RabbitmqCluster так же, как и любой другой ресурс Kubernetes:

apiVersion: rabbitmq.com/v1beta1
kind: RabbitmqCluster
metadata:
 name: <cluster name>
 namespace: <cluster namespace>
spec:
 replicas: 3 # default is 1. Replicas should be odd.
 persistence:
 storageClassName: <storage class name> # default is the default
storage class
 storage: 20Gi # default 10Gi

Раздел persistence определяет параметры для сохранения очередей в постоянном хранилище. Если вы его опустите, будут использованы все значения по умолчанию. Если вы опустите количество реплик, будет создан кластер с одним сервером. Дополнительные параметры доступны в официальной документации: https://www. rabbitmq.com/kubernetes/operator/using-operator.

Вы можете получить имя пользователя и пароль пользователя по умолчанию кластера RabbitMQ, выведя секрет <имя кластера>-default-user, где они хранятся, как показано ниже: kubectl get secret <имя кластера>-default-user -n <пространство имен кластера> -o yaml Имя пользователя и пароль закодированы в формате base-64. Самый простой способ их декодировать — скопировать каждый из них из вывода консоли, открыть консоль Linux и использовать команду base64: echo <строка для декодирования> | base64 -d При желании вы также можете установить оператор кластера RabbitMQ в Minikube, но в этом случае лучше запустить Minikube с минимум 4 процессорами и 6-8 гигабайтами памяти. Если вам нужно подключиться к кластеру RabbitMQ извне кластера Kubernetes для отладки, вы можете использовать команду kubectl port-forward: kubectl port-forward service/<имя кластера> 5672:5672 Вышеуказанная инструкция замораживает консоль и перенаправляет порт 5672 службы service/<имя кластера> ClusterIP, которая является частью кластера RabbitMQ, на порт 5672 localhost. Перенаправление портов остается активным, пока окно консоли открыто или не нажата комбинация клавиш Ctrl+C для прерывания инструкции. Общий синтаксис kubectl port-forward: kubectl port-forward service/<имя службы> <порт локального хоста>:<порт службы

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

Служба <имя кластера> — это служба ClusterIP, которую необходимо использовать для доступа к кластеру RabbitMQ изнутри кластера Kubernetes. Таким образом, имя хоста RabbitMQ, которое необходимо указать в соединении, — <имя кластера>.<пространство имен кластера>.

Вы также можете получить доступ к интерфейсу управления RabbitMQ с помощью браузера, перенаправив порт 15672: kubectl port-forward service/<имя кластера> 15672:15672 После этого интерфейс будет доступен по адресу localhost:15672. Там необходимо использовать учетные данные, которые вы ранее извлекли из секрета cluster name>-default-user.

Перенаправление портов является безопасным и не открывает RabbitMQ для внешнего мира, поскольку соединение между localhost и сервисом осуществляется через API-сервер Kubernetes. Его можно безопасно использовать для подключения тестового кода, выполняющегося на машине разработчика, к кластеру RabbitMQ, как мы увидим более подробно в следующем подразделе.

Методы отладки

Когда вы запускаете все контейнеры из Visual Studio, вы можете отлаживать свой код без дополнительной настройки. Однако, если вам нужно отлаживать некоторые микросервисы, работающие в Minikube, в тестовой среде или в производственной среде, вам потребуется дополнительная настройка. Вместо того, чтобы пытаться подключить отладчик внутри кластера Kubernetes, проще использовать так называемый мост: вы выбираете конкретный микросервис для отладки и вместо отладки в Kubernetes перенаправляете его трафик на копию микросервиса, запущенную в Visual Studio, а затем перенаправляете весь локальный выходной трафик микрослужбы обратно внутрь кластера. Таким образом, вы отлаживаете только локальную копию, скомпилированную в режиме отладки, что позволяет избежать как необходимости замены релиза кода на отладочный код, так и сложности подключения отладчика внутри вашего кластера Kubernetes. На изображении ниже показана идея моста:

image Figure 8.7: Bridging

Если входы и выходы обрабатываются брокер сообщениями, то соединение осуществляется легко: достаточно подключить локальную копию к тем же очередям RabbitMQ, что и в кластере реплик. Таким образом, часть трафика будет автоматически перенаправлена на локальную копию. Если кластер RabbitMQ работает внутри кластера Kubernetes, необходимо перенаправить его порты на localhost, как описано в предыдущем разделе. Кроме того, если микрослужба подключена к базе данных, мы также должны подключить локальную копию к той же базе данных. Если вы находитесь в производственной среде, это может потребовать определения правила брандмауэра, чтобы разрешить доступ вашей машины разработки к базе данных. Если некоторые входы и выходы обрабатываются службами, а не брокерами сообщений, соединение становится более сложным. В частности, перенаправление вывода на службу внутри кластера Kubernetes достаточно просто, поскольку для этого требуется только перенаправить порт целевой службы на localhost с помощью kubectl port-forward. Однако перенаправление трафика от службы к локальной копии микрослужбы требует некоторого взлома службы. Службы вычисляют поды, на которые они должны направлять трафик, а затем создают ресурсы, называемые EndpointSlice, содержащие IP-адреса, по которым они должны маршрутизировать трафик. Поэтому, чтобы маршрутизировать весь трафик службы на ваш локальный компьютер, вам необходимо переопределить EndpointSlices этой службы. Это можно сделать, удалив селектор целевой службы, чтобы все EndpointSlices были удалены, а затем вручную добавив EndpointSlice, который указывает на ваш компьютер для разработки . Вы можете сделать это следующим образом:

  1. Получите определение целевой службы с помощью: kubectl get service <имя службы> -n <пространство имен службы> -o yaml
  2. Удалите селектор и примените новое определение.
  3. Если вы работаете с удаленным кластером, добавьте EndpointSlice ниже:
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
 name: <service name>-1
 namespaces: <service namespace>
 labels:
 kubernetes.io/service-name: <service name>
addressType: IPv4
ports:
- name: http # should match with the name of the service port
 appProtocol: http
 protocol: TCP
 port: <target port>
endpoints:
 - addresses:
 - "<your development machine IP address>"
  1. Если же вы работаете с локальным кластером Minikube, добавьте EndpointSlice ниже:
apiVersion: discovery.k8s.io/v1
kind: EndpointSlice
metadata:
 name: <service name>-1
 namespaces: <service namespace>
 labels:
 kubernetes.io/service-name: <service name>
addressType: FQDN
ports:
 - name: http # should match with the name of the service port
 appProtocol: http
 protocol: TCP
 port: <target port>
endpoints:
 - addresses:
 - "host.minikube.local"
  1. По завершении отладки повторно примените исходное определение службы. Ваш настраиваемый EndpointSlice будет автоматически уничтожен. Как видите, использование брокерских служб значительно упрощает отладку. Это рекомендуемый вариант при реализации приложений. Службы являются более подходящим вариантом при реализации таких инструментов, как кластеры баз данных или брокерские службы, работающие внутри вашего кластера. Существуют инструменты, которые автоматически обрабатывают все необходимые изменения сервисов, такие как Bridge to Kubernetes (https://learn.microsoft.com/en-us/visualstudio/bridge/bridge-to-kubernetes-vs), но, к сожалению, Microsoft объявила, что прекратит его поддержку. Microsoft порекомендует подходящую альтернативу. Теперь мы наконец готовы протестировать реальный микросервис на Minikube.

Тестирование микрослужбы-рабочего процесса сопоставления маршрутов

Мы протестируем микрослужбу-рабочий процесс сопоставления маршрутов, реализованную в главе 7 «Микрослужбы на практике», вместе с двумя микрослужбами-заглушками. Первая из них будет отправлять ей тестовые входные данные, а вторая будет собирать все ее выходные данные и записывать их в консоль, чтобы мы могли получить доступ к этим выходным данным с помощью команды kubectl logs. Это типичный способ проведения предварительного тестирования. Затем более сложные тесты могут также включать другие сервисы приложения. Давайте создадим копию нашего решения микросервиса-рабочего по сопоставлению маршрутов, затем добавим еще два проекта сервиса Worker и назовем их соответственно FakeSource и FakeDestination. Для каждого из них включите поддержку контейнеров для Linux, как показано на следующем скриншоте:

image Figure 8.8: Worker services project settings

Затем добавим все необходимые пакеты EasyNetQ, чтобы обе службы могли взаимодействовать с кластером RabbitMQ:

  1. EasyNetQ
  2. EasyNetQ.Serialization.NewtonsoftJson
  3. EasyNetQ.Serialization.SystemTextJson Выберите версию не ниже 8, даже если она еще находится в стадии предварительного выпуска. Затем необходимо добавить RabbitMQ к службам в файле Program.cs обоих проектов:
builder.Services.AddEasyNetQ(
builder.Configuration?.GetConnectionString("RabbitMQConnection") ?? string.E

Строка подключения RabbitMQ должна быть добавлена в переменные среды, определенные в Properties->launchSettings.json, как показано ниже:

"Container (Dockerfile)": {
 "commandName": "Docker",
 "environmentVariables": {
 "ConnectionStrings__RabbitMQConnection":
 "host=host.docker.internal:5672;username=guest;password=_myguest;
 publisherConfirms=true;timeout=10"
 }

Наконец, обратитесь к проекту SharedMessages из FakeSource и FakeDestination, чтобы они могли использовать все сообщения приложения. На этом этапе мы готовы к написанию кода наших служб-заглушек. В файле Worker.cs, созданном Visual Studio в проекте FakeDestination, замените существующий класс следующим:

public class Worker: BackgroundService
{
 private readonly ILogger<Worker> _logger;
 private readonly IBus _bus;
 public Worker(ILogger<Worker> logger, IBus bus)
 {
 _logger = logger;
 _bus= bus;
 }
 protected override async Task ExecuteAsync(CancellationToken
 stoppingToken)
 {
 var routeExtensionProposalSubscription = await _bus.PubSub.
 SubscribeAsync<
 RouteExtensionProposalsMessage>(
 "FakeDestination",
 m =>
 {
 var toPrint=JsonSerializer.Serialize(m);
 _logger.LogInformation("Message received: {0}",
 toPrint);
},
 stoppingToken);
 await Task.Delay(Timeout.Infinite, stoppingToken);
 routeExtensionProposalSubscription.Dispose();
 }
}

Хостируемый сервис добавляет подписку с именем FakeDestination к событию RouteExtensionProposalsMessage. Таким образом, он получает все совпадающие предложения между существующим маршрутом и некоторыми запросами. Как только обработчик подписки получает предложение, он просто регистрирует сообщение в формате JSON, чтобы мы могли проверить, что события правильного совпадения предложений генерируются путем изучения журналов FakeDestination. В файле Worker.cs, созданном Visual Studio в проекте FakeSource, мы заменим существующий класс простым кодом, который выполняет следующие действия:

  1. Создает три сообщения о городах: Феникс, Санта-Фе и Шайенн.
  2. Отправляет запрос из Феникса в Санта-Фе.
  3. Отправляет предложение маршрута, проходящего через Феникс, Санта-Фе и Шайенн. Как только это сообщение получается микрослужбой планирования маршрутов, она должна создать предложение, соответствующее этому предложению с предыдущим запросом. Это предложение должно быть получено FakeDestination и зарегистрировано.
  4. Отправляет запрос из Санта-Фе в Шайенн. Как только это сообщение получает микросервис планирования маршрутов, он должен создать предложение, чтобы сопоставить этот запрос с предыдущим предложением. Это предложение должно быть получено FakeDestination и зарегистрировано.
  5. Через 10 секунд он имитирует, что оба предыдущих предложения были приняты, и создает событие расширения маршрута на основе предыдущего предложения, содержащее оба сопоставленных запроса. Как только это сообщение поступает в микрослужбу планирования маршрутов, она должна обновить предложение и добавить два запроса к предложению. В результате поле RouteId обоих запросов должно указывать на идентификатор предложения.

Код класса Worker.cs:

public class Worker : BackgroundService
{
 private readonly ILogger<Worker> _logger;
 private readonly IBus _bus;
 public Worker(ILogger<Worker> logger, IBus bus)
 {
 _logger = logger;
 _bus = bus;
 }
 protected override async Task ExecuteAsync(CancellationToken
 stoppingToken)
 {
 …
 …
 /* The code that defines all messages has been omitted */
 var delayInterval = 5000;
 await Task.Delay(delayInterval, stoppingToken);
 await _bus.PubSub.PublishAsync<RouteRequestMessage>(request1);
 await Task.Delay(delayInterval, stoppingToken);
 await _bus.PubSub.PublishAsync<RouteOfferMessage>(offerMessage);
 await Task.Delay(delayInterval, stoppingToken);
 await _bus.PubSub.PublishAsync<RouteRequestMessage>(request2);
 await Task.Delay(2*delayInterval, stoppingToken);
 await _bus.PubSub.PublishAsync<
RouteExtendedMessage>(extendedMessage);
 await Task.Delay(Timeout.Infinite, stoppingToken);
 }

Код, определяющий все сообщения, был опущен. Полный код можно найти в файле ch08->CarSharing->FakeSource->Worker.cs репозитория GitHub, связанного с книгой.

Теперь давайте подготовимся к запуску всех микрослужб в Docker, выполнив следующие действия:

  1. Щелкните правой кнопкой мыши строку решения в Visual Studio Solution Explorer и выберите Configure Startup Projects… (Настроить проекты запуска…).
  2. Затем выберите Multiple startup projects (Несколько проектов запуска) и измените название параметра запуска на AllMicroservices.
  3. Затем выберите все три проекта FakeDestination, FakeSource и RoutesPlanning и для каждого из них выберите «Запуск» в качестве действия и «Контейнер (файл Docker)» в качестве цели отладки, как показано ниже:
image Figure 8.9: Launch settings

Теперь вы можете запустить все проекты одновременно, выбрав AllMicroservices в Visual Studio Debug Launcher. Убедитесь, что SQL Server приложения и сервер RabbitMQ работают. Затем скомпилируйте проект и запустите его. На вкладке Containers, которая появится, выберите FakeDestination, чтобы вы могли просматривать его журналы. Через несколько секунд вы должны увидеть два сообщения с предложениями по сопоставлению, как показано ниже:

image Figure 8.10: FakeDestination logs

Затем в панели «Обзор объектов SQL Server» выберите базу данных приложения, если она уже есть; в противном случае подключитесь к ней, а затем отобразите ее таблицы:

image Figure 8.11: Application database

Щелкните правой кнопкой мыши по dbo.RouteOffers и dbo.RouteRequests и выберите View Data, чтобы увидеть все их данные. Вы должны увидеть, что Timestamp предложения изменился на 2, потому что предложение было обновлено после того, как два совпадающих предложения были приняты:

image Figure 8.12: Updated offer

Кроме того, вы должны убедиться, что оба запроса были связаны с предложением:

image Figure 8.13: Updated requests

Теперь давайте прекратим отладку и удалим все записи в таблицах dbo.RouteOffers и dbo.RouteRequests.

Пришло время развернуть наши микросервисы в Minikube! Мы будем использовать те же RabbitMQ и SQL Server, которые работают на машине разработчика. Однако прежде чем приступить к развертыванию наших файлов .yaml в Minikube, необходимо выполнить несколько предварительных шагов:

  1. Мы должны создать подходящие образы Docker, поскольку образы отладки, созданные Visual Studio, не могут работать вне Visual Studio. Все они имеют версию dev. Перейдите к файлам Docker трех проектов FakeDestination, FakeSource и RoutesPlanning в Visual Studio Explorer, щелкните их правой кнопкой мыши и выберите Build Docker Image. Эти действия создадут три образа Docker с последней версией.
  2. Запустите локальный контейнер реестра из интерфейса Docker. Если вы еще не создали контейнер реестра, обратитесь к подразделу «Реестры контейнеров» для получения инструкций по установке .
  3. Отправьте наши вновь созданные образы в этот реестр, чтобы их можно было загрузить с помощью Minikube (помните, что для выполнения приведенных ниже команд вам понадобится консоль Linux): docker tag fakesource:latest localhost:5000/fakesource:latest docker push localhost:5000/fakesource:latest docker tag fakedestination:latest localhost:5000/ fakedestination:latest docker push localhost:5000/fakedestination:latest docker tag routesplanning:latest localhost:5000/ routesplanning:latest docker push localhost:5000/routesplanning:latest Нам нужно создать 3 развертывания, по одному для каждого из наших трех микросервисов. Давайте создадим папку Kubernetes в папке решения CarSharing. Мы поместим туда наши определения развертывания. Ниже FakeSource.yaml:
apiVersion: apps/v1
kind: Deployment
metadata:
 name: fakesource
 namespace: car-sharing
labels:
 app: car-sharing
 classification: stub
 role: fake-source
spec:
 selector:
 matchLabels:
 app: car-sharing
 role: fake-source
 replicas: 1
 template:
 metadata:
 labels:
 app: car-sharing
 classification: stub
 role: fake-source
 spec:
 containers:
 - image: host.docker.internal:5000/fakesource:latest
 name: fakesource
 resources:
 requests:
 cpu: 10m
 memory: 10Mi
 env:
 - name: ConnectionStrings__RabbitMQConnection
 value:
 "host=host.docker.internal:5672;username=guest;password=_
myguest;
 publisherConfirms=true;timeout=10"

Он содержит только одну переменную среды для строки подключения RabbitMQ — ту же, которую мы определили в launchSettings.json. Запрос ресурсов минимален. Метки также являются инструментом документирования. Поэтому они определяют как название приложения, так и его роль в приложении, а также то, что этот микросервис является заглушкой. Мы разработали пространство имен car-sharing для размещения всего приложения.

host.docker.internal:5000 — это имя хоста нашего локального реестра, как оно отображается изнутри Minikube. Нашим развертываниям не нужны службы, поскольку они обмениваются данными через RabbitMQ. FakeDestination.yaml полностью аналогичен:

apiVersion: apps/v1
kind: Deployment
metadata:
 name: fakedestination
 namespace: car-sharing
 labels:
 app: car-sharing
 classification: stub
 role: fake-destination
spec:
 selector:
 matchLabels:
 app: car-sharing
 role: fake-destination
 replicas: 1
 template:
 metadata:
 labels:
 app: car-sharing
 classification: stub
 role: fake-destination
 spec:
 containers:
 - image: host.docker.internal:5000/fakedestination:latest
 name: fakedestination
 resources:
 requests:
 cpu: 10m
 memory: 10Mi
 env:
 - name: ConnectionStrings__RabbitMQConnection
 value: "host=host.docker.internal:5672;username=guest;password=_
myguest;publisherConfirms=true;timeout=10"

RoutesPlanning.yaml отличается от других файлов тем, что содержит гораздо больше переменных среды и открывает порт 8080, который мы можем использовать для проверки работоспособности службы (см. подраздел «Проверки готовности, работоспособности и запуска» в следующем разделе).

apiVersion: apps/v1
kind: Deployment
metadata:
 name: routesplanning
 namespace: car-sharing
 labels:
 app: car-sharing
 classification: worker
 role: routes-planning
spec:
 selector:
 matchLabels:
 app: car-sharing
 role: routes-planning
 replicas: 1
 template:
 metadata:
 labels:
 app: car-sharing
 classification: worker
 role: routes-planning
 spec:
 containers:
 - image: host.docker.internal:5000/routesplanning:latest
 name: routesplanning
 ports:
 - containerPort: 8080
 resources:
 requests:
 cpu: 10m
 memory: 10Mi
 env:
 - name: ASPNETCORE_HTTP_PORTS
 value: "8"
- name: ConnectionStrings__DefaultConnection
 value: "Server=host.docker.internal;Database=RoutesPlanning;User
 Id=sa;Password=Passw0rd_;Trust Server
 Certificate=True;MultipleActiveResultSets=true"
 - name: ConnectionStrings__RabbitMQConnection
 value: "host=host.docker.internal:5672;username=guest;password=_
 myguest;publisherConfirms=true;timeout=10"
 - name: Messages__SubscriptionIdPrefix
 value: "routesPlanning"
 - name: Topology__MaxDistanceKm
 value: "50"
 - name: Topology__MaxMatches
 value: "5"
 - name: Timing__HousekeepingIntervalHours
 value: "48"
 - name: Timing__HousekeepingDelayDays
 value: "10"
 - name: Timing__OutputEmptyDelayMS
 value: "500"
 - name: Timing__OutputBatchCount
 value: "10"
 - name: Timing__OutputRequeueDelayMin
 value: "5"
 - name: Timing__OutputCircuitBreakMin
 value: "4"

Откроем консоль Windows в папке Kubernetes и начнем развертывание нашего приложения:

  1. Запустим Minikube с помощью minikube start.
  2. Создадим пространство имен car-sharing с помощью kubectl create namespace car-sharing.
  3. Сначала развернем FakeDestination.yaml: kubectl apply -f FakeDestination.yaml.
  4. Теперь давайте проверим, все ли поды в порядке и готовы, с помощью kubectl get all -n car-sharing. Если они не готовы, повторите команду, пока они не будут готовы.
  5. Скопируем имя созданного пода. Оно нам понадобится для доступа к его журналам.
  6. Затем развернем RoutesPlanning.yaml: kubectl apply -f RoutesPlanning.yaml.
  7. Снова проверим, что все Pods в порядке и готовы, с помощью kubectl get all -n car-sharing.
  8. Затем развернем FakeSource.yaml: kubectl apply -f FakeSource.yaml.
  9. Снова проверьте, что все Pods в порядке и готовы к работе, с помощью команды kubectl get all -n car-sharing.
  10. Теперь проверьте журналы FakeDestination, чтобы убедиться, что он получил предложения о совпадении, с помощью команды: kubectl logs -n car-sharing. Где — это имя, которое мы получили в шаге 5.
  11. Также проверьте таблицу базы данных, чтобы убедиться, что приложения работают правильно.
  12. Когда вы закончите эксперимент, удалите все, просто удалив пространство имен carsharing: kubectl delete namespace car-sharing.
  13. Также удалите записи в таблицах базы данных dbo.RouteOffers и dbo.RouteRequests.
  14. Остановите Minikube с помощью команды: minikube stop. Теперь, если вы хотите поэкспериментировать с отладкой с помощью техники моста, повторите вышеуказанные шаги, но замените пункты 6 и 7, которые развертывают микрослужбу RoutePlanning, запуском единственного проекта RoutePlanning внутри Visual Studio (просто замените AllMicroservices на RoutePlanning в отладочном виджете Visual Studio, а затем запустите отладчик). Поскольку все контейнеры подключены к одному и тому же серверу RabbitMQ, контейнер, запущенный в Visual Studio, будет получать все входные сообщения, созданные из Minikube, а все его выходные сообщения будут маршрутизироваться внутри Minikube. Давайте разместим точку останова в том месте, где вы хотите проанализировать код, прежде чем продолжить развертывание Kubernetes. Через несколько секунд после развертывания файла FakeSource.yaml точка останова должна быть достигнута!

Расширенная настройка Kubernetes

В этом разделе описаны расширенные ресурсы Kubernetes, которые играют фундаментальную роль в проектировании приложений. Другие расширенные ресурсы и настройки, связанные конкретно с безопасностью и наблюдаемостью, будут описаны в главе 10 «Безопасность и наблюдаемость для бессерверных приложений и микросервисов». Начнем с секретов.

Secrets

Kubernetes допускает различные типы секретов. Здесь мы опишем только общие секреты и секреты tls, которые используются в практической разработке приложений на основе микросервисов. Каждый общий секрет содержит набор пар «имя записи/значение записи». Секреты можно определять с помощью файлов .yaml, но поскольку нецелесообразно смешивать конфиденциальную информацию с кодом, их обычно определяют с помощью команд kubectl.

Ниже приведено описание того, как определить секрет, взяв значения записей из содержимого файла: kubectl create secret generic credentials --from-file=username.txt --fromfile=password.txt Имена файлов становятся именами записей (только имя файла с расширением — информация о пути удаляется), а содержимое файлов становится связанными значениями записей. Каждая запись определяется с помощью различной опции --from-file=…. Создайте два файла с указанными выше именами в каталоге, вставьте в них некоторое содержимое, затем откройте консоль в этом каталоге и, наконец, попробуйте выполнить указанную выше команду. После создания вы можете увидеть его в формате .yaml с помощью: kubectl get secret credentials -o yaml В разделе данных вы увидите две записи, но значения записей отображаются в зашифрованном виде. На самом деле, они не зашифрованы, а просто закодированы в base64. Разумеется, вы можете запретить некоторым пользователям Kubernetes доступ к секретным ресурсам. Мы увидим, как это сделать, в главе 10 «Безопасность и наблюдаемость для бессерверных приложений и микросервисов». Секрет можно удалить с помощью: kubectl delete secret credentials Вместо использования файлов можно указать значения записей в строке: kubectl create secret generic credentials --from-literal=username=devuser --from-literal=password=„$dsd_weew1“ Как обычно, мы можем указать пространство имен секрета с помощью опции -n. После определения общие секреты можно смонтировать в качестве томов на поды:

volumes:
- name: credentialsvolume
 secret:
 secretName: credentials

Каждая запись рассматривается как файл, имя которого является именем записи, а содержимое — значением записи.

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

Секреты также могут передаваться в виде переменных среды: env:

- name: USERNAME
 valueFrom:
 secretKeyRef:
 name: credentials
 key: username
- name: PASSWORD
 valueFrom:
 secretKeyRef:
 name: credentials
 key: password

В этом случае секретные значения автоматически декодируются с помощью base64 перед передачей в качестве переменных среды.

Давайте попробуем использовать секреты в микросервисах, отвечающих за сопоставление маршрутов. Создадим секрет Kubernetes, содержащий строку подключения RabbitMQ, и исправим файлы FakeDestination.yaml, FakeSource.yaml и RoutesPlanning.yaml, чтобы использовать этот секрет.

Секреты tls предназначены для хранения сертификатов веб-серверов. Мы увидим, как их использовать, в подразделе «Ingresses». Секреты tls принимают в качестве входных данных как сертификат закрытого ключа (.key), так и сертификат открытого ключа (.crt): kubectl create secret tls test-tls --key=«tls.key» --cert=«tls.crt» Следующая важная тема касается того, как наш контейнерный код может помочь Kubernetes проверить, готов ли каждый контейнер к взаимодействию с остальной частью приложения и находится ли он в хорошем состоянии.

Проверки готовности, работоспособности и запуска

Проверки работоспособности информируют Kubernetes о том, когда контейнеры находятся в невосстановимом неисправном состоянии, поэтому Kubernetes должен их убить и перезапустить. Если для контейнера не определена проверка работоспособности, Kubernetes перезапускает его на случай, если он выйдет из строя из-за непредсказуемого исключения или превышения ограничений памяти. Проверки работоспособности должны быть тщательно разработаны, чтобы обнаруживать фактические неустранимые ошибки ; в противном случае контейнер может оказаться в бесконечном цикле перезапусков. Временные сбои, напротив, связаны с проверками готовности. Когда проверка готовности завершается сбоем, она сообщает Kubernetes, что контейнер не может принимать трафик. Соответственно, Kubernetes удаляет неработающий контейнер из всех списков соответствующих служб, которые могли бы отправлять ему трафик. Таким образом, трафик распределяется только между готовыми контейнерами. Неисправный контейнер не перезапускается и вновь вставляется в список служб, как только проверка готовности снова проходит успешно. Наконец, зонд запуска сообщает Kubernetes, что контейнер завершил процедуру запуска. Его единственная цель — избежать того, чтобы Kubernetes убивал и перезапускал контейнер во время запуска из-за сбоев зонда работоспособности. Фактически, подобные события могут привести к тому, что контейнер попадет в бесконечный цикл перезапусков. Проще говоря, Kubernetes запускает зонды работоспособности и готовности только после успешного выполнения зонда запуска. Поскольку проверки работоспособности и готовности уже имеют начальные задержки, проверки запуска необходимы только в случае очень длительных процедур запуска. Все проверки имеют операцию проверки, которая может либо завершиться неудачей, либо успехом, со следующими параметрами:

  1. failureThreshold: количество последовательных неудач операции проверки, необходимое для считать проверку неудачной. Если не указано, по умолчанию равно 3.
  2. successThreshold: используется только для проверок готовности. Это минимальное количество последовательных успехов, необходимое для того, чтобы проверка считалась успешной после неудачи. По умолчанию значение равно 1.
  3. initialDelaySeconds: время в секундах, которое Kubernetes должен подождать после запуска контейнера, прежде чем пытаться выполнить первую проверку. По умолчанию значение равно 0.
  4. periodSeconds: время в секундах между двумя последовательными пробами. По умолчанию — 10 секунд.
  5. timeoutSeconds: количество секунд, по истечении которых проба завершается с ошибкой. По умолчанию — 1 секунда. Часто пробы работоспособности и готовности реализуются с помощью одной и той же операции пробы, но проба работоспособности имеет более высокий порог сбоев.

Пробы являются свойствами уровня контейнера, то есть они находятся на том же уровне, что и порты контейнера, и имя. Операции проб могут основываться на командах оболочки, HTTP-запросах или попытках подключения TCP/IP. Пробы, основанные на командах оболочки, определяются как:

livenessProbe/readinessProbe/startupProbe:
 exec:
 command:
 - cat
 - /tmp/healthy
 initialDelaySeconds: 10
 periodSeconds: 5
 ...

Список команд содержит команду и все ее аргументы. Операция выполняется успешно, если она завершается с кодом статуса 0, то есть если команда выполняется без ошибок. В приведенном выше примере команда выполняется успешно, если файл /tmp/healthy существует. Проверки на основе TCP/IP-соединений определяются следующим образом:

livenessProbe/readinessProbe/startupProbe:
 tcpSocket:
 port: 8080
 initialDelaySeconds: 10
 periodSecond

Операция завершается успешно, если TCP/соединение установлено. Наконец, пробы на основе HTTP-запросов определяются следующим образом:

livenessProbe/readinessProbe/startupProbe:
 httpGet:
 path: /healthz
 port: 8080
 httpHeaders:
 -name: Custom-Health-Header
 value: Kubernetes-probe
 initialDelaySeconds: 10
 periodSeconds: 5

path и port указывают путь и порт конечной точки. В опциональном разделе httpHeaders перечислены все HTTP-заголовки, которые Kubernetes должен предоставить в своем запросе. Операция завершается успешно, если ответ возвращает код статуса, удовлетворяющий условию: 200<=status<400. Добавим проверку работоспособности в развертывание RoutesPlanning.yaml раздела «Тестирование микрослужбы route-matching worker». Нам не нужна проверка готовности, поскольку проверки готовности влияют только на службы, а мы не используем службы, поскольку вся коммуникация обрабатывается RabbitMQ. Прежде всего, определим следующий API в файле Program.cs проекта RoutesPlanning:

app.MapGet("/liveness", () =>
{
 if (MainService.ErrorsCount < 6) return Results.Ok();
 else return Results.InternalServerError();
})
.WithName("GetLiveness");

Код возвращает статус ошибки, если было как минимум 6 последовательных неудачных попыток связи с RabbitMQ. В развертывании RoutesPlanning.yaml необходимо добавить следующий код:

livenessProbe:
 httpGet:
 path: /liveness
 port: 8080
 initialDelaySeconds: 10
 periodSecond

После этого изменения, если хотите, вы можете повторить весь тест Minikube из раздела «Тестирование микросервиса route-matching worker». В следующем разделе описывается структурированный, модульный и эффективный способ обработки взаимодействия между нашим кластером и внешним миром.

Входы (Ingresses)

Большинство микросервисных приложений имеют несколько фронтенд-микросервисов, поэтому для их раскрытия с помощью сервисов LoadBalancer потребуется отдельный IP-адрес для каждого из них. Кроме того, внутри нашего кластера Kubernetes нам не нужна нагрузка HTTPS и сертификатов для каждого микросервиса, поэтому лучшим решением является единственная точка входа для всего кластера с уникальным IP-адресом, который обеспечивает HTTPS-связь с внешним миром, перенаправляя HTTP-связь на службы внутри кластера. Обе эти функции являются типичными для веб-серверов.

Как правило, к каждому IP-адресу привязано несколько доменных имен, и веб-сервер распределяет трафик между несколькими приложениями в соответствии с доменным именем и путем запроса внутри каждого домена. Эта функция веб-сервера называется виртуальным хостингом. Преобразование между HTTPS и HTTP также является особенностью веб-серверов. Оно называется HTTPS терминацией. Наконец, веб-серверы предоставляют дополнительные услуги, такие как фильтрация запросов для предотвращения различных видов атак. В более общем плане, они понимают протокол HTTP и предлагают связанные с HTTP услуги, такие как доступ к статическим файлам и различные виды переговоров с клиентом по протоколу и контенту. С другой стороны, службы LoadBalancer просто обрабатывают протокол TCP/IP более низкого уровня и выполняют некоторую балансировку нагрузки. Поэтому было бы замечательно использовать реальный веб-сервер для взаимодействия нашего кластера Kubernetes с внешним миром вместо нескольких служб LoadBalancer. Kubernetes предлагает возможность запускать реальные веб-серверы внутри ресурсов, называемых Ingresses. Ingresses действуют как интерфейсы между реальным веб-сервером и API Kubernetes и позволяют нам настраивать большинство служб веб-сервера с помощью общего интерфейса, который не зависит от конкретного веб-сервера, находящегося за Ingress. На следующей диаграмме показано, как Ingress распределяет трафик между всеми фронтенд-микрослужбами внутри кластера Kubernetes:

image Figure 8.14: Ingress

Ingress можно создавать в кластере только после установки контроллера Ingress в кластере. Каждая установка контроллера Ingress предоставляет как конкретный веб-сервер, такой как NGINX, так и код, который связывает его с API Kubernetes. Информация о контроллере Ingress и его настройках предоставляется в ресурсе под названием IngressClass, на который есть ссылка в фактическом определении Ingress. Однако часто установки контроллера Ingress уже определяют класс IngressClass по умолчанию, поэтому нет необходимости указывать его имя в определении ingress. Ниже приведено описание того, как определить IngressClass:

apiVersion: networking.k8s.io/v1
kind: IngressClass
metadata:
 labels:
 app.kubernetes.io/component: controller
 name: nginx-example
 annotations:
 ingressclass.kubernetes.io/is-default-class: "true"
spec:
 controller: k8s.io/ingress-nginx
 parameters: # optional parameters that depend on the installed
controller

Каждый класс указывает только имя контроллера (controller), если это класс по умолчанию (анотация …/isdefault-class), и некоторые дополнительные параметры, которые зависят от конкретного контроллера. Ниже приведено описание того, как определить Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
 metadata:
 name: my-example-ingress
 namespace: my-namespace
 # annotations used to configure the ingress
spec:
 ingressClassName: <IngressClass name> # Sometimes it is not needed
 tls: # HTTPS termination data
 ...
 rules: # virtual hosting rules
 ...

Некоторые контроллеры, такие как контроллер на основе NGINX, используют аннотации, размещенные в разделе метаданных, для настройки веб-сервера. Правила прекращения HTTPS (tls) представляют собой пары, состоящие из набора доменных имен и связанных с ними сертификатов HTTPS, где каждый сертификат должен быть упакован как секрет tls (см. подраздел «Секреты»):

tls:
- hosts:
 - www.mydomain.com
 secretName: my-certificate1
- hosts:
 - my-subdomain.anotherdomain.com
 secretName:

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

rules:
- host: *.mydomain.com # leave this field empty to catch all domains
 http:
 paths:
 - path: /
 pathType: Prefix # or Exact
 backend:
 service:
 name: my-service-name
 port:
 number: 80
- host: my-subdomain.anotherdomain.com
...

Сегменты домена могут быть заменены подстановочными знаками (*). Каждое подправило пути указывает имя службы, и весь трафик, соответствующий этому правилу, будет отправлен в эту службу на порт, указанный в правиле. Служба, в свою очередь, пересылает трафик всем соответствующим подсистемам.

Если pathType является префиксом, он будет соответствовать всем путям запроса, которые имеют указанный путь в качестве подсегмента. В противном случае требуется полное совпадение. В приведенном выше примере первое правило соответствует всем путям, поскольку все пути имеют пустой сегмент/ в качестве подсегмента.

Если входной запрос соответствует нескольким путям, предпочтение отдается более конкретному (содержащему больше сегментов).

В следующем подразделе мы применим на практике то, что мы узнали об Ingress, на очень простом примере в Minikube.

Тестирование Ingress с Minikube

Самый простой способ установить контроллер Ingress на основе NGINX в Minikube — это включить дополнение ingress. Поэтому, после запуска Minikube, давайте включим это дополнение:

minikube addons enable ingress В результате в пространстве имен ingress-nginx создаются некоторые Pods. Проверим это с помощью kubectl get pods -n ingress-nginx! Дополнение устанавливает тот же контроллер входа на основе NGINX, который используется в большинстве сред Kubernetes (https://github.com/kubernetes/ingress-nginx?tab=readme-ov-file). При установке также автоматически создается IngressClass с именем nginx. Аннотации, поддерживаемые этим контроллером, перечислены здесь: https://kubernetes.github.io/ingress-nginx/user-guide/ nginx-configuration/annotations/. Папка ch08 репозитория GitHub book содержит файлы IngressExampleDeployment.yaml и IngressExampleDeployment2.yaml. Они определяют два развертывания с соответствующими службами ClusterIP. Они развертывают две разные версии очень простого веб-приложения, которое создает простую HTML-страницу. Как обычно, скопируем два файла .yaml в папку и откроем консоль в этой папке. В качестве первого шага применим эти файлы: kubectl apply -f IngressExampleDeployment.yaml kubectl apply -f IngressExampleDeployment2.yaml

Теперь мы создадим ингресс, который соединяет первую версию приложения с /, а вторую версию приложения с /v2. Имена служб ClusterIP двух развертываний — helloworldingress-service и helloworldingress2-service, и обе принимают на порту 8080. Поэтому нам нужно привязать порт 8080 службы helloworldingress-service к /, а порт 8080 службы helloworldingress2-service к /v2:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
 name: example-ingress
 namespace: basic-examples
spec:
 ingressClassName: nginx
 rules:
 - host:
 http:
 paths:
 - path: /
 pathType: Prefix
 backend:
 service:
 name: helloworldingress-service
 port:
 number: 8080
 - path: /v2
 pathType: Prefix
 backend:
 service:
 name: helloworldingress2-service
 port:
 number: 8080

Стоит отметить, что свойство host пустое, поэтому Ingress не выполняет никакого выбора на основе доменного имени, а выбор микрослужбы основан только на пути. Это был вынужденный выбор, поскольку мы экспериментируем на изолированной машине для разработки без поддержки DNS, поэтому мы не можем связывать доменные имена с IP-адресами.

Давайте поместим вышеуказанный код в файл с именем IngressConfiguration.yaml и применим его: kubectl apply -f IngressConfiguration.yaml Чтобы подключиться к Ingress, нам нужно открыть туннель с виртуальной машиной Minikube. Как обычно, откройте другую консоль и выполните в ней команду minikube tunnel. Помните, что туннель работает, пока это окно остается открытым. Теперь откройте браузер и перейдите по адресу http://localhost. Вы должны увидеть что-то вроде: Hello, world! Version: 1.0.0 Hostname: …… Затем перейдите по адресу http://localhost/v2. Вы должны увидеть что-то вроде: Hello, world! Version: 2.0.0 Hostname: …… Нам удалось разделить трафик между двумя приложениями в соответствии с путем запроса! Когда вы закончите экспериментировать, давайте очистим среду с помощью: kubectl delete -f IngressConfiguration.yaml kubectl delete -f IngressExampleDeployment2.yaml kubectl delete -f IngressExampleDeployment.yaml Наконец, давайте остановим Minikube с помощью: minikube stop. В следующем подразделе объясняется, как установить тот же контроллер Ingress на AKS.

Резюме

В этой главе вы узнали об основах оркестраторов, а затем научились устанавливать и настраивать кластер Kubernetes. В частности, вы узнали, как взаимодействовать с кластером Kubernetes с помощью Kubectl и основных команд Kubectl. Затем вы узнали, как развертывать и обслуживать приложение микрослужб, а также как тестировать его локально с помощью Docker и Minikube. Вы также узнали, как подключить кластер Kubernetes к LoadBalancer и Ingress, а также как настроить его для оптимизации производительности. Все концепции были применены на практике как с помощью простых примеров, так и с помощью более полного примера, взятого из кейса по каршерингу.

Вопросы

  1. Почему приложениям Kubernetes требуется сетевое дисковое хранилище? Потому что POD не могут полагаться на дисковое хранилище узлов, на которых они работают, так как они могут быть перемещены на другие узлы.
  2. Правда ли, что если узел, содержащий Pod развертывания с 10 репликами, выйдет из строя, ваше приложение будет продолжать работать нормально? Да.
  3. Правда ли, что если узел, содержащий Pod StatefulSet с 10 репликами, выйдет из строя, ваше приложение будет продолжать работать нормально? Не обязательно.
  4. Правда ли, что если Pod выходит из строя, он всегда автоматически перезапускается? Да.
  5. Почему StatefulSets нуждаются в шаблонах запросов на постоянный объем вместо запросов на постоянный объем? Потому что каждому POD из StatefulSet требуется свой объем.
  6. В чем заключается полезность запросов на постоянный объем? Они позволяют пользователям Kubernetes динамически запрашивать и управлять ресурсами хранения, отделяя предоставление хранилища от развертывания приложения.
  7. Что более подходит для соединения приложения с тремя различными фронтенд-сервисами: LoadBalancer или ingress? Ingress. LoadBalancers подходят только в том случае, если есть единственный фронтенд-сервис.
  8. Каков наиболее подходящий способ передачи строки подключения контейнеру, работающему в Pod кластера Kubernetes? С помощью секрета Kubernetes, поскольку он содержит конфиденциальную информацию.
  9. Как устанавливаются сертификаты HTTPS в Ingresses? С помощью секрета определенного типа.
  10. Позволяет ли стандартный синтаксис Kubernetes устанавливать сертификат HTTPS на службе LoadBalancer? Нет.

Simplifying Containers and Kubernetes

Хотя Kubernetes, вероятно, является наиболее полнофункциональным оркестратором, любой переход от монолитного разработки к микросервисам на Kubernetes сталкивается с двумя серьезными трудностями. Первая сложность заключается в том, что стоимость кластера Kubernetes часто не оправдывается низким начальным трафиком приложения. Фактически, кластер Kubernetes производственного уровня обычно требует нескольких узлов для обеспечения избыточности и надежности. В то время как самоуправляемые кластеры могут потребовать как минимум два главных узла и три рабочих узла, управляемые службы Kubernetes, такие как Amazon Elastic Kubernetes Service (Amazon EKS), Azure Kubernetes Service (AKS) или Google Kubernetes Engine (GKE), часто обеспечивают избыточность контрольной плоскости при более низких затратах (контрольная плоскость Amazon EKS стоит ~72 доллара в месяц). Команды могут начать с меньших типов экземпляров и масштабироваться по мере необходимости, снижая начальную нагрузку. Другая сложность заключается в кривой обучения самому Kubernetes. Переход всей команды на дискретные знания/опыт Kubernetes может потребовать времени, которого у нас просто нет. Более того, если мы переводим существующее монолитное приложение, в начале перехода — когда количество микросервисов еще невелико, а их организация все еще напоминает организацию монолитного приложения — нам просто не нужны все возможности и опции, предлагаемые Kubernetes.

Предыдущие соображения привели к созданию Azure Container Apps, который является бессерверной альтернативой Kubernetes. Поскольку это бессерверный вариант, вы платите только за то, что используете, и решаете проблему начального порога размера кластера. Azure Container Apps также упрощает процесс обучения благодаря следующим функциям:

  1. В то время как Kubernetes предлагает все компоненты для кодирования как инструментов, так и микрослужб, компонентами Azure Container Apps являются сами микрослужбы, поэтому разработчик может сосредоточиться на бизнес-логике, не тратя слишком много времени на технические детали. Такие инструменты, как решения для хранения данных, брокеры сообщений и другие инструменты для обеспечения производительности и безопасности, берутся из хостинговой платформы, то есть Azure.
  2. Для всего есть приемлемые настройки по умолчанию, поэтому развертывание приложения может стать таким же простым, как выбор образов Docker для развертывания. Настройки также можно указать позже. После краткого описания различных инструментов, используемых для упрощения использования и администрирования кластеров Kubernetes, в этой главе подробно описывается Azure Container Apps и его использование на практике. Эта глава основана на уже имеющихся знаниях о Kubernetes, поэтому прочитайте ее после изучения главы 8 «Практическая организация микрослужб с помощью Kubernetes». В частности, в этой главе рассматриваются следующие темы: • Инструменты для упрощения использования и администрирования кластеров Kubernetes • Основы и планы Azure Container Apps • Развертывание приложения микрослужб с помощью Azure Container Apps

Инструменты для упрощения использования и администрирования кластеров Kubernetes

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

  1. Инструменты для упаковки библиотек и приложений.
  2. Графические интерфейсы Kubernetes.
  3. Административные инструменты для сбора и представления различных метрик кластера, обработки сигналов тревоги
  4. Административные инструменты для сбора и представления различных метрик кластера, обработки сигналов тревоги и выполнения административных действий.
  5. Инструменты для управления всем процессом разработки и развертывания приложений на основе микросервисов, которые включают Kubernetes в качестве целевой платформы развертывания.
  6. Программные среды, построенные на базе Kubernetes. К ним относятся как вертикальные приложения, такие как инструменты машинного обучения и больших данных, так и универсальные программные среды, такие как Azure Container Apps. Что касается инструментов упаковки, наиболее актуальным является Helm, который стал де-факто стандартом для упаковки приложений и библиотек Kubernetes. Мы проанализируем его в следующем подразделе.

Helm и Helm-диаграммы

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

image Figure 9.1: Folder structure of a Helm chart

Файлы .yaml, относящиеся к приложению, размещаются в верхнем каталоге шаблонов, а каталог charts может содержать другие диаграммы Helm, используемые в качестве вспомогательных библиотек. Файл Chart. yaml верхнего уровня содержит общую информацию о пакете (название и описание), а также версию приложения и версию диаграммы Helm. Ниже приведен типичный пример:

apiVersion: v2
name: myhelmdemo
description: My Helm chart
type: application
version: 1.3.0
appVersion: 1.2.0

Здесь тип может быть либо приложением, либо библиотекой. Развертывать можно только диаграммы приложений, а диаграммы библиотек являются утилитами для разработки других диаграмм. Диаграммы библиотек размещаются в папке charts других диаграмм Helm. Для настройки каждой конкретной установки приложения файлы Helm chart .yaml содержат переменные, которые указываются при установке Helm charts. Кроме того, Helm charts также предоставляют простой язык шаблонов, который позволяет включать некоторые декларации только в том случае, если выполняются определенные условия, зависимые от входных переменных. Файл values.yaml верхнего уровня объявляет значения по умолчанию для входных переменных, что означает, что разработчику необходимо указать только несколько переменных, для которых требуются значения, отличные от значений по умолчанию. Мы не будем описывать язык шаблонов Helm chart, поскольку это было бы слишком обширным, но вы можете найти его в официальной документации Helm, на которую есть ссылка в разделе «Дополнительная литература». Helm-чарты обычно организованы в публичных или частных репозиториях аналогично образам Docker. Существует клиент Helm, который можно использовать для загрузки пакетов из удаленного репозитория и установки чартов в кластерах Kubernetes. Клиент Helm можно установить на любом компьютере с установленным kubectl через диспетчер пакетов Chocolatey следующим образом: choco install kubernetes-helm В свою очередь, процедуру установки Chocolatey вы можете найти в разделе «Технические требования» главы 8 «Практическая организация микросервисов с помощью Kubernetes». Helm работает с текущим кластером Kubernetes kubectl и пользователем. Перед использованием пакетов удаленного репозитория необходимо добавить его, как показано в следующем примере: helm repo add https://mycharts.helm.sh/stable

Предыдущая команда делает информацию о пакетах удаленного репозитория доступной локально и присваивает этому удаленному репозиторию локальное имя. Информацию обо всех диаграммах, доступных в одном или нескольких репозиториях, можно обновить с помощью следующей команды: helm repo update <my-repo-local-name 1> <my-repo-local-name 2>… Если имя репозитория не указано, обновляются все локальные репозитории. После этого любой пакет из удаленного репозитория можно установить с помощью такой команды, как следующая: helm install <имя экземпляра> /<имя пакета> -n <пространство имен> Здесь <пространство имен> — это пространство имен Kubernetes, в которое будет установлено приложение. Как обычно, если оно не указано, используется пространство имен по умолчанию. — это имя пакета, который вы хотите установить, и, наконец, — это имя, которое вы даете установленному приложению. Это имя необходимо для получения информации об установленном приложении с помощью следующей команды: helm status Вы также можете получить информацию обо всех приложениях, установленных с помощью Helm, с помощью следующей команды: helm ls Имя приложения также необходимо для удаления приложения из кластера с помощью следующей команды: helm delete <имя экземпляра> При установке приложения мы также можем предоставить файл .yaml со всеми значениями переменных по умолчанию, которые мы хотим переопределить. Мы также можем указать конкретную версию диаграммы Helm; в противном случае будет использована самая последняя версия. Вот пример с переопределением версии и значений: helm install <имя экземпляра> /<имя пакета> -f values.yaml --version <версия> Наконец, переопределение значений по умолчанию также можно указать с помощью опции --set, как показано здесь: ...--set =,=...

Мы также можем обновить существующую установку с помощью команды upgrade, как показано здесь: helm upgrade <имя экземпляра> /<имя пакета>... Команда upgrade может указывать новые значения переопределений с помощью опции –f или --set, а также может указывать новую версию для установки с помощью --version. Если версия не указана, устанавливается более поздняя версия. Более подробную информацию о Helm можно найти в официальной документации по адресу https://helm.sh/. Мы покажем, как использовать Helm на практике, в следующем подразделе, посвященном инструментам администрирования Kubernetes.

Графические интерфейсы Kubernetes

Существуют также инструменты, которые помогают определять и развертывать ресурсы Kubernetes с помощью удобных графических интерфейсов. Среди них стоит упомянуть ArgoCD и Rancher UI. ArgoCD обрабатывает базу данных ресурсов Kubernetes и автоматически обновляет кластер Kubernetes при каждом изменении кода, определяющего ресурс. ArgoCD значительно упрощает управление кластером Kubernetes, но автоматическое повторное развертывание ресурсов может вызвать проблемы в производственных средах, где требуется нулевое время простоя. Мы не будем описывать ArgoCD здесь, но заинтересованные читатели могут найти более подробную информацию в разделе «Дополнительная литература». Rancher UI позволяет пользователям взаимодействовать с несколькими кластерами Kubernetes через веб-интерфейс. Он также имеет инструменты для управления всем процессом разработки, такие как определение проектов. Веб-приложение Rancher UI должно быть доступно из каждого кластера Kubernetes, которым оно должно управлять, и требует установки программного обеспечения внутри каждого из кластеров Kubernetes, которыми оно должно управлять. Rancher UI также можно установить на локальном компьютере разработчика, где его можно использовать для взаимодействия с minikube. Самый простой способ выполнить локальную установку — через Docker. Откройте оболочку Linux и введите следующий код:

docker run -d \
 --restart unless-stopped \
 -p 80:80 \
 -p 443:443 \
 --privileged \
 --name rancher \
 rancher/rancher:stable

Через несколько минут после завершения установки интерфейс Rancher UI будет доступен по адресу https://localhost. Если вы не можете получить к нему доступ, подождите минуту и повторите попытку. При первом появлении веб-интерфейса вам понадобится временный пароль. Вы можете получить этот пароль с помощью следующей команды Linux: docker logs rancher 2>&1 | grep «Bootstrap Password:» Скопируйте временный пароль на начальной странице интерфейса Rancher и нажмите «Continue» (Продолжить). На новой странице должно появиться предложение ввести новый постоянный пароль для администратора и URL-адрес, который будет использоваться minikube для доступа к интерфейсу Rancher. Заполните эту страницу, как показано здесь:

image Figure 9.2: Rancher initial settings

Примите предложенный пароль, скопируйте его и сохраните в надежном месте. Имя хоста host.docker.internal позволяет minikube подключаться к нашему локальному хосту. На панели управления нажмите кнопку «Импортировать существующий», чтобы начать процесс подключения существующего кластера к интерфейсу Rancher:

image Figure 9.3: Importing an existing cluster

На новой странице, которая появится, выберите опцию «Общий кластер»:

image Figure 9.4: Generic cluster option

Введите только название кластера и описание на появившейся странице, как показано здесь:

image Figure 9.5: Filling in the cluster information

Затем нажмите кнопку «Создать». Должна появиться страница с кодом для запуска в вашем кластере. Вы должны выбрать второй вариант кода, поскольку локальная установка Rancher использует самоподписанный сертификат, который должен выглядеть примерно так: curl --insecure -sfL https://host.docker.internal/v3/import/6rd2jg4nntmkkw 9z9mjhttrjfjj64cz9vl8zr6pr6tskbt6cc98zfz_c-2p47w.yaml | kubectl apply -f - Однако этот код должен быть выполнен в оболочке Linux, а kubectl установлен только в Windows. Поэтому замените предыдущую инструкцию следующей: curl --insecure -sfL https://host.docker.internal/v3/import/6rd2jg4nntmkkw 9z9mjhttrjfjj64cz9vl8zr6pr6tskbt6cc98zfz_c-2p47w.yaml > install.yaml

Затем запустите его в оболочке Linux. Будет создан файл install.yaml, содержащий наш код Kubernetes.

Теперь мы можем установить Rancher на minikube. Убедитесь, что minikube запущен, откройте консоль Windows и выполните следующую команду: kubectl apply -f install.yaml По завершении установки вернитесь на панель управления; вы должны увидеть вновь импортированный кластер minikube:

image Figure 9.6: Minikube cluster connected

Щелкните ссылку minikube и наслаждайтесь возможностями взаимодействия с Minikube через графический интерфейс пользователя! Здесь вы можете видеть узлы, поды, пространства имен и все типы ресурсов Kubernetes, а также определять новые ресурсы. Когда вы закончите экспериментировать, остановите minikube и контейнер Rancher в интерфейсе Docker. Если вам больше не нужно взаимодействовать с minikube через Rancher, просто выполните следующее: kubectl delete -f install.yaml

Административные инструменты Kubernetes

Каждый поставщик облачных услуг предлагает административные пользовательские интерфейсы вместе с продуктом Kubernetes. Эти пользовательские интерфейсы включают в себя возможность выполнять действия над кластером, такие как проверка ресурсов Kubernetes, сбор различных метрик, а также запрос и построение графиков этих метрик. Мы более подробно проанализируем административные инструменты, предлагаемые Azure, в главе 10 «Безопасность и наблюдаемость для бессерверных приложений и микросервисов».

Однако существует также несколько инструментов, предлагаемых третьими сторонами, а также несколько проектов с открытым исходным кодом. Среди проектов с открытым исходным кодом стоит упомянуть сборщик метрик под названием Prometheus и административную консоль на основе пользовательского интерфейса под названием Grafana. Обычно они устанавливаются вместе, и Prometheus работает как источник метрик для Grafana. Их можно установить на любой кластер Kubernetes, включая minikube. Подробное описание этих инструментов выходит за рамки данной книги, но поскольку они очень распространены и являются необходимым условием для работы других инструментов, мы опишем, как их установить. Если вы хотите протестировать эти инструменты на minikube, вам понадобится конфигурация с большим объемом памяти и некоторыми другими настройками, поэтому лучшим вариантом будет определить новый профиль при запуске minikube с помощью следующей команды: minikube start --memory=6g --extra-config=kubelet.authentication-tokenwebhook=true --extra-config=kubelet.authorization-mode=Webhook --extraconfig=scheduler.bind-address=0.0.0.0 --extra-config=controller-manager. bind-address=0.0.0.0 -p <имя вашего профиля> Здесь опция --extra-config позволяет настроить различные параметры установки Kubernetes. Если вы не используете minikube, вы должны убедиться, что кластер Kubernetes настроен с параметрами, переданными с помощью --extra-config в предыдущем инструкции. Эти настройки включают веб-хуки на контроллере-менеджере, которые Prometheus использует для сбора своих метрик, и изменяют IP-адреса, открытые как контроллером, так и планировщиком на главных узлах, чтобы обеспечить совместимость с Prometheus. После того, как все эти настройки будут исправлены, мы сможем установить Prometheus и Grafana с помощью Helm: helm repo add prometheus-community https://prometheus-community.github.io/ helm-charts helm repo add grafana https://grafana.github.io/helm-charts helm repo update helm install prometheus prometheus-community/prometheus --namespace monitoring --create-namespace helm install grafana grafana/grafana --namespace monitoring

Первые две команды добавляют репозитории, содержащие Prometheus и Grafana, соответственно, а третья команда обновляет все локальные каталоги репозитория. Третья команда устанавливает Prometheus в пространстве имен мониторинга после создания этого пространства имен, и, наконец, последняя команда устанавливает Grafana в том же пространстве имен. После установки мы можем проверить пространство имен мониторинга, чтобы убедиться, что все ресурсы готовы: kubectl get all -n monitoring Наконец, к пользовательским интерфейсам Prometheus и Grafana можно получить доступ путем перенаправления портов соответствующих сервисов. Не забудьте использовать отдельное окно консоли для каждого сервиса перенаправления портов, так как консоль зависает во время перенаправления портов: kubectl --namespace monitoring port-forward service/prometheus-server 9090:80 kubectl --namespace monitoring port-forward service/grafana 3000:80 После этого Prometheus будет доступен по адресу http://localhost:9090, а Grafana — по адресу http:// localhost:3000. Prometheus не требует входа в систему, а для Grafana пользователем по умолчанию является admin, а пароль необходимо извлечь из секрета Kubernetes, как показано здесь: kubectl get secret --namespace monitoring grafana -o jsonpath="{.data. admin-password}" Скопируйте строку, возвращенную предыдущим командой; нам нужно декодировать ее с помощью Base64, чтобы получить фактический пароль. Как обычно, декодирование Base64 можно выполнить, открыв консоль Linux и используя команду base64: echo -n <строка для декодирования> | base64 -d После входа в Grafana мы должны объявить Prometheus в качестве источника данных метрик. В левом меню Grafana перейдите в раздел «Соединения» -> «Источники данных», а затем выберите «Добавить новый источник данных». На появившейся странице выберите Prometheus, как показано на следующем рисунке:

image Figure 9.7: Selecting Prometheus as the data source

Нам необходимо настроить Prometheus в качестве источника данных по умолчанию и установить URL-адрес, по которому будут извлекаться все метрики, равным http://prometheus-server:80, что соответствует адресу и порту того же сервиса Prometheus, для которого мы настроили перенаправление портов, как показано здесь:

image Figure 9.8: Prometheus settings

Вы можете оставить все остальные настройки по умолчанию; просто нажмите кнопку «Сохранить и протестировать». После этого перейдите на вкладку «Дашборды» и импортируйте все предложенные дашборды. Затем перейдите в раздел «Дашборды» в левом меню Grafana и просмотрите все импортированные дашборды, щелкнув по их ссылкам:

image Figure 9.9: Available dashboards

Если вы нажмете «Новый», а затем «Импорт», вы сможете импортировать панель инструментов с сайта grafana.com. Просто перейдите по ссылке grafana.com/dashboards, выберите панель инструментов, скопируйте ее ID, как показано здесь:

image Figure 9.10: Importing a dashboard from grafana.com

Возможно, вам потребуется подписаться, чтобы получить идентификатор панели управления. Подписка бесплатна. Страницы выбора панели управления содержат ссылки на документацию, которая может вас заинтересовать.

Если вы остановите minikube с помощью minikube stop -p <имя профиля>, minikube будет остановлен, но все ваши данные будут сохранены, поэтому вы сможете продолжить экспериментировать с Grafana. Если вы хотите удалить Grafana и Prometheus, вы можете сделать это с помощью Helm, как показано здесь: helm delete grafana helm delete prometheus Завершим этот раздел остальными инструментами.

Среды разработки на базе Kubernetes

Среди комплексных платформ разработки на базе Kubernetes стоит упомянуть OpenShift (https://www.redhat.com/en/technologies/cloud-computing/openshift), которая включает в себя инструменты для всего процесса разработки, включая автоматизацию DevOps и облачные сервисы. OpenShift можно установить локально или использовать как сервис PaaS, доступный в основных облачных сервисах, включая Azure (https://azure.microsoft.com/it-it/products/openshift). Фреймворки больших данных и машинного обучения используют Kubernetes, но мы не будем их обсуждать, поскольку они полностью выходят за рамки этой книги. Стоит также упомянуть простые генераторы кода, предлагаемые некоторыми стартапами, которые создают приложения Kubernetes, комбинируя контейнеры с помощью графических интерфейсов. Разумеется, подобные инструменты предназначены только для создания недорогих приложений. Мы не будем их описывать, поскольку в центре внимания книги находятся высококачественные корпоративные приложения, а на данный момент не существует ни общего шаблона, ни конкретной платформы. Вместо этого, когда речь идет об альтернативах Kubernetes более высокого уровня абстракции, построенных на основе Kubernetes, на момент написания этой книги наиболее актуальным вариантом является Azure Container Apps, который будет описан в остальной части главы.

⚠️ **GitHub.com Fallback** ⚠️