Паттерн Configuration Template (Макет конфигурации) позволяет создавать и обрабатывать большие и сложные конфигурации на этапе запуска приложения. Сгенерированная конфигурация зависит от целевого окружения времени выполнения, определяемого параметрами, которые используются при обработке макета конфигурации.
В главе 19 «Конфигурация в ресурсах» вы узнали, как использовать стандартные объекты ресурсов Kubernetes — ConfigMap и Secret — для настройки приложений. Но иногда конфигурационные файлы могут быть очень большими и сложными. Поместить такие файлы непосредственно в ConfigMaps может быть проблематично, поскольку они должны быть правильно внедрены в определения ресурсов. Также приходится быть очень осторожными и избегать специальных символов, таких как кавычки и переносы строк, играющих особую роль в синтаксисе определения ресурсов Kubernetes. Размер конфигурации — еще один важный фактор, который следует учитывать, потому что ресурсы ConfigMap или Secret ограничивают суммарный объем всех значений величиной 1 Мбайт (это ограничение накладывается внутренним хранилищем Etcd).
Большие конфигурационные файлы для разных окружений обычно имеют незначительные отличия. Такое их сходство приводит к большому количеству повторений и избыточности в ресурсах ConfigMap, потому что в каждом окружении используются в основном одни и те же данные. Паттерн Configuration Template (Макет конфигурации), который мы рассмотрим в этой главе, решает эти конкретные проблемы.
Чтобы уменьшить объем повторяющихся данных, имеет смысл хранить в ConfigMap или даже непосредственно в переменных окружения только различающиеся конфигурационные значения, такие как параметры подключения к базе данных. Во время запуска контейнера эти значения обрабатываются с помощью паттерна Configuration Template (Макет конфигурации), чтобы получить полный конфигурационный файл (как, например, standalone.xml в JBoss WildFly). Существует большое число инструментов, таких как Tiller (Ruby) или Gomplate (Go), для обработки макетов на этапе инициализации приложения. На рис. 21.1 показан пример макета конфигурации, заполненный данными, поступающими из переменных окружения или смонтированного тома, возможно, основанного на ConfigMap.
Рис. 21.1. Макет конфигурации
Перед запуском приложения подготовленный конфигурационный файл помещается туда, откуда его можно напрямую использовать, как любой другой файл конфигурации.
Вот два способа, как можно реализовать такую оперативную обработку во время запуска:
• Можно добавить обработчик паттернов как часть ENTRYPOINT в Dockerfile, чтобы сделать обработку макета непосредственной частью образа контейнера. Роль точки входа в таких случаях обычно играет сценарий, который сначала обрабатывает макет, а потом запускает приложение. Параметры для макета извлекаются из переменных окружения.
• Лучший способ выполнить инициализацию в Kubernetes — использовать init-контейнер с обработчиком макетов, который создает конфигурацию для контейнеров приложений в поде. Init-контейнеры подробно описываются в главе 14.
Решение с использованием init-контейнеров выглядит более привлекательным в Kubernetes, потому что позволяет использовать ConfigMap для хранения параметров макета. Схема на рис. 21.1 иллюстрирует его работу.
Определение пода приложения включает как минимум два контейнера: init-контейнер для обработки макета и контейнер приложения. Init-контейнер содержит не только обработчик макетов, но и сами макеты конфигурации. Кроме контейнеров, этот под определяет также два тома: один для параметров паттерна, основанный на ConfigMap, и том emptyDir, используемый для совместного использования обработанных макетов init-контейнером и контейнером приложения.
При такой организации во время запуска этого пода выполняются следующие шаги:
1. Init-контейнер запускается и вызывает обработчик макетов. Обработчик извлекает макеты из своего образа и параметры из смонтированного тома ConfigMap и сохраняет результат в томе emptyDir.
2. После завершения init-контейнера запускается контейнер приложения и загружает конфигурационные файлы из тома emptyDir.
Следующий пример использует init-контейнер для управления полным набором конфигурационных файлов WildFly для двух окружений: окружения разработки и окружения промышленной эксплуатации. Оба очень похожи друг на друга и имеют мало отличий. На самом деле в нашем примере они отличаются только способом журналирования: каждая строка в журнале снабжается префиксом DEVELOPMENT: или PRODUCTION: соответственно.
Полный пример с исчерпывающими инструкциями по установке можно найти в репозитории GitHub (http://bit.ly/2TKUHZY), а здесь будет показана только основная идея.
Макет конфигурации журналирования в листинге 21.1 хранится в файле standalone.xml и параметризуется с использованием синтаксиса паттернов Go.
Листинг 21.1. Макет конфигурации журналирования
....
<formatter name="COLOR-PATTERN">
<pattern-formatter pattern="{{(datasource "config").logFormat}}"/>
</formatter>
....
Здесь для обработки макетов применяется процессор паттернов Gomplate, который использует понятие источника данных для ссылки на параметры макета, которые требуется заполнить. В нашем случае источником данных является том на основе ConfigMap, смонтированный в init-контейнер. В данном случае ConfigMap содержит одну запись с ключом logFormat, из которой извлекается фактический формат.
Теперь на основе этого макета можно создать Docker-образ init-контейнера. Dockerfile для образа k8spatterns/example-configuration-template-init очень прост (листинг 21.2).
Листинг 21.2. Простой Dockerfile, определяющий образ макета
FROM k8spatterns/gomplate
COPY in /in
Базовый образ k8spatterns/gomplate определяет обработчик макетов и сценарий точки входа, который по умолчанию использует следующие каталоги:
• /in содержит макеты конфигурации для WildFly, включая параметризованный standalone.xml. Они добавляются непосредственно в образ.
• /params используется для поиска источников данных Gomplate — файлов YAML. Этот каталог монтируется из тома ConfigMap.
• /out — это каталог, куда сохраняются обработанные файлы. Этот каталог монтируется в контейнер приложения WildFly и используется для конфигурации.
Второй ингредиент нашего примера — ресурс ConfigMap, определяющий параметры. В листинге 21.3 мы используем простой файл с парами ключ/значение.
Листинг 21.3. Значения для макета конфигурации
logFormat: "DEVELOPMENT: %-5p %s%e%n"
Ресурс ConfigMap с именем wildfly-parameters определяет эти данные в формате YAML с ключом config.yml, который используется init-контейнером для извлечения данных.
Наконец, нам нужно определить ресурс Deployment для развертывания сервера WildFly (листинг 21.4).
Листинг 21.4. Развертывание с использованием init-контейнера в роли обработчика макета
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
labels:
example: cm-template
name: wildfly-cm-template
spec:
replicas: 1
template:
metadata:
labels:
example: cm-template
spec:
initContainers:
- image: k8spatterns/example-config-cm-template-init
name: init
volumeMounts:
- mountPath: "/params"
name: wildfly-parameters
- mountPath: "/out"
name: wildfly-config
containers:
- image: jboss/wildfly:10.1.0.Final
name: server
command:
- "/opt/jboss/wildfly/bin/standalone.sh"
- "-Djboss.server.config.dir=/config"
ports:
- containerPort: 8080
name: http
protocol: TCP
volumeMounts:
- mountPath: "/config"
name: wildfly-config
volumes:
- name: wildfly-parameters
configMap:
name: wildfly-parameters
- name: wildfly-config
emptyDir: {}
Образ с макетами конфигураций.
Параметры из ConfigMap wildfly-parameters.
Целевой каталог для записи обработанных макетов. Монтируется из пустого тома.
Каталог для записи сгенерированных конфигурационных файлов, монтируемый как /config.
Объявления тома ConfigMap с параметрами и пустого каталога, используемого для передачи обработанной конфигурации.
Это довольно длинное объявление, поэтому рассмотрим его подробнее: определение Deployment содержит описание пода с нашим init-контейнером, контейнером приложения и двумя внутренними томами:
• Первый том, wildfly-parameters, содержит наш ресурс ConfigMap с тем же именем (то есть он содержит файл config.yml со значением параметра).
• Второй том изначально является пустым каталогом и используется совместно init-контейнером и контейнером WildFly.
Если запустить это развертывание Deployment, произойдет следующее:
• Будет создан init-контейнер и выполнится определяемая им команда. Этот контейнер извлечет файл config.yml из тома ConfigMap, заполнит макеты из каталога /in в init-контейнере и сохранит получившиеся файлы в каталоге /out. Каталог /out — это точка монтирования тома wildfly-config.
• После того как init-контейнер завершится, будет запущен сервер WildFly с параметром, указывающим, что полная конфигурация находится в каталоге /config. И снова, /config — это общий том wildfly-config, содержащий обработанные файлы макета.
Важно отметить, что это описание ресурса развертывания Deployment не придется изменять при переносе приложения из окружения разработки в окружение промышленной эксплуатации. Изменить понадобится только ConfigMap с параметрами макета.
С помощью этого метода легко создать конфигурацию DRY без копирования и обслуживания повторяющихся больших конфигурационных файлов. Например, если понадобится внести одинаковые изменения в конфигурации WildFly для всех окружений, достаточно будет изменить только один файл макета в init-контейнере. Это дает значительные преимущества для тех, кто занимается обслуживанием, потому что устраняет опасность несогласованного изменения конфигураций.
Советы по отладке томов
При работе с подами и томами, как в этом паттерне, не совсем понятно, как отлаживать проблемы, если паттерн работает не так, как ожидалось. Если понадобится проверить обработанные макеты, проверьте каталог /var/lib/kubelet/pods/ndompodid/volumes/kubernetes.io~empty-dir/ на узле, потому что именно там находится содержимое тома emptyDir. Просто выполните kubectl exec в поде после его запуска и проверьте этот каталог на наличие созданных файлов.
Паттерн Configuration Template (Макет конфигурации) построен на основе паттерна Configuration Resource (Конфигурация в ресурсах) и прекрасно подходит для ситуаций, когда приложение требуется запускать в разных окружениях со сложными и похожими конфигурациями. Однако настройка приложений с использованием паттерна Configuration Template (Макет конфигурации) более сложна и включает больше компонентов, которые могут работать неправильно. Используйте его, только если приложение требует огромного объема конфигурационных данных. Часто такие конфигурационные данные имеют весьма небольшие отличия в разных окружениях. Даже если копирование всей конфигурации для конкретного окружения непосредственно в ConfigMap позволяет добиться желаемого, такой поход усложнит обслуживание конфигураций в будущем, потому что со временем конфигурации неизбежно будут расходиться все дальше друг от друга. Для таких случаев паттерн Configuration Template (Макет конфигурации) подходит идеально.
• Пример реализации макета конфигурации (http://bit.ly/2TKUHZY).
• Механизм обработки макетов Tiller (https://github.com/markround/tiller).
• Gomplate (https://github.com/hairyhenderson/gomplate).
• Синтаксис макетов Go (https://golang.org/pkg/html/template/).
DRY — аббревиатура от англ. «Don’t Repeat Yourself» — не повторяйся.