Эта статья предназначена для быстрого старта в Kubernetes. Цель статьи – максимально просто рассказать о среде Kubernetes так, чтобы вы сразу могли начать работать с ней. Я специально опущу такие вещи, как: пространство имен, задачи, расписания, версионность, репликация, авто-масштабирование, авто определение сервисов, супервайзинг и прочие страшные слова. Оставим их, как бонус, на потом. Мы так же не будем углубляться в архитектуру k8s, это тема для отдельной статьи.
Также мы писали про Обзор k9s — продвинутого терминального интерфейса для Kubernetes.
Что такое Kubernetes?
Для простоты будем представлять его, как менеджер контейнеров.
Контейнер – это изолированная среда для работы какого-то приложения. Чаще всего на данный момент таким контейнером служит Docker. Наверняка вам приходилось запускать приложения в Docker-е. А может и несколько приложений, работающий совместно, используя docker-compose.
Pod
В k8s один экземпляр приложения называется pod (от англ. – стручок). Контейнеры в нем – горошины. Дело в том, что kubernetes позволяет запускать несколько контейнеров в одном pod. Все контейнеры внутри pod, как правило, объединены одной логикой. К примеру pod может называться web, а в нем 2 контейнера: php и db.
Запущенный Docker контейнер – это горошина в стручке
Чаще всего внутри pod вы найдете всего один контейнер – само приложение, и это нормально. Вторыми, третьими контейнерами там как правило бывают служебные (типа сбора метрик, адаптера и пр.).
Внутри pod все контейнеры имеют один TCP/IP адрес и могут обращаться друг с другом через localhost !
Deployment и StatefulSet
У приложения (pod), помимо контейнеров существуют: описание, методанные, имя, метки, количество запущенных копий (replicas). Плюс к pod-у мы можем подключать внешние тома (volumes) для хранения данных, init-контейнеры (которые будут настраивать наше приложение до его запуска), и много чего еще.
Такое описание pod-а, с его дополнительными ресурсами называется Deployment. Описание в k8s происходит на языке разметки yaml
.
Вот пример описания Deployment-а:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | <span class="pun">---</span><span class="pln"> apiVersion</span><span class="pun">:</span><span class="pln"> apps</span><span class="pun">/</span><span class="pln">v1beta2 kind</span><span class="pun">:</span> <span class="typ">Deployment</span><span class="pln"> metadata</span><span class="pun">:</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> web</span><span class="pun">-</span><span class="pln">site spec</span><span class="pun">:</span><span class="pln"> replicas</span><span class="pun">:</span> <span class="lit">1</span> <span class="kwd">template</span><span class="pun">:</span><span class="pln"> metadata</span><span class="pun">:</span><span class="pln"> labels</span><span class="pun">:</span><span class="pln"> app</span><span class="pun">:</span><span class="pln"> web spec</span><span class="pun">:</span><span class="pln"> containers</span><span class="pun">:</span> <span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> php image</span><span class="pun">:</span><span class="pln"> php</span><span class="pun">-</span><span class="pln">fpm</span><span class="pun">:</span><span class="pln">latest ports</span><span class="pun">:</span> <span class="pun">-</span><span class="pln"> containerPort</span><span class="pun">:</span> <span class="lit">80</span><span class="pln"> volumeMounts</span><span class="pun">:</span> <span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> data mountPath</span><span class="pun">:</span> <span class="str">/var/</span><span class="pln">www subPath</span><span class="pun">:</span><span class="pln"> data </span><span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> db image</span><span class="pun">:</span><span class="pln"> mysql</span><span class="pun">:</span><span class="pln">latest volumeMounts</span><span class="pun">:</span> <span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> db mountPath</span><span class="pun">:</span> <span class="str">/var/</span><span class="pln">lib</span><span class="pun">/</span><span class="pln">mysql subPath</span><span class="pun">:</span><span class="pln"> db</span> |
В данном примере image
– имя образа Docker, доступного из репозитария (docker hub или личного). Пример: image: osixia/openldap:1.2.5
или image: localhost:5000/openldap:latest
. Еще вы можете увидеть описание экспортируемых TCP портов ports
и внешних томов volumeMounts
.
ЗАМЕТКА: Другой вариант Deployment называется StatefulSet – это тот же Deployment, только со статичными именами хостов и особым способом разделения ресурсов: каждый экземпляр реплики получает доступ только ко своей директории. Тогда как в Deployment все реплики имеют доступ к общим директориям. StatefulSet используется реже, как правило для балансировки нагрузки и репликации внутри приложения. К примеру его можно встретить в СУБД PostgreSQL с master-slave репликацией, где у каждой реплики – свой набор данных.
Service
Итак, у нас уже есть приложение, описанное со всеми ресурсами в Deployment. Нам надо к нему обратиться из-вне, к примеру открыть страницу нашего web. Для этого служит новый тип ресурсов: Service. Он позволяет описать что и на каком порту предоставлять в использование:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | <span class="pln">apiVersion</span><span class="pun">:</span><span class="pln"> v1 kind</span><span class="pun">:</span> <span class="typ">Service</span><span class="pln"> metadata</span><span class="pun">:</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> web</span><span class="pun">-</span><span class="pln">page spec</span><span class="pun">:</span><span class="pln"> selector</span><span class="pun">:</span><span class="pln"> app</span><span class="pun">:</span><span class="pln"> web type</span><span class="pun">:</span> <span class="typ">NodePort</span><span class="pln"> ports</span><span class="pun">:</span> <span class="pun">-</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> http port</span><span class="pun">:</span> <span class="lit">80</span><span class="pln"> targetPort</span><span class="pun">:</span> <span class="lit">80</span><span class="pln"> nodePort</span><span class="pun">:</span> <span class="lit">9000</span><span class="pln"> protocol</span><span class="pun">:</span><span class="pln"> TCP</span> |
В данном примере мы описали новый сервис (Service) с именем web-page
. Работать он будет с помощью селектора (selector
), выбирая все, что попадает под его правила. В данном случаи: app: web
. То есть наш pod, описанный в Deployment как раз имеет такую метку (label) (см. выше).
В k8s выборки делаются через метки (label).
Выше указан тип сервиса: type: NodePort
. Существует несколько способов открыть доступ до приложения:
- NodePort – этот тип вывешивает TCP/IP порт прямо на ноде, на которой запущен kubernetes. То есть вы можете обратиться по внешнему IP адресу сервера k8s и этот порт (у нас
nodePort: 9000
) будет перенаправлен в соответствующий pod. - ClusterIP – вывешивает сервис на выделенный IP адрес внутри кластера. Как правило он серый. Это самый используемый тип сервиса, так как позволяет экспортировать порты внутри кластера для общения приложений между собой.
- LoadBalancerIP – выделенный внешний (белый) IP адрес для сервиса. Располагается на балансировщике нагрузок, расположенным перед нодами k8s кластера.
К примеру реверсивный proxy-сервер в вашем кластере может иметь тип LoadBalancerIP и висеть на выделенном IP адресе, обращаясь к другим сервисам, которые имеют тип ClusterIP. Такой proxy-сервер в k8s называется Ingress, о нем будет ниже.
Все! Deployment + Service – нам этого достаточно, чтобы запустить наше приложение в kubernetes!
Принцип работы Kubernetes
Вот упрощенно как будет выглядеть работа приложения в k8s:
Тут app: web
– наше приложение, запущенное в единственном экземпляре. И какое-то другое (app: other
) – масштабируемое. У них разные сервисы и разные метки. По этому прокси сервер при обращении к определенному порту (или имени, в случаи если мы используем Ingress
) – будет переправлять нас на соответствующее приложение внутри k8s.
Ingress
Ingress – это http прокси сервер k8s, позволяющий обращаться к ресурсам по именам. Вот пример описания ingress-сервиса:
1 2 3 4 5 6 7 8 9 10 11 12 13 | <span class="pln">apiVersion</span><span class="pun">:</span><span class="pln"> extensions</span><span class="pun">/</span><span class="pln">v1beta1 kind</span><span class="pun">:</span> <span class="typ">Ingress</span><span class="pln"> metadata</span><span class="pun">:</span><span class="pln"> name</span><span class="pun">:</span><span class="pln"> web</span><span class="pun">-</span><span class="pln">ingress spec</span><span class="pun">:</span><span class="pln"> rules</span><span class="pun">:</span> <span class="pun">-</span><span class="pln"> host</span><span class="pun">:</span><span class="pln"> www</span><span class="pun">.</span><span class="pln">burlutsky</span><span class="pun">.</span><span class="pln">su http</span><span class="pun">:</span><span class="pln"> paths</span><span class="pun">:</span> <span class="pun">-</span><span class="pln"> path</span><span class="pun">:</span> <span class="pun">/</span><span class="pln"> backend</span><span class="pun">:</span><span class="pln"> serviceName</span><span class="pun">:</span><span class="pln"> web</span><span class="pun">-</span><span class="pln">page servicePort</span><span class="pun">:</span><span class="pln"> http</span> |
По указанным правилам (имя хоста и путь) – прокси сервер выберет соответствующий сервис: serviceName: web-page
, который был описан нами ранее.
ЗАМЕТКА: Тип нашему сервису (Service) web-page, в данном случаи, лучше поставить ClusterIP, убрав директиву nodePort, чтобы не вывешивать его прямо на ноде.
Helm
Все это конечно хорошо, но когда же мы приступим к практике? С чего начать? Я предлагаю сразу в бой! Но для этого нам понадобится шлем (Helm).
Чтобы не составлять все эти yaml – файлы с описанием вручную, не искать их по интернету – мы будем использовать готовые шаблоны (templates).
Дело в том, что работать напрямую с yaml-файлами kubernetes не очень удобно. Во-первых их надо много (чтобы описать все возможные ресурсы). Во-вторых, чтобы что-то изменить – надо искать и править эти файлы. Такой подход не приемлем для множественных установок одного и того же приложения, но с разными параметрами (именами, настройками, логинами, паролями и пр.).
Одним из самых популярных инструментов шаблонизации yaml-файлов для k8s на сегодня является Helm.
Вышеприведенный yaml-файл на нем выглядел бы как-то так:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | <span class="pun">{{-</span><span class="pln"> $fullName </span><span class="pun">:=</span><span class="pln"> include </span><span class="str">"fullname"</span> <span class="pun">.</span> <span class="pun">-}}</span><span class="pln"> apiVersion</span><span class="pun">:</span><span class="pln"> extensions</span><span class="pun">/</span><span class="pln">v1beta1 kind</span><span class="pun">:</span> <span class="typ">Ingress</span><span class="pln"> metadata</span><span class="pun">:</span><span class="pln"> name</span><span class="pun">:</span> <span class="pun">{{</span><span class="pln"> $fullName </span><span class="pun">}}-</span><span class="pln">ingress spec</span><span class="pun">:</span><span class="pln"> rules</span><span class="pun">:</span> <span class="pun">-</span><span class="pln"> host</span><span class="pun">:</span> <span class="pun">{{</span> <span class="pun">.</span><span class="typ">Values</span><span class="pun">.</span><span class="pln">hostname </span><span class="pun">}}</span><span class="pln"> http</span><span class="pun">:</span><span class="pln"> paths</span><span class="pun">:</span> <span class="pun">-</span><span class="pln"> path</span><span class="pun">:</span> <span class="pun">/</span><span class="pln"> backend</span><span class="pun">:</span><span class="pln"> serviceName</span><span class="pun">:</span> <span class="pun">{{</span><span class="pln"> $fullName </span><span class="pun">}}</span><span class="pln"> servicePort</span><span class="pun">:</span><span class="pln"> http</span> |
Тут вы видите, что изменяемые данные заменены на переменные:
- .Values.hostname – переменная из специального
values.yaml
– файла. Записывается в файле так:
1 | <span class="pln">hostname</span><span class="pun">:</span><span class="pln"> www</span><span class="pun">.</span><span class="pln">burlutsky</span><span class="pun">.</span><span class="pln">su</span> |
.Values.hostname
. То есть мы вынесли уже статичные данные из yaml-файла и сделали его шаблоном, пригодным для применения с другими данными. Ничего не надо править – только values.yaml .- $fullName – какая-то дополнительная переменная. В данном случаи обозначает имя нашего приложения. Для таких вспомогательных переменных служит специальный файл: _helpers.tpl.
Все файлы шаблонов находятся в директории templates. Посмотрите любой удобный для вас пример от сюда: https://github.com/helm/charts/tree/master/stable
Найдите в выбранном вами примере шаблоны описания Deployment (или StatefulSet), Service, Ingress. Как правило они располагаются в соответствующих файлах директории templates: deployment.yaml, services.yaml и т.п.
Установка приложения с Helm сводится к правке values.yaml-файла, или созданию нового my-values.yaml – файла, с нужными вам значениями переменных, а затем использованию клиента helm для установки данного набора шаблонов в k8s:
1 | <span class="pln">helm install </span><span class="pun">-</span><span class="pln">f </span><span class="kwd">my</span><span class="pun">-</span><span class="pln">values</span><span class="pun">.</span><span class="pln">yaml stable</span><span class="pun">/</span><span class="pln">postgresql</span> |
Данная команда возьмет переменные из вашего my-values.yaml – файла, совместит их с другими переменными values.yaml – файла, идущего в поставке. После чего на их основе сгенерирует конечные yaml-файлы и применит их в k8s.
Полезные команды
kubectl get pods
– получить все запущенные podkubectl describe pod <name>
– получить детальные описания podkubectl get services
– получить все сервисыkubectl get deployments
– получить все Deploymenthelm list
– получить все установленные пакеты приложений (релизы)
Minikube
Для старта работы с k8s вы можете использовать minikube, который позволяет получить окружение kubernetes у вас на компьютере или в виртуальной машине: https://github.com/kubernetes/minikube