post-thumb

kubernetes

Kubernetes, также известный как K8s, представляет собой систему с открытым исходным кодом для автоматизации развертывания, масштабирования и управления контейнерными приложениями.

Ваше приложение будет работать в кластере (cluster) — группе однородных вычислительных ресурсов, если говорить в общих терминах. В большинстве случаев это стойка с Linux/UNIX-серверами одного типа (server rack).

Элемент кластера, то есть сервер Linux или похожий на него ресурс, будет называться узлом (node, cluster node, иногда worker node — рабочий узел).

Все ваши сервисы, микро или чуть больше, или непосредственно приложения будут работать в контейнерах, размещенных в узлах кластера. Как правило, контейнеры будут работать под управлением Docker или аналогичной системы запуска контейнеров, например containerd или rkt.

Есть только одно важное отличие от простого вызова docker run — контейнеры будут работать не в самом узле, а в своем небольшом пространстве, называемом отсеком (pod). В отсеке могут выполняться несколько контейнеров одновременно.

  • Kubernetes работает с кластером — группой серверов или похожих вычислительных ресурсов, с помощью которых он будет исполнять ваши сервисы или приложения.
  • Главная единица сервиса или приложения, которая будет исполняться на кластере, — это образ (image) контейнера, в котором содержится полное описание приложения, его зависимости, инструменты и библиотеки, открытые порты и требуемое хранилище, а также команды, необходимые для запуска. Кроме этого образа, Kubernetes ничего не знает про приложение.
  • Контейнеры запускаются на узлах кластеров в отдельных пространствах, называемых отсеками (pod).
  • Как правило, один из узлов в кластере является управляющим (на нем работает плоскость управления). Он распоряжается запуском и масштабированием контейнеров на всех остальных узлах.
  • На каждом узле кластера установлен агент Kubernetes, так называемый кублет. Он получает команды от управляющего узла и запускает, останавливает и проверяет состояние отсеков с контейнерами. Только узлы, на которых работает кублет, доступны для управления Kubernetes.
  • Наконец, сам управляющий узел получает команды от вас — с помощью командной строки и команды kubectl или через разнообразные варианты пользовательских интерфейсов, созданных для управления Kubernetes. Командой, как правило, является развертывание образа вашего контейнера с описанием его точек доступа и желаемым уровнем и способом масштабирования — например, автоматическим в зависимости от загрузки или сразу с определенным количеством работающих экземпляров на разных узлах и отсеках.
  • Kubernetes — это система развертывания и масштабирования, которая запускает ваши сервисы и приложения из образов, в которые они упакованы, развертывает и запускает их по всему пространству ресурсов, доступных в кластере, и далее следит за работающими экземплярами. Часто говорят, что подобная система занимается оркестровкой контейнеров (orchestration).

Для работы с любыми ресурсами и API Kubernetes требуется интерпретатор команд Kubernetes kubectl.

Получить первую информацию о структуре нашего кластера с помощью команд:

  • kubectl config current-context (получить название контекста kubectl, то есть именованного набора адресов, управляющего узла, ключей доступа и остальных данных о текущем кластере)
  • kubectl cluster-info (сетевые адреса управляющего кластера)
  • kubectl get nodes (список узлов кластера).

Чтобы управляющая среда Kubernetes понимала, откуда ей нужно взять образ контейнера с нашим микросервисом, нужно разместить его в доступном ей репозитории образов контейнеров. Для простого тестирования проще и популярнее всего создать аккаунт в открытом репозитории Docker Hub или загрузить его в репозиторий контейнеров, который предоставляют все популярные провайдеры облачных услуг (это будет ваше закрытое личное хранилище образов в облаке — к примеру, Artifact Registry для Google Cloud).

Поступим наиболее простым способом и загрузим образ в репозиторий Docker Hub.

$ docker push {учетная_запись_Docker}/time-service:0.1.0

Развертывание (deployment) Kubernetes

У нас есть работающий кластер, доступ к нему, и собранный микросервис, упакованный в образ (image) контейнера, доступный в хранилище Docker Hub. Дальше нам нужно развернуть (deploy) наш микросервис из этого образа, а после развертывания получить его параметры и настроить доступ к его точкам доступа (endpoints), потенциально через внешний адрес в Интернете (если это реальный облачный кластер).

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

Следующая простая команда сделает все, что нам нужно, — нам нужно просто будет напрямую указать, что мы создаем развертывание на основе образа контейнера (create deployment):

$ kubectl create deployment time-service –image {ваша_учетная_запись_Docker}/t_-service:0.1.0_deployment.apps_time-service_

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

