Языки программирования — как рецепты крафтового пива: непрерывно усложняются. Сперва программисты задавали железякам инструкции простыми низкоуровневыми командами. Потом появился код, в котором можно было использовать переменные и операторы. А когда переменных и операторов стало слишком много, люди научили языки программирования группировать код в функции. В конце концов функций тоже стало слишком много — и тогда инженеры придумали классы. Затем пакеты, чтобы группировать классы вместе и повторно использовать их в разных проектах.

Благодаря развитию интернета у нас появились огромные библиотеки и готовые к использованию сервисы — их можно скачивать и подключать в свои проекты, получая огромные готовые блоки функциональности.

Контейнер как базовый строительный блок

Направление тренда понятно — каждые несколько лет мы получаем в свои руки все более крупные строительные блоки для конструирования наших программ. Самые крупные на сегодняшний день блоки — это контейнеры, которые умеют принимать наши данные на вход и выдавать результат на выход.

Контейнеры (как и любые другие блоки кода) можно комбинировать между собой, следуя определенным принципам. Многие годы экспериментов привели к тому, что инженеры разработали некоторые крайне удачные приемы совместного использования контейнеров и описали свои идеи в виде шаблонов.

Давайте посмотрим несколько шаблонов проектирования контейнеризованных приложений и попробуем понять, как работает применение паттернов проектирования на таком высоком уровне.

Паттерн «Адаптер»

Паттерн, который пришел к нам из мира объектно-ориентированного программирования. В мире контейнеров этот паттерн работает аналогичным образом — он трансформирует определенным образом внешний интерфейс контейнера в какой-то другой интерфейс, соответствующий духу и стилистике вашей основной системы.

Допустим, у вас есть контейнеризованное приложение, которое умеет принимать картинки по HTTP и классифицировать изображения по разным типам. А основная система не использует HTTP для коммуникаций, потому что вы все решили построить вокруг шины сообщений.

Можно, конечно, расковырять приложение внутри контейнера и научить его работать через шину. Но тогда придется создавать свою версию этого сервиса, обновлять его и следить за проблемами внутри. Чтобы этого не делать, умные инженеры советуют написать адаптер, который проксирует сообщения шины через себя и переводит их в обращения к HTTP.

Вот и все: вы запускаете сервис в двух контейнерах — один реализует функциональность распознавания картинок, а второй — переводит инструкции для распознавалки из формата сообщений шины в формат HTTP. Такой адаптер можно использовать повторно и запускать через него и другие сервисы, которые не могут работать через шину сообщений, — это огромная экономия времени и сил разработчиков.

Паттерн «Шардированный сервис»

Шардирование появляется, когда данных становится очень много. Допустим, вам нужно положить в базу данных 1000 Тб записей. На сегодня ни один из жестких дисков в мире не сможет вместить такой массив информации. Поэтому данные придется разбивать на несколько серверов базы данных (БД) — шардов.

После того как мы раскидали данные по серверам, их нужно еще как-то найти. Для этого нужно иметь некий способ установить нахождение данных в системе по их идентификатору.

Можно решать это на уровне приложения и в каждой программе реализовывать метод, который будет находить нужные серверы БД по идентификатору данных и запрашивать данные оттуда.

А можно реализовать приложение в контейнере, которое умеет принимать запросы на чтение данных из БД и автоматически доставать их из нужного шарда. Внешне такое контейнеризованное приложение выглядит как сервер БД, но на деле оно является прослойкой, которая знает все о состоянии шардов и том, где и как лежат данные в кластере. Вместо реального выполнения запросов в БД прослойка формирует правильный запрос на нужный сервер, достает данные и возвращает их клиенту.

Плюс очевиден — у вас лишь одна точка для управления всеми параметрами шардинга. И в случае изменений в структуре кластера или алгоритмах разбивки данных по шардам вам нужно менять код только в одном месте.

Паттерн «Очередь задач»

Ситуация, когда у вас в систему поступают задачи, а несколько процессов (или серверов) их разгребают — стандартный подход к проектированию архитектуры. Для этого мы упаковываем наше приложение для разгребания очереди в контейнер и учим его забирать данные из шины сообщений.

Оформляем программу в контейнер и запускаем несколько копий в кластере Kubernetes или Docker Swarm. Если задачи копятся в очереди — динамически увеличиваем число контейнеров с программой. Если задач мало и очередь пустая — динамически отключаем часть контейнеров и направляем свободные ресурсы на другие задачи.

Для примера: представьте, что у вас работает сервис потоковой обработки видео. Пик нагрузки в нем приходится на период между 6 и 11 вечера. Для того чтобы всё работало как надо, большую часть времени мы будем держать в кластере 10 контейнеров, а на пиковые часы поставим 100. Изи!

Паттерн «Реплицированный сервис»

Этот паттерн логически связан с паттерном «Очередь задач». Допустим, у вас в системе есть очень важный сервис, отказ которого парализует работу всего вашего бизнеса. Такой сервис стоит держать запущенным в нескольких экземплярах — это называется репликацией.

Контейнеризация же упрощает репликацию. Например, вы запускаете сколько угодно копий сервиса и раздаете запросы всем копиям по очереди с помощью алгоритма Round Robin.

В случае если одна из копий сервиса повисает или выходит из строя, кластер контейнеров перераспределяет задачи между живыми копиями программы и старается перезапустить упавший контейнер.

Конечно, реализовать репликацию можно и без контейнеров, но их применение делает развертывание и управление копиями гораздо более элементарным и дешевым.

Где-то мы все это уже видели…

Все эти паттерны — классические подходы из мира ООП и многопоточного программирования. Мы всего лишь взяли то, что было придумано до этого и адаптировали эти идеи к очень большим строительным блокам, состоящим из целых сервисов.

Смело открывайте классические книги по паттернам проектирования и пробуйте адаптировать традиционные шаблоны к миру контейнеров — тогда у вас на выходе получится очень дешевая и гибкая система.

Да, возможно, для этого вам потребуется изменить свои взгляды на проектирование систем на высоком уровне, но, как говорится, без труда не вытащишь и мышку из компа!