что такое pod в kubernetes
Сети Kubernetes: поды
Материал, перевод которого мы сегодня публикуем, посвящён особенностям сетевого взаимодействия подов Kubernetes. Он предназначен для тех, у кого уже есть некоторый опыт работы с Kubernetes. Если вы пока не очень хорошо разбираетесь в Kubernetes, то вам, вероятно, прежде чем читать этот материал, полезно будет взглянуть на это руководство по Kubernetes, где работа с данной платформой рассматривается в расчёте на начинающих.
Контейнер Docker, запущенный на локальной машине
Два контейнера Docker, запущенные на локальной машине
Всё это хорошо, но это не описывает пока то, что мы, в применении к подам Kubernetes, называем «разделяемым сетевым стеком». К счастью, пространства имён отличаются большой гибкостью. Docker может запустить контейнер, и, вместо того, чтобы создавать для него новый виртуальный сетевой интерфейс, сделать так, чтобы он использовал бы, совместно с другими контейнерами, существующий интерфейс. При таком подходе нам придётся изменить вышеприведённую схему так, как показано ниже.
Контейнеры используют общий сетевой интерфейс
Контейнеры в гипотетическом поде
Сеть подов
Один под, полный контейнеров, это строительный блок некоей системы, но пока ещё не сама эта система. В основе архитектуры Kubernetes лежит требование, в соответствии с которым у подов должна быть возможность взаимодействовать с другими подами вне зависимости от того, выполняются ли они на одном и том же компьютере или на разных машинах. Для того чтобы узнать о том, как всё это устроено, нам нужно перейти на более высокий уровень абстракции и поговорить о том, как в кластере Kubernetes работают узлы. Здесь мы затронем тему сетевой маршрутизации и маршрутов. Данной темы в материалах, подобных этому, нередко избегают, считая её слишком сложной. Непросто найти понятное и не слишком длинное руководство по IP-маршрутизации, но если вам хочется взглянуть на краткий обзор этой проблемы — можете взглянуть на этот материал.
Кластер Kubernetes состоит из одного узла или из большего количества узлов. Узел — это хост-система, физическая или виртуальная, которая содержит разные программные средства и их зависимости (речь идёт, в основном, о Docker), а также несколько системных компонентов Kubernetes. Узел подключён к сети, что позволяет ему обмениваться данными с другими узлами кластера. Вот как может выглядеть простой кластер, состоящий из двух узлов.
Простой кластер, состоящий из двух узлов
В целом же можно отметить, что вам, обычно, не придётся размышлять о том, как именно работает сеть подов. Когда под обменивается данными с другим подом, чаще всего это происходит посредством сервисов Kubernetes. Это — нечто вроде программно определяемых прокси. Но сетевые адреса подов появляются в логах. В некоторых ситуациях, в частности, при отладке, вам может понадобиться явным образом задавать правила маршрутизации в сетях подов. Например, трафик, покидающий под Kubernetes, привязанный к любому адресу в диапазоне 10.0.0.0/8, не обрабатывается по умолчанию с помощью NAT. Поэтому если вы взаимодействуете с сервисами, находящимися в другой частной сети, имеющей тот же диапазон адресов, вам может понадобиться настроить правила маршрутизации, которые позволят организовать правильную доставку пакетов.
Итоги
Сегодня мы поговорили о подах Kubernetes и об особенностях их сетевого взаимодействия. Надеемся, этот материал поможет вам сделать правильные шаги в направлении реализации сложных сценариев взаимодействия подов в сетях Kubernetes.
Уважаемые читатели! Эта статья является первым материалом цикла, посвящённого сетям Kubernetes. Вторая часть этого цикла уже переведена. Мы размышляем о том, нужно ли переводить третью часть. Просим вас высказаться об этом в комментариях.
Так что же такое pod в Kubernetes?
Прим. перев.: Эта статья продолжает цикл материалов от технического писателя из Google, работающего над документацией для Kubernetes (Andrew Chen), и директора по software engineering из SAP (Dominik Tornow). Их цель — доступно и наглядно объяснить основы организации Kubernetes. В прошлый раз мы переводили статью про high availability, а теперь речь пойдет про такое базовое понятие в Kubernetes, как pod.
Kubernetes — движок оркестровки контейнеров, созданный для запуска контейнеризированных приложений на множестве узлов, которые обычно называют кластером. В этих публикациях мы используем подход системного моделирования с целью улучшить понимание Kubernetes и его нижележащих концепций. Читающим рекомендуется уже иметь базовое представление о Kubernetes.
Pods (Поды) — базовые строительные блоки Kubernetes, однако даже опытные пользователи Kubernetes не всегда могут объяснить, что же это такое.
Данная публикация предлагает лаконичную мысленную модель, которая проливает свет на определяющие характеристики pod’ов Kubernetes. Ради этой краткости пришлось опустить некоторые другие особенности Pod’ов, такие как liveness и readiness probes, разделение ресурсов (включая появившееся недавно namespace sharing — прим. перев.), работу с сетью.
Определение
Pod представляет собой запрос на запуск одного или более контейнеров на одном узле.
Pod определяется представлением запроса на запуск (execute) одного или более контейнеров на одном узле, и эти контейнеры разделяют доступ к таким ресурсам, как тома хранилища и сетевой стек.
Однако в обиходе термин «pod» может употребляться и в смысле этого запроса, и в смысле совокупности контейнеров, которые запускаются в ответ на запрос. Поэтому в публикации мы будем использовать слово «pod», когда говорим о запросе, а для второго случая — употреблять выражение «набор контейнеров».
Pod’ы считаются базовыми строительными блоками Kubernetes, потому что все рабочие нагрузки в Kubernetes — например, Deployments, ReplicaSets и Jobs — могут быть выражены в виде pod’ов.
Pod — это один и единственный объект в Kubernetes, который приводит к запуску контейнеров. Нет pod’а — нет контейнера!
Схема 1. Deployment, ReplicaSet, pod и контейнеры
Архитектура Kubernetes
Схема 2. Pod’ы, планировщик (Scheduler) и Kubelet
На этой иллюстрации выделены соответствующие объекты и компоненты. Pod’ы представлены как Kubernetes Pod Objects, а работой с ними занимаются:
Объекты Kubernetes
Схема 3. Объекты Kubernetes
На этой иллюстрации показаны объекты Kubernetes, ответственные за работу с pod’ом:
Binding Object привязывает Pod Object к Node Object, т.е. назначает pod на узел для последующего запуска.
Node Object представляет узел в кластере Kubernetes.
Обработка pod’а
Схема 4. Обработка pod’а
Когда pod создан пользователем или контроллером вроде ReplicaSet Controller или Job Controller, Kubernetes обрабатывает pod в два этапа:
Планирование pod’а
Схема 5. Управляющий цикл планировщика Kubernetes
Задача планировщика (Scheduler) в Kubernetes — запланировать pod, то есть назначить ему подходящий узел в кластере Kubernetes для последующего запуска.
Связывание объекта pod’а с объектом узла
Pod назначается узлу (или связывается с ним) тогда и только тогда, когда есть объект связывания (binding), у которого:
Запуск pod’а
Схема 6. Управляющий цикл Kubelet
Задача Kubelet — запустить pod, что по сути означает запуск набора контейнеров pod’а. Запуск pod’а Kubelet’ом происходит в две фазы: инициализацию и основную стадию.
Как правило, набор контейнеров на фазе инициализации осуществляет подготовительные работы, такие как подготовку необходимой структуры директорий и файлов. А набор контейнеров на основной фазе выполняет уже «самые главные» задачи.
В обиходе же, хотя это и не совсем корректно, термин «pod» зачастую подразумевает набор контейнеров на основной фазе или же ещё более узкое значение «самого главного» контейнера основной фазы.
Схема 7.1. Запуск pod’а, фаза инициализации (init) и основная фаза (main)
Схема 7.2. Запуск pod’а, подробности этого процесса
У политики рестарта pod’а различная семантика для init-контейнеров и основных контейнеров: если запуск init-контейнеров обязан привести к завершению, то основные контейнеры могут и не завершаться.
Политика рестарта для init-контейнера
Init-контейнер будет перезапущен (т.е. повлечёт за собой запуск нового контейнера с такой же спецификацией) при завершении своей работы только при выполнении следующих условий:
Основной контейнер будет перезапущен (т.е. повлечёт за собой запуск нового контейнера с такой же спецификацией) при завершении своей работы только при выполнении следующих условий:
На иллюстрации показана возможная временная шкала запуска pod’а с двумя спецификациями init-контейнеров и двумя спецификациями основных контейнеров. Также она показывает создание (в соответствии с политикой рестарта) нового контейнера Main Container 1.2 после проблемы с запуском Main Container 1.1.
Фазы pod’а
Схема 9. Взаимодействие Kubelet с объектом pod’а и исполняемой средой контейнера (container runtime)
Фаза pod’а — это проекция состояния контейнеров из набора контейнеров, она зависит от:
Ожидание (Pending)
Фаза Pending
Pod находится в фазе ожидания тогда и только тогда, когда:
Работает (Running)
Фаза Running
Pod находится в фазе работы тогда и только тогда, когда:
Успех (Success)
Фаза Success
Pod находится в фазе успеха тогда и только тогда, когда:
Отказ (Failure)
Фаза Failure
Pod находится в фазе отказа тогда и только тогда, когда:
Неизвестно (Unknown)
В дополнение к описанным выше фазам pod может находиться в неизвестной фазе, что свидетельствует о невозможности определить его текущую фазу.
Сбор мусора для pod’ов
Схема 11. Управляющий цикл сборщика мусора для pod’ов (Pod Garbage Collector)
После того, как pod был запланирован и запущен, специальный контроллер в Kubernetes — Pod Garbage Collector Controller — отвечает за удаление объектов pod’ов из хранилища объектов Kubernetes Object Store.
Заключение
Pod — базовый строительный блок Kubernetes: pod определяется как представление запроса на запуск одного или более контейнеров на одном узле. После того, как pod создан, Kubernetes обрабатывает его в два этапа: сначала планировщик (Scheduler) планирует pod, а затем — Kubelet запускает его. На протяжении своего жизненного цикла pod проходит через разные фазы, сообщая о состоянии — или, точнее говоря, о состоянии своего набора контейнеров — пользователю и системе.
Как расширить приложение в Kubernetes при помощи мультиконтейнерных подов: основные рекомендации
Запустить облачные микросервисы или 12-факторные приложения в Kubernetes относительно просто. Но как насчет запуска приложений, которые явно не предназначены для работы в контейнерной среде?
Перевели статью об одном из самых мощных инструментов в Kubernetes — мультиконтейнерном поде. Он позволяет менять поведение приложения, не изменяя его кода. Эта функция удобна для приложений, которые изначально не предназначены для работы в контейнерах.
Итак, посмотрим на примере.
Защита HTTP
Elasticsearch разработали до того, как контейнеры стали популярны (хотя сейчас его можно запустить в Kubernetes). И его можно рассматривать в качестве замены для устаревшего Java-приложения, которое предназначено для работы на виртуальной машине.
В этой статье я буду использовать Elasticsearch в качестве примера приложения, которое нужно улучшить с помощью мультиконтейнерных подов.
Вот очень простой, но не для продакшена, деплой и сервис для Elasticsearch:
Переменная окружения dicsovery.type нужна, чтобы запустить одиночную реплику.
Elasticsearch слушает HTTP-порт 9200 по умолчанию.
Вы можете убедиться, что под работает, запустив в кластере другой под и подключившись к службе elasticsearch:
Теперь предположим, что вы хотите получить модель безопасности с нулевым доверием и зашифровать весь трафик в сети. Как поступить, если в приложении нет встроенной поддержки TLS?
Последние версии Elasticsearch поддерживают TLS, раньше эта функция была платной.
Первая мысль — выполнить терминацию TLS при помощи Nginx Ingress, поскольку Ingress маршрутизирует внешний трафик в кластере. Это не соответствует требованиям: трафик между подом входящего трафика и подом Elasticsearch может проходить по сети в незашифрованном виде.
Внешний трафик проходит через Ingress и направляется к подам
Если вы завершите TLS на входе, остальной трафик не будет зашифрован
Решение, которое соответствует требованиям, — прикрепить прокси-контейнер Nginx к поду, доступному по TLS. Тогда трафик будет зашифрован на всем пути от пользователя к поду.
Если вы включаете прокси-контейнер в модуль, то можете завершить TLS в модуле Nginx
До контейнера Elasticsearch трафик идет в зашифрованном виде
Вот как выглядит деплоймент:
Давайте немного поясню:
Таким образом, запросы извне пода поступают в Nginx через порт 9200 HTTPS, а затем попадают в Elasticsearch через порт 9201.
Вы можете проверить, что все работает, отправив HTTPS-запрос из кластера:
Беглый взгляд на логи показывает, что запрос прошел через прокси Nginx:
Вы также можете убедиться: нельзя подключиться к Elasticsearch через незашифрованные соединения.
Поздравляю, вы включили TLS, не меняя кода Elasticsearch или образа в контейнере!
Практика добавления прокси-контейнера в под распространена, поэтому у нее есть свое имя — шаблон Ambassador.
Все шаблоны в этом посте подробно описаны в отличной статье от Google.
Добавление базовой поддержки TLS — только начало. Вот еще несколько вещей, которые можно сделать с шаблоном Ambassador:
Принцип работы мультиконтейнерных подов
Давайте вернемся назад и посмотрим, в чем разница между подами и контейнерами в Kubernetes. Это поможет лучше понять происходящее под капотом.
Традиционный контейнер, например запущенный при помощи Docker, обеспечивает несколько форм изоляции:
Docker позволяет настраивать и другие вещи, но эти — наиболее важные.
Контрольные группы — удобный способ ограничить ресурсы, которые может использовать конкретный процесс, например CPU или память. Допустим, вы указываете: процесс может использовать только 2 ГБ памяти и одно из четырех ядер CPU.
В то же время пространства имен отвечают за изоляцию процесса и ограничивают то, что он может видеть. Например, процесс может видеть только сетевые пакеты, непосредственно с ним связанные. И не может видеть все сетевые пакеты, проходящие через сетевой адаптер.
Другой пример — вы можете изолировать файловую систему и заставить процесс поверить, что у него есть доступ ко всей системе.
Начиная с версии ядра 5.6, есть восемь видов пространств имен, и mount — одно из них
Используя пространство имен mount, вы заставляете процесс поверить, что ему доступны все каталоги на хосте, хотя это не так
Пространство имен mount предназначено для изоляции ресурсов, в данном случае — файловой системы
Каждый процесс видит одну и ту же файловую систему, но при этом изолирован от других
Если вам нужно освежить в памяти контрольные группы и пространства имен, то вот отличная статья, в которой рассматриваются некоторые технические детали.
В Kubernetes контейнер обеспечивает все перечисленные формы изоляции, кроме изоляции сети.
Изоляция сети происходит на уровне пода. Другими словами: у каждого контейнера в поде — своя файловая система, таблица процессов и так далее. Но все они совместно используют одно и то же сетевое пространство имен.
Чтобы лучше понять, как все работает, давайте поиграемся с простым подом, состоящим из нескольких контейнеров:
Вы можете убедиться, что том смонтирован в первом контейнере, используя kubectl exec:
Команда подключает терминальный сеанс к контейнеру c1 в поде podtest. Вы можете проверить прикрепленные тома с1 с помощью команды:
Как видите, том смонтирован на /shared — общем томе, который мы создали ранее. Теперь создадим несколько файлов:
Проверим те же файлы из второго контейнера. Сначала подключимся к нему с помощью команды:
Как видите, файл, созданный в общем каталоге, доступен в обоих контейнерах, а файл в /tmp — нет. Так случилось, поскольку за исключением тома, файловые системы контейнеров полностью изолированы друг от друга.
Теперь посмотрим на сеть и изоляцию процессов. Простой способ понять, как настроена сеть, — использовать команду ip link, она показывает сетевые устройства системы Linux.
Выполним команду в первом контейнере:
А теперь ту же команду в другом контейнере:
Вы можете видеть, что в обоих контейнерах есть:
Предполагается, что MAC-адреса глобально уникальны, значит, это явный признак того, что модули используют одно и то же устройство.
Теперь давайте посмотрим на совместное использование сети в действии. Подключимся к первому контейнеру с помощью команды:
Запустим очень простой сетевой листенер nc:
Команда запускает листенер на локальном хосте на порту 5000 и показывает дату любому подключенному TCP-клиенту. Может ли к нему подключиться второй контейнер? Откроем терминал во втором контейнере с помощью команды:
Посмотрите сами — второй контейнер может подключиться к сетевому порту, но не видит процесс nc:
Подключившись через telnet, вы можете увидеть дату, это доказывает, что листенер nc работает. Но ps aux, показывающий все процессы в контейнере, вообще не отображает nc. Это связано с тем, что у контейнеров внутри модуля есть изоляция процессов, но нет изоляции сети.
И это объясняет, как работает паттерн Ambassador:
Контейнер, который получает внешний трафик, называют Ambassador — отсюда и название шаблона.
Поскольку сетевое пространство имен общее, то несколько контейнеров в поде не могут прослушивать один и тот же порт!
Давайте посмотрим на другие варианты использования многоконтейнерных подов.
Предоставление метрик по стандартному интерфейсу
Допустим, вы используете Prometheus для мониторинга всех сервисов в вашем кластере Kubernetes. Но есть приложения, которые изначально не экспортируют метрики Prometheus, например Elasticsearch.
Можете ли вы добавить метрики Prometheus в свои поды, не меняя код приложения? Можете, если используете шаблон адаптера.
В примере с Elasticsearch давайте добавим в под контейнер «экспортер», он предоставляет метрики Elasticsearch в формате Prometheus. Это легко сделать, поскольку для Elasticsearch есть экспортер с открытым исходным кодом (вам нужно добавить соответствующий порт в сервис).
Как только вы примените этот манифест, метрики станут доступны на порту 9114.
Еще раз — вы можете изменить поведение приложения, фактически не меняя код или образы контейнеров. В данном случае мы предоставили стандартизированные метрики Prometheus, которые используют кластерные инструменты (такие, как оператор Prometheus). И таким образом добились хорошего разделения между приложением и базовой инфраструктурой.
Просмотр логов
Давайте посмотрим на паттерн Sidecar, с его помощью в под добавляют улучшающий приложение контейнер.
Шаблон Sidecar — общий и применяется в различных вариантах использования. Так, вы можете услышать, что любые контейнеры в модуле, кроме первого, называют «sidecars».
Сначала рассмотрим один из классических вариантов использования — log tailing sidecar.
В контейнерной среде рекомендуют всегда писать в стандартный вывод, чтобы собирать и агрегировать журналы логов централизованно. Но многие старые приложения разработаны для логирования в файлы, и изменить это иногда нетривиальная задача. Благодаря log tailing sidecar вам, возможно, не придется ничего менять!
Вернемся к примеру с Elasticsearch. Он немного надуманный, поскольку контейнер Elasticsearch по умолчанию ведет журнал в стандартном режиме, и нетривиально заставить его писать в файл.
Вот как выглядит деплой:
Файл конфигурации ведения журнала логов — отдельный файл ConfigMap, он слишком большой, чтобы включать его сюда.
У обоих контейнеров есть общий том с именем logs. Контейнер Elasticsearch записывает журналы на этот том, а контейнер журналов логов — читает из соответствующего файла и выводит его в стандартный формат.
Вы можете получить поток журнала логов, указав нужный контейнер в kubectl logs:
Самое прекрасное в Sidecar: потоковая передача в стандартный вывод — не единственный вариант. Если нужно переключиться на настраиваемую службу агрегации журналов логов, то можно просто изменить контейнер Sidecar, ничего не меняя в приложении.
Другие примеры использования Sidecars
Есть множество вариантов использования Sidecars, контейнер логов — только один и довольно простой пример.
Вот еще несколько вариантов, которые могут вам пригодиться:
Подготовка пода к запуску
Во всех примерах многоконтейнерных подов, которые мы рассмотрели выше, несколько контейнеров работают одновременно.
Еще в Kubernetes можно запускать контейнеры инициализации — контейнеры, которые выполняются и завершаются до запуска «обычных» контейнеров. Это позволяет запустить сценарий инициализации до того, как под запустится полностью.
Почему иногда нужно выполнять инициализацию в отдельном контейнере, а не, допустим, добавлять инициализацию в сценарий точки входа вашего контейнера?
Давайте посмотрим на примере Elasticsearch. В документации Elasticsearch рекомендуют устанавливать параметр sysctl vm.max_map_count в продакшен развертываниях. В контейнерных средах это проблематично — для sysctl нет изоляции на уровне контейнера, и любые изменения происходят на уровне узла.
Что делать с этим, если вы не можете настраивать узлы Kubernetes? Один из способов — запустить Elasticsearch в привилегированном контейнере. Это позволяет Elasticsearch изменять системные настройки на своем хост-узле и сценарий точки входа для добавления sysctl.
Но это чрезвычайно опасно с точки зрения безопасности! Если служба Elasticsearch будет скомпрометирована, то злоумышленник получил бы root-доступ к своему хост-узлу.
Чтобы снизить этот риск, можно использовать контейнер инициализации:
Под устанавливает sysctl в привилегированном контейнере инициализации, после чего контейнер Elasticsearch запускается, как и ожидалось. Вы по-прежнему используете привилегированный контейнер, что не идеально. Но он, по крайней мере, маленький и работает недолго, так что поверхность атаки намного ниже.