Всем известно, что pod в Kubernetes может включать в себя несколько  контейнеров: для Service Mesh, работы с внешним хранилищем секретов, журналирования и т. д. В итоге это множество вызывает вопросы. Правильно ли использовать столько контейнеров? Как их изолировать от пользовательских приложений? Можно ли вообще исключить дополнительные контейнеры из пользовательских релизов? Я Максим Чудновский, занимаюсь Synapse Service Mesh в СберТехе. Расскажу, какие есть паттерны применения дополнительных контейнеров в Kubernetes, как они могут помочь в платформенной инженерии, и, самое главное,  как полностью автоматизировать процесс управления жизненным циклом таких контейнеров. Поскольку тема контейнеров довольно объёмна, в этом материале коснусь того, какие виды дополнительных «полезных» контейнеров бывают и как добавлять их в Kubernetes так, чтобы развести релизные процессы прикладных и платформенных команд. А в следующей статье поговорим, как автоматизировать управление дополнительными контейнерами и управлять кластером через политики. Для ответа на этот вопрос обратимся к  замечательной книге «Kubernetes Patterns» от O'Reilly. В части, посвящённой структурным паттернам, описано, как организовать своё приложение в Kubernetes с точки зрения структуры пода в Kubernetes: что там должно быть, а от чего лучше отказаться. В общем виде можно выделить 4 вида дополнительных «полезных» контейнеров:  Init;  Sidecar;  Adapter;  Ambassador. Давайте рассмотрим их подробнее. Как видно из названия, init-контейнеры нужны для того, чтобы инициализировать приложение. В отличие от обычных, они имеют собственный жизненный цикл: всегда выполняются первыми, а основные контейнеры стартуют только по их завершению. Это удобно, когда необходимо что-то подготовить, например, можно получить доступ к каким-то секретам или сделать редирект сетевого трафика через модификацию iptables. А после этого запустить само приложение и забыть про эти вещи. В общем случае sidecar-контейнер — это контейнер с законченной функциональностью, которая нужна приложению, но не является частью его бизнес-логики. За счёт такого разделения разработчики могут фокусироваться на одной задаче. Платформенная команда отвечает за дополнительные возможности, например, делает приложение более отказоустойчивым, надёжным. В свою очередь прикладные разработчики занимаются исключительно бизнес-логикой приложения. Sidecar-контейнеры чаще всего используются для добавления платформенной функциональности, например:  Service Mesh — в этом случае добавляется сетевой прокси, который обрабатывает все запросы, добавляет observability, делает mutual TLS и остальные полезные вещи.  Журналирование, если вам по каким-то причинам не нравится решение собирать логи через daemon, который бежит на воркере Kubernetes и забирает данные сразу с контейнерного runtime'а.  Централизованный аудит. Адаптеры — такие же sidecar’ы, но узкоспециализированные. Они используются тогда, когда в приложение нужно добавить новый API, но не хочется (или не получается) сделать это на уровне приложения. Вот классические примеры: Metrics API. Есть система мониторинга на базе Prometheus и приложение, которое про эту систему мониторинга вообще ничего не знает. У этого приложения есть либо только свои метрики, либо нет вообще нет никаких. Для «неинвазивного» решения проблемы достаточно добавить Prometheus экспортер отдельным контейнером, который опубликует все нужные метрики в правильном формате. Custom API. Позволяет добавить произвольный API по аналогии с примером метрик. RBAC Proxy. Дополнительный контейнер становится точкой входа в приложение и добавляет стандартный RBAC Kubernetes. Это делается очень быстро и во многих случаях бывает полезно. Эти контейнеры похожи на адаптеры, но работают в обратную сторону: они инкапсулируют в себя всю сложность внешнего API и позволяют использовать его понятным для приложения способом. Распространённые примеры использования: Kubernetes API Access. Допустим, наше приложение умеет работать только с файлами и может читать оттуда свои конфигурации. При этом количество файлов не ограничено, и состав часто меняется — пользователи добавляют новые и удаляют неактуальные. Стандартная история с ConfigMaps тут не слишком удобная. Придётся либо постоянно менять спецификацию приложения и добавлять новые каталоги монтирования, либо все файлы будут в одной ConfigMap, и её крайне сложно редактировать в многопользовательском режиме. Для решения этой проблемы можно научить приложение самостоятельно ходить в Kubernetes API и доставать нужную информацию. Но ещё проще поставить рядом Ambassador sidecar, который всё это сделает и за нас, и за приложение. Distributed or Local Cache Access. Можно унифицировать доступ к сложным 	распределённым кешам со стороны приложения. Data Access within a Data Mesh. Можно построить витрину данных, которая 	доступна непосредственно приложению. Самый простой способ — использовать обычные Kubernetes-манифесты. Достаточно добавить нужные контейнеры в спецификацию деплоймента (template spec). Но этот способ приводит к огромному количеству boilerplate yaml-файлов. Кажется, что решением могут стать различные template-менеджеры (Helm, Kustomize, CUE и т. д.). Они дадут какое-никакое управление зависимостями: платформенные команды смогут публиковать свои артефакты, на которые уже прикладные разработчики будут ссылаться и использовать. В результате количество yaml-кода сильно сократится. Но есть нюанс: template-менеджеры в конечном итоге развернут все в тот самый Raw Kubernetes Resource, который  будет использован в кластере.   В целом это рабочий подход. Но есть нюанс: у платформенных и прикладных разработчиков разный релизный цикл. И платформенным командам нужно публиковать свои sidecars независимо, без запроса на обновление бизнес-приложения, а сделать это будет невозможно. Для enterprise-решений это довольно серьезная проблема, поэтому важно разводить эти два релизных процесса. Хорошо, что Kubernetes позволяет это сделать. Когда вы создаёте объект внутри Kubernetes (например, делаете kubectl apply -f myfile.yaml), он проходит предобработку из нескольких этапов, которые реализуются с помощью специализированных admission контроллеров.   Они встроены в kube-api, включаются определёнными флагами и делают много полезного. Например, есть admission-контроллер Service Account, который монтирует токен Service Account в под, чтобы вы могли ходить в kube-api. Но нам интересен другой — mutation admission.   Когда Kubernetes видит новый запрос, он вызывает этот контроллер (как и все остальные), чтобы обработать ресурс. Суть в том, что mutating admission— кастомизируемый. Таким образом мы получаем инструмент расширения admission-контроллеров, чтобы создавать свою кастомную логику, которая отвечает за изменение («мутацию») создаваемых ресурсов «на лету».   Это очень удобно и Kubernetes-native. Мы создаем Mutating Admission Configuration, регистрируем webhook и выполняем нужные мутации. Например, автоматически добавляем sidecar-контейнеры при старте пода.   Решения, которые используют данный подход, называются Sidecar Injectors. В следующем материале подробно расскажу, как они устроены, а заодно поделюсь опытом автоматизации управления sidecars и готовыми open source решениями, которые позволяют комплексно решать такие задачи.  Open Source Software Architect