Паттерн Sidecar (Прицеп) заключается в определении контейнера, который расширяет возможности существующего контейнера без его изменения. Это один из основополагающих паттернов контейнеров, который позволяет создавать узкоспециализированные контейнеры, тесно взаимодействующие друг с другом. В этой главе вы узнаете все, что связано с идеей паттерна Sidecar (Прицеп). А в главах 16 и 17 вы познакомитесь со специализированными вариантами этого паттерна — паттернами Adapter (Адаптер) и Ambassador (Посредник) соответственно.
Контейнеры — популярная технология упаковки, которая позволяет разработчикам и системным администраторам создавать, доставлять и запускать приложения унифицированным способом. Контейнер представляет естественную границу функциональной единицы со своей средой времени выполнения, циклом выпуска, API и коллективом разработчиков, которому он принадлежит. Типичный контейнер действует подобно процессу в Linux — решает одну проблему, и делает это хорошо, — и создается в предположении возможности замены и повторного использования. Последнее особенно важно, поскольку позволяет быстрее создавать приложения с использованием существующих специализированных контейнеров.
В настоящее время, чтобы послать HTTP-запрос, не нужно писать клиентскую библиотеку, достаточно использовать уже существующую. Аналогично, для обслуживания веб-сайта не нужно создавать контейнер с веб-сервером, достаточно использовать уже существующий. Этот подход позволяет разработчикам не изобретать колесо и создать экосистему с меньшим количеством контейнеров лучшего качества для обслуживания. Однако чтобы иметь возможность использовать узкоспециализированные многоразовые контейнеры, необходимы способы расширения их возможностей и средства для организации взаимодействий между ними. Паттерн Sidecar (Прицеп) описывает как раз такой способ организации взаимодействий, когда один контейнер расширяет возможности другого, уже существующего контейнера.
В главе 1 мы видели, как поды позволяют объединить несколько контейнеров в один блок. За кулисами, во время выполнения, под также является контейнером, но запускается как приостановленный (буквально с помощью команды pause) процесс перед всеми остальными контейнерами в поде. Он не делает ничего, кроме того, что хранит все пространств имен Linux, которые контейнеры приложений используют для взаимодействия на протяжении жизненного цикла пода. Кроме этой детали реализации, интерес представляют также все характеристики, которые предоставляет абстракция пода.
Под является настолько фундаментальным примитивом, что присутствует во многих облачных платформах, пусть под разными именами, но всегда со схожими возможностями. Под, как единица развертывания, накладывает определенные ограничения времени выполнения на принадлежащие ему контейнеры. Например, все контейнеры разворачиваются на одном узле и имеют общий жизненный цикл. Кроме того, под позволяет своим контейнерам использовать общие тома и обмениваться данными через локальную сеть или с применением средств межпроцессных взаимодействий хоста. Именно поэтому пользователи объединяют контейнеры в поды. Паттерн Sidecar (Прицеп), который иногда называют также Sidekick (Компаньон), описывает сценарий добавления контейнера в под для расширения возможностей другого контейнера.
Типичным примером, демонстрирующим этот паттерн, может служить HTTP-сервер и механизм синхронизации с репозиторием Git. Контейнер HTTP-сервера решает задачи, связанные с обслуживанием файлов через HTTP и не знает, как и откуда эти файлы поступают. Точно так же единственной целью контейнера, осуществляющего синхронизацию с Git, является синхронизация данных в локальной файловой системе с данными на сервере Git. Ему неважно, что происходит с файлами после синхронизации, его единственная задача — синхронизировать содержимое локальной папки с содержимым на удаленном сервере Git. В листинге 15.1 приводится определение пода с этими двумя контейнерами, настроенными на использование тома для обмена файлами.
Листинг 15.1. Реализация паттерна Sidecar (Прицеп)
apiVersion: v1
kind: Pod
metadata:
name: web-app
spec:
containers:
- name: app
image: docker.io/centos/httpd
ports:
- containerPort: 80
volumeMounts:
- mountPath: /var/www/html
name: git
- name: poll
image: axeclbr/git
volumeMounts:
- mountPath: /var/lib/data
name: git
env:
- name: GIT_REPO
value: https://github.com/mdn/beginner-html-site-scripted
command:
- "sh"
- "-c"
- "git clone $(GIT_REPO) . && watch -n 600 git pull"
workingDir: /var/lib/data
volumes:
- emptyDir: {}
name: git
Основной контейнер приложения, обслуживающий файлы через HTTP.
Вспомогательный контейнер (Прицеп), действующий параллельно и получающий данные с сервера Git.
Общая папка для обмена данными между основным и вспомогательным контейнерами.
Этот пример показывает, как контейнер синхронизации с Git добавляет контент для обслуживания сервером HTTP и поддерживает его в актуальном состоянии. Можно также сказать, что оба контейнера действуют в тесном сотрудничестве и одинаково важны, но паттерн Sidecar (Прицеп) предполагает наличие основного (главного) и вспомогательного контейнера, который расширяет коллективное поведение. Обычно главный перечисляется в списке контейнеров первым и представляет контейнер по умолчанию (который запускается командой kubectl exec, например).
Этот простой паттерн, изображенный на рис. 15.1, обеспечивает тесное сотрудничество контейнеров во время выполнения и в то же время позволяет разделить задачи, которые могут принадлежать отдельным коллективам разработчиков, использующим разные языки программирования,
Рис. 15.1. Паттерн Sidecar (Прицеп)
с разными циклами выпуска новых версий и т.д. Он также способствует взаимозаменяемости и многократному использованию контейнеров — в том смысле, что HTTP-сервер и механизм синхронизации Git могут повторно использоваться в других приложениях и с другими настройками, как самостоятельно, так и совместно с другими контейнерами.
Выше уже говорилось, что образы контейнеров подобны классам, а контейнеры — объектам в объектно-ориентированном программировании (ООП). Продолжая эту аналогию, можно сравнить расширение возможностей контейнера с наследованием в ООП, а совместную работу нескольких контейнеров в поде — с приемом композиции в ООП. Оба этих подхода допускают повторное использование кода, но наследование определяет более тесную связь и представляет отношение «является» между контейнерами.
Композиция в поде представляет отношение «имеет» — более гибкое, потому что не требует объединения контейнеров во время сборки, что дает возможность потом поменять контейнеры в определении пода. Но композиция также подразумевает наличие нескольких контейнеров (процессов), действующих одновременно, которые нужно проверять на работоспособность и перезапускать и которые потребляют ресурсы, как и главный контейнер приложения. Паттерн Sidecar (Прицеп) предполагает создание маленьких вспомогательных контейнеров, потребляющих минимальные ресурсы, но вам решать, стоит ли запускать отдельный процесс или лучше объединить все задачи в основной контейнер.
С другой точки зрения композиция контейнеров похожа на аспектно-ориентированное программирование, когда с помощью дополнительных контейнеров в под добавляются ортогональные (независимые) возможности, не касающиеся главного контейнера. В последние месяцы паттерн Sidecar (Прицеп) обретает все большую популярность, особенно для решения задач управления сетью, мониторинга служб, где каждая служба также поставляется в виде вспомогательных контейнеров.
• Пример реализации паттерна Sidecar (Прицеп, http://bit.ly/2FqSZUV).
• Паттерны проектирования для распределенных систем на основе контейнеров (http://bit.ly/2Odan24).
• Prana: вспомогательный контейнер для приложений и служб с поддержкой Netflix на основе PaaS (http://bit.ly/2Y9PRnS).
• Корабельный телефон: паттерны для добавления авторизации и шифрования в устаревшие приложения (https://www.feval.ca/posts/tincan-phone/).
• Универсальный контейнер для приостановки (http://bit.ly/2FOYH21).