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 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 deploymentsNAME READY UP-TO-DATE AVAILABLE AGEtime-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.
Еще один способ проверить, что происходит в кластере в целом - получить список событий целиком.
$ kubectl get eventsДля большого кластера информации здесь будет слишком много, но для наших начальных экспериментов полный список событий даст крайне полезную картину всего происходящего в кластере.
В мире Kubernetes доступ к портам работающих приложений открывается и управляется через сервисы (services). Сервис — это служебный объект Kubernetes с настройками, которые позволяют управляющей системе кластера понять, какие порты открываются приложением, как обеспечить к ним доступ и в каких отсеках находятся работающие экземпляры приложения, особенно в том случае, когда приложение масштабировано и работает во множественных экземплярах, каждый из которых находится в своем отсеке. Для создания сервиса, с помощью которого мы будем получать доступ к нашему развертыванию и портам своего приложения, вызовем команду expose. Ей достаточно указать, какое развертывание мы собираемся открывать и какой порт нужно будет открыть. По умолчанию сервис будет доступен только внутри кластера (что, конечно же, имеет смысл для взаимодействия множественных сервисов, работающих в одном кластере), но нам хотелось бы попробовать его в деле прямо сейчас. В этом нам поможет более расширенная версия сервиса Kubernetes с названием NodePort, создадим мы ее следующим образом:
kubectl expose deployment time-service --port=8080 --type=NodePortservice "time-service" exposedСервис с типом NodePort создает прокси-доступ к нашему сервису и его открытому порту 8080 на каждом узле кластера, так что мы можем отправить к нему запрос, если у нас есть доступ к какому-либо узлу. Прежде чем сделать это, давайте посмотрим, какой порт теперь будет использоваться для нашего сервиса, посмотрев краткий список сервисов (конечно же, снова пригодится команда get):
$ kubectl get servicesNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEkubernetes ClusterIP 10.96.0.1 443/TCP 1dtime-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-servicehttp://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 8080Forwarding from 127.0.0.1:8080 -> 8080Forwarding from \[::1\]:8080 -> 8080- оставляем команду запущенной
- вызываем сервис time-service через curl localhost:8080/time ..
Обратите внимание, команда port-forward должна продолжать работать в отдельном процессе (можно запустить ее с амперсандом (&) в конце, чтобы она работала в фоновом режиме и не блокировала терминал).
Как видно, мы просто указываем, какой порт (оригинальный порт, открытый контейнером, в котором находится наш сервис) нужно переадресовать на нашу локальную машину. По умолчанию порт на локальной машине будет совпадать с портом контейнера. Каждый раз при доступе к порту через переадресацию команда будет печатать диагностическое сообщение.
Если часто используемые порты на вашей машине уже заняты (как используемый нами 8080), можно указать, на какой порт будет осуществляться переадресация — просто укажите его еще одним параметром, перед портом контейнера, через двоеточие:
$ kubectl port-forward service/time-service 9999:8080Forwarding from 127.0.0.1:9999 -> 8080.. вызываем сервис time-service через порт 9999
Вызвать наш микросервис теперь проще простого, даже если он работает в удаленном кластере в центре данных в Сибири (впрочем, то же самое будет работать, если это просто minikube):
$ curl localhost:9999/time{"time":"2022-05-20 02:07:49.356305898 +0000 UTC m=+1843.034870684"}Переадресация работает, пока активен запущенный процесс kubectl port-forward. Как только процесс заканчивает работу, заканчивается и переадресация.
Доступ к сервису из Интернета — балансировщик нагрузки
Если вы используете кластер одного из публичных провайдеров облака и все ваши отсеки и развертывания находятся на удаленных серверах и виртуальных машинах, получить доступ к узлам этого кластера из Интернета не получится, если только вы не используете дополнительную аутентификацию. Для получения доступа к сервису извне, из «большого Интернета», например, чтобы предоставить доступ к сервису всем пользователям или внешним элементам нашей системы (чаще всего это пользовательский интерфейс UI), мы можем использовать доступный во всех провайдерах облака балансировщик нагрузки (load balancer):
$ kubectl expose deployment time-service --type "LoadBalancer"Команда здесь также проста — мы просим открыть во внешний Интернет (expose) наш сервис и указываем, что все запросы к нему должны будут проходить через встроенный в Kubernetes балансировщик нагрузки — мы мгновенно получаем самую распространенную схему распределения вычислительной нагрузки и потенциальных запросов к нашему сервису в кластере, не заботясь о мелочах и деталях. Балансировщик нагрузки требует публичного IP-адреса, поэтому в локальных кластерах недоступен.
К этому моменту у нас окончательно все развернуто и готово, мы можем показать параметры и внешний адрес сервиса:
$ kubectl get service time-serviceNAME TYPE CLUSTER-IP EXTERNAL-IP PORT(S) AGEtime-service LoadBalancer 10.63.254.246 внешний адрес 8080:30875/TCP 18hЧерез некоторое время в столбце EXTERNAL-IP появится внешний адрес нашего сервиса, и мы сможем получить к нему доступ из любой точки мира. Конечно, большая часть микросервисов стандартных приложений будет исключительно для внутреннего использования и вспомогательными, и во внешний мир мы будем выставлять конечную точку вводных данных от пользователей или других приложений, как правило, веб-сервер, такой как nginx, или доступную для внешних потребителей версию доступа к интерфейсу REST, со всеми предосторожностями и соображениями безопасности, обязательными для сервисов, напрямую доступных через Интернет.
И еще одно: если вы экспериментируете с коммерческим облаком, не забывайте, что балансировщик нагрузки и внешний IP-адрес довольно дороги, удалите их сразу после экспериментов.
По материалам книги Программирование Cloud Native. Микросервисы, Docker и Kubernetes ([Иван Портянкин](https://leanpub.com/u/ivanporty), [cloud-native-docker-k8s](https://leanpub.com/cloud-native-docker-k8s "cloud-native-docker-k8s"))