Разбираем Kubernetes пример

К моему глубочайшему удивлению, начиная с прошлой недели Kubernetes стал неотъемлемой частью моей работы. То есть теперь я не просто должен им интересоваться, его нужно ещё и понимать. А с этим, как можно судить по прошлому Kubernetes посту, есть проблемы. Вроде ж и пример тогда простой был, и по всем шагам прошёлся, но всё равно осталось какое-то ощущение недосказанности.

Я всё думал, почему же так получилось, и, кажется, проблема кроется в неудачном выборе инструментов (а не в мозгах, как мог подумать чрезмерно проницательный читатель). Нюанс в том, что сконфигурировать Kubernetes приложение можно как минимум двумя с половиной способами. Например, есть команды для создания k8s объектов «на лету», вроде kubectl run  или kubectl expose, которыми я и пользовался в том злосчастном посте. Они простые, понятные, но скрывают пару-тройку важных абстракций, и по итогу картина остаётся неполной.

А оставшиеся полтора способа — это создание объектов из конфигурационных файлов. По одному (первый способ), или сразу пачкой целиком (ещё половина). И, хотя этот подход более громоздкий и менее простой, после него в картине мира вообще не остаётся белых пятен.

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

Предварительная подготовка

Нам понадобятся только VirtualBox, для создания виртуальных машин, minikube, чтобы делать из них Kubernetes кластер, и kubectl, чтобы этот кластер надругивать. Как только всё это установилось, minikube start сделает свою тёмную магию и Kubernetes кластер заодно.

Pod (язык больше не поворачивается называть его «подом»)

Как вы уже наверное знаете, pod — это самый маленький юнит работы в Kubernetes, обёртка вокруг одного или нескольких контейнеров со своей айпишкой, именем, идентификатором и наверняка богатым внутренним миром.

Как и в прошлый раз, мы сделаем наш первый pod вокруг Docker контейнера с nginx. Только в этот раз — из файла конфигурации:

YAML файлы эстетически и практически прекрасны. Их просто читать, просто создавать. Запороть, правда, тоже легко, но это ко многим вещам относится. В нашем YAML мы задали тип (kind) создаваемого объекта, имя, и из каких контейнеров он создан. То есть «Pod», «single-nginx-pod» и «nginx» соответственно.

Чтобы создать из него реальный объект, этот файл нужно скормить команде kubectl apply, и гештальт будет завершён.

Pod создался не сразу, ведь nginx образ ещё нужно было и скачать, но через пару секунд всё будет готово. В этот pod можно даже зайти и осмотреться:

Смотреть особо не на что, так что я установил htop, чтобы порадовать себя ядовито зелёными цветами и заодно убедиться, что процесс с nginx на месте:

htop

Правда, pod-одиночка — уязвим и удивительно бесполезен. Во-первых, снаружи по HTTP к нему не достучаться. Во-вторых, если к контейнеру или хосту придёт костлявая, то, собственно, история nginx на этом и закончится. Если же нам нужно будет его отмасштабировать, то команду apply придётся повторить ещё кучу раз.

pod-одиночка

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

Деплоймент-контроллер

Контроллеры — это такие Kubernetes объекты, которые могут манипулировать pod’ами. Например, есть Cron Job контроллер, который умеет запускать их по расписанию. Или ещё есть Replica Set, который может их масштабировать. Но самый универсальный контроллер — это Deployment. Он и воскресить может, и отмасштабировать, и апдэйт накатить, если попросят.

Но сейчас нам важно, чтобы деплоймент следил за здоровьем nginx, воскрешал его в случае чего, и масштабировал иногда. В общем, создаём.

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

  1. replicas , разумеется, задаёт, сколько копий pod’а нужно держать живыми.
  2. selector, в свою очередь, как отличать свои pod’ы от чужих. В нашем случае если на pod’е есть метка  app: webserver, то клиент — наш.
  3. template — он же шаблон — описывает, каким образом создавать pod, если тот скоропостижно скончался, либо вообще ещё никогда не существовал. Этот кусок очень похож на наш первый YAML код, где мы создавали pod-одиночку. В довесок к описанию контейнера мы повесили на него метку — app: webserver, чтобы деплоймент мог его найти.