После запуска посмотреть состояние развертывания (deployment) можно столь же логичной командой kubectl get:

kubectl get deployments

NAME           READY   UP-TO-DATE   AVAILABLE   AGE

time-service   1/1              1                     1                  18s

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

Наш микросервис для получения времени был развернут простой командой kubectl, поэтому по умолчанию он работает в единственном экземпляре, в созданном для него отсеке (pod). Посмотреть детали работающих на данный момент отсеков в кластере можно следующим образом (вывод указан для локального кластера minikube):

$ kubectl get pods -o wide

Мы использовали расширенную форму команды (-o wide), чтобы увидеть, на каком узле развернут наш отсек. Легко видеть, что отсек создан для нашего сервиса — его имя содержит названия сервиса (time-service). Чтобы убедиться, что именно в этом отсеке работает наш контейнер, и получить полную информацию о нем, выполним команду describe, указав имя отсека:

$ kubectl describe pod time-service-7c886f94bf-gwk4x

Отладка развертывания

Вполне может быть, что развернуть наш микросервис вам по какой-либо причине не удасться - и вместо заветных цифр (1/1 - все отсеки развернуты и готовы) вы увидите 0/1. Лучший способ понять, что происходит - все та же команда describe:

$ kubectl describe deployment time-service

$ kubectl describe pod time-service-{…}

Команда describe содержит подробное описание всех свойств развертывания или отсека (pod) - в том случае если отсек был создан. Более того, в конце команды вы найдете список событий (events) Kubernetes - своеобразный журнал всех управляющих команд, выполненных для создания вашего развертывания. Как правило, в нашем простом случае ошибка может быть в доступе к образу time-service - поищите в выводе команды статус ошибки ErrImagePull. Если вы видите такую ошибку, проверьте имя образа, его метку (версию), проверьте, что образ свободно доступен через Docker Hub. В случае использования minikube, загрузите локальный образ в minikube c помощью команды image load.

Еще один способ проверить, что происходит в кластере в целом - получить список событий целиком.

1 $ kubectl get events

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

В мире Kubernetes доступ к портам работающих приложений открывается и управляется через сервисы (services). Сервис — это служебный объект Kubernetes с настройками, которые позволяют управляющей системе кластера понять, какие порты открываются приложением, как обеспечить к ним доступ и в каких отсеках находятся работающие экземпляры приложения, особенно в том случае, когда приложение масштабировано и работает во множественных экземплярах, каждый из которых находится в своем отсеке. Для создания сервиса, с помощью которого мы будем получать доступ к нашему развертыванию и портам своего приложения, вызовем команду expose. Ей достаточно указать, какое развертывание мы собираемся открывать и какой порт нужно будет открыть. По умолчанию сервис будет доступен только внутри кластера (что, конечно же, имеет смысл для взаимодействия множественных сервисов, работающих в одном кластере), но нам хотелось бы попробовать его в деле прямо сейчас. В этом нам поможет более расширенная версия сервиса Kubernetes с названием NodePort, создадим мы ее следующим образом:

kubectl expose deployment time-service –port=8080 –type=NodePort

service “time-service” exposed

Сервис с типом NodePort создает прокси-доступ к нашему сервису и его открытому порту 8080 на каждом узле кластера, так что мы можем отправить к нему запрос, если у нас есть доступ к какому-либо узлу. Прежде чем сделать это, давайте посмотрим, какой порт теперь будет использоваться для нашего сервиса, посмотрев краткий список сервисов (конечно же, снова пригодится команда get):

$ kubectl get services

NAME           TYPE        CLUSTER-IP       EXTERNAL-IP   PORT(S)          AGE

kubernetes     ClusterIP   10.96.0.1                443/TCP          1d

time-service   NodePort    10.110.186.179           8080:30130/TCP   3m

Команда get services получит список всех доступных в кластере сервисов, включая сам управляющий сервис Kubernetes. Мы увидим, что наш сервис был создан с таким же именем, как и само развертывание (time-service), и в колонке портов указан порт, через который мы теперь можем получить доступ к нашему сервису, — 30130 (в вашем случае номер может быть другим, по умолчанию из диапазона, который вы можете найти в документации или исходном коде Kubernetes).

Для локальных кластеров minikube и docker у нас есть прямой доступ к нашему виртуальному кластеру, и мы сможем вызвать свой сервис через полученный прокси-порт. Для локального кластера Docker управляющий узел доступен прямо на локальном адресе localhost:

$ curl localhost:30130/time

{“time”:“2021-09-26 16:26:10.4901133 +0000 UTC m=+227950.182386538”}

В случае minikube виртуальная машина имеет отдельный сетевой адрес, и узнать его адрес проще всего, используя встроенную команду minikube service –url, указав имя сервиса, доступ к которому мы хотели бы получить:

$ minikube service –url time-service

http://192.168.64.3:30130

$ curl http://192.168.64.3:30130/time

{“time”:“2021-09-26 16:28:12.6901133 +0000 UTC m=+227950.182386538”}

Отладка сервисов — переадресация портов

Если вы развернули сервисы вашего приложения на публичных облаках, в удаленных центрах данных, прямого доступа к узлам кластера, на которых развернуты отсеки Kubernetes и контейнеры с вашими сервисами, у вас нет, если только вы не запросите для этих узлов публичные IP-адреса, что, как правило, связано с дополнительными затратами. Тем не менее необходимость проверить работоспособность сервисов и отладить (debug) их функциональность в реальном кластере, а не локальной версии minikube или Docker, возникает постоянно. Использование балансировщика нагрузки для отладки своих сервисов также связано с вопросами безопасности и стоимости — доступный в Интернете IP-адрес, даже кратковременно и для отладки, неминуемо привлечет внимание, и незащищенный доступ к сервису, обладающему доступом к чувствительным данным, особенно опасен. Балансировщики нагрузки основных провайдеров облака (Amazon, Azure и Google) к тому же, как правило, довольно дороги.

Как раз для такого случая в Kubernetes предусмотрена временная переадресация портов, с помощью все той же команды kubectl. Вместо того чтобы открывать сервис всем опасностям Интернета, вы можете временно получить доступ к любому порту любого сервиса через управляющий узел (плоскость управления), к которому у вас всегда есть доступ (иначе управлять кластером просто не получится). Делается это простой командой:

$ kubectl port-forward service/time-service 8080

Forwarding from 127.0.0.1:8080 -> 8080

Forwarding from [::1]:8080 -> 8080

. оставляем команду запущенной

. вызываем сервис time-service через curl localhost:8080/time ..

Handling connection for 8080

Обратите внимание, команда port-forward должна продолжать работать в отдельном процессе (можно запустить ее с амперсандом (&) в конце, чтобы она работала в фоновом режиме и не блокировала терминал).

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

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

$ kubectl port-forward service/time-service 9999:8080

Forwarding from 127.0.0.1:9999 -> 8080

.. вызываем сервис time-service через порт 9999

Вызвать наш микросервис теперь проще простого, даже если он работает в удаленном кластере в центре данных в Сибири (впрочем, то же самое будет работать, если это просто minikube):

1 $ curl localhost:9999/time

2 {“time”:“2022-05-20 02:07:49.356305898 +0000 UTC m=+1843.034870684”}

Переадресация работает, пока активен запущенный процесс kubectl port-forward. Как только процесс заканчивает работу, заканчивается и переадресация.

Доступ к сервису из Интернета — балансировщик нагрузки

Если вы используете кластер одного из публичных провайдеров облака и все ваши отсеки и развертывания находятся на удаленных серверах и виртуальных машинах, получить доступ к узлам этого кластера из Интернета не получится, если только вы не используете дополнительную аутентификацию. Для получения доступа к сервису извне, из «большого Интернета», например, чтобы предоставить доступ к сервису всем пользователям или внешним элементам нашей системы (чаще всего это пользовательский интерфейс UI), мы можем использовать доступный во всех провайдерах облака балансировщик нагрузки (load balancer):

1 $ kubectl expose deployment time-service –type “LoadBalancer”

Команда здесь также проста — мы просим открыть во внешний Интернет (expose) наш сервис и указываем, что все запросы к нему должны будут проходить через встроенный в Kubernetes балансировщик нагрузки — мы мгновенно получаем самую распространенную схему распределения вычислительной нагрузки и потенциальных запросов к нашему сервису в кластере, не заботясь о мелочах и деталях. Балансировщик нагрузки требует публичного IP-адреса, поэтому в локальных кластерах недоступен.

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

$ kubectl get service time-service

NAME           TYPE           CLUSTER-IP      EXTERNAL-IP     PORT(S)          AGE

time-service   LoadBalancer   10.63.254.246   <внешний адрес>   8080:30875/TCP   18h

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

И еще одно: если вы экспериментируете с коммерческим облаком, не забывайте, что балансировщик нагрузки и внешний IP-адрес довольно дороги, удалите их сразу после экспериментов.

По материалам книги Программирование Cloud Native. Микросервисы, Docker и Kubernetes (Иван Портянкин, cloud-native-docker-k8s)

comments
comments powered by Disqus