Как и в прошлый раз, kubectl create сделает всё, что нужно:

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

Поддерживаем заявленную конфигурацию

А теперь смотрите, какой deployment, оказывается, полезный. Представим, что вмешались высшие силы (сегодня это я), и унесли бедолагу-pod’а в лучший из миров (полагаю, это AWS):

Ещё до того, как непечатное слово сорвётся с уст возмущённого кластер-администратора, деплоймент всё исправит:

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

Ещё один пример полезности деплоймента — масштабирование вверх и вниз. Если в какой-то момент мы осознали, что наш убийца фейсбука всё-таки «стрельнул»,  и одним контейнером с nginx больше не отделаешься, за одну команду (либо через YAML) их можно превратить в десяток:

Правда, даже после масштабирования у pod’ов с nginx есть существенный недостаток — к ним всё ещё не достучаться снаружи. Нужна какая-то точка входа.

deployment

Сервисы

Точку входа реально тяжело сделать, если pod’ы то умирают, то воскресают, то мигрируют по кластеру. Сервис-объект, в свою очередь, может эти pod’ы отыскать по меткам и общаться с миром от их лица.

Что здорово, сервис может работать даже без pod’ов. Это полезно тогда, когда у нас есть какой-то сервис вне кластера, но мы не хотим, чтобы pod’ы лазили к нему напрямую.

Но сегодня мы сделаем простейший и дефолтный ClusterIP сервис, который позволит общаться к pod’ами по внутренней айпишке:

Бесплатный DNS и балансировщик нагрузки

И хотя внутренняя айпишка на то и внутренняя, что ей снаружи не воспользуешься, у нас появилась пара интересных полезностей. Сервис умеет распределять входящие запросы среди всех своих pod’ов, тем самым работая балансировщиком нагрузки, и кроме этого его имя теперь есть в кластерном DNS, так что с ним можно общаться даже не зная айпишки.

Это легко проверить. Для простоты я уменьшил количество реплик nginx’а до двух и поменял слово «nginx» в их дефолтном HTML на имя хоста. Ну чтобы видеть, с кем общаемся. К тому же у нас всё ещё висит pod-одиночка — single-nginx-pod, в который можно зайти и попинать наш сервис тем же curl.

Видели? Это же офигенно. nginx-service разбрасывает запросы между двумя оставшимися pod’ами и это чётко видно по ответам. То же самое, кстати, умели делать сервисы в Docker Swarm.

service

Но основная проблема-то никуда не ушла! К pod’ам всё не достучаться снаружи. Ладно, это легко исправить.

Ingress

Ингресс — это могущественный чёрный ящик, который может маршрутизировать запросы из внешнего мира к конкретным сервисам внутри кластера. Он умеет делать не только это, но нам сегодня нужно просто провести HTTP запрос к nginx-service. Так что, без лишней болтовни:

Если я правильно помню, то сначала ingress ещё нужно было и включить командой minikube addons enable ingress. Хотя, может, показалось.

После того, как бессменный kubectl create превратит ingress в нечто большее, чем тыква, мы разузнаем айпишку единственной машины кластера через minikube ip и начнём любоваться и на дефолтную страницу nginx:

nginx-first

как и на то, что она каждый раз приходит из разного pod’а:

nginx-second

Сильное колдунство.

Ingress

Мораль

Надеюсь, что теперь всё стало понятнее. Лично мне насоздавать стайку объектов по одному и руками определённо помогло.

Но есть ещё один момент, который не вылазит у меня из головы до сих пор. Если посмотреть на конфигурационный файлы, то, в принципе, их можно создавать в любом порядке. Например, создать сначала сервис, а с pod’ами и деплойментами — подождать. И ничего не упадёт. Оно, конечно, и понятно, объекты же ищут друг друга по селекторам, а не по какой-то жёсткой связке. Но мне интересно, это изначально было частью гуглового плана, или просто они сделали грамотный дизайн, а независимость объектов — его естественный побочный эффект? Вот как они к этому пришли? Интересно же.

Но я отвлёкся. Конфигурационный файлы — трушный путь, Kubernetes теперь классный, двигаемся дальше.

11 комментариев для “Разбираем Kubernetes пример

  1. Одно непонятно… Почему вместо создания Ingress мы не можем открыть сервис nginx наружу через NodePort? Для чего в этом случае нужен Ingress?

    1. Мы можем. Если бы мы использовали NodePort или LoadBalancer, то тогда Ingress действительно был бы не нужен. Задачу можно решать по-разному, и здесь мне показалось важным разделить концепции сервиса и внешней точки доступа к нему. Плюс Ingress может работать роутером к нескольким сервисам, и тогда идейно картина была бы вообще прекрасной: все сервисы сидят внутри кластера, а Ingress открывает доступ к тем из них, к которым имеет смысл обращаться снаружи. Сервисам даже не надо в этом случае знать, что существует «большой» интернет.

      1. Понятно. Я просто написал свой конфиг Nginx для всех нужных внутренних сервисов и открыл NodePort наружу. Все работает как положено, с балансировкой. Просто читаю что все для этого используют исключительно Ingress, даже перед Nginx его ставят, вот и не мог понять зачем. По сути, Ingress внутри себя уже содержит Nginx.

    2. Можем! одно из, для того чтобы не светить айпишниками в мир, если у нас к примеру 5 рабочих нод, они могут все ходить через Ингресс (тем неменее через 1 айпи)

    1. Точно, пропущена. Но там была бы kubectl create -f, как и до этого.
      Что любопытно, самый первый pod я создавал через kubectl apply, а деплоймент уже через kubectl create. И то и то валидно, но прикольно, что только сейчас заметил

  2. Павел, спасибо за прекрасный стиль изложения.
    У вас случайно не было проблем чтобы Ingress на external мог уметь вешать серые адреса 10.х.х.х 192.168.х.х. Как-то страшно чтобы ingress во весь мир задом смотрел. Не приходилось для этого чтото дополнительно ‘подпинывать’?

    1. Пожалуйста.
      Надеюсь это просто полуночный эффект, а не старость, но начиная с фразы «ingress на external» я вопрос не понял. У нас единственный продакшен кластер живёт в Google Container Engine, и там какую гугл айпишку дал, той и рады. Он жеж гугл. Если хочется перед ingress какой-нибудь reverse proxy или WAF поставить для безопасности, то даже не знаю. В теории, сценарий по умолчанию — оставить ингрес внутри датацентра/проекта/облака, чтобы к нему доступ по внешней айпишке был закрыт (файрволом, либо вешать только на внутреннюю сеть), и в этом же облаке ставить прокси с выходом и наружу, и во внутреннюю сеть c ингрес. Но именно с kubernetes мне такое делать не приходилось.

  3. Спасибо! Лучшая из вводных статей, которые я встречал. Только в приведенной конфигурации ingress у меня отвечал «Connection refused». Отредактировал так:

    $cat ingress.yml
    apiVersion: extensions/v1beta1
    kind: Ingress
    metadata:
    name: nginx-ingress
    spec:
    rules:
    — host: minikube # in /etc/hosts has to be a line ‘ minikube’
    http:
    paths:
    — path: /
    backend:
    serviceName: nginx-service
    servicePort: 80

    1. Пожалуйста! Возможно, мы по-разному кластер настраиваем, или kubernetes уже не торт. Я точно помню, что в мои времена та ingress конфигурация была валидной.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *