Книга: Паттерны Kubernetes: Шаблоны разработки собственных облачных приложений
Назад: Глава 19. Конфигурация в ресурсах
Дальше: Глава 21. Макет конфигурации

Глава 20. Неизменяемая конфигурация

Паттерн Immutable Configuration (Неизменяемая конфигурация) предполагает упаковку конфигурационных данных в неизменяемый образ контейнера и его подключение к приложению во время выполнения. Это не только дает возможность использовать разные и неизменяемые версии конфигурационных данных, но и позволяет преодолеть ограничения на объем этих данных, свойственные переменным окружения и ресурсам ConfigMap.

Задача

Как рассказывалось в главе 18 «Конфигурация в переменных окружения», переменные окружения предлагают самый простой способ настройки контейнерных приложений. Но несмотря на простоту и универсальность, управлять переменными окружения становится крайне сложно, как только их число превысит определенный порог.

Эту сложность отчасти можно преодолеть с помощью Configuration Resources (Конфигурация в ресурсах). Однако эти паттерны не обеспечивают неизменности самих конфигурационных данных. Под неизменностью здесь подразумевается невозможность изменить конфигурацию после запуска приложения, которая гарантирует, что конфигурационные данные всегда находятся в четко определенном состоянии. Кроме того, неизменяемую конфигурацию можно хранить в системе управления версиями и следовать за процессом контроля изменений.

Решение

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

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

Тома Docker

Прежде чем рассматривать решение для Kubernetes, отступим на шаг назад и рассмотрим случай с обычными контейнерами Docker. В Docker контейнер может экспортировать том с данными. С помощью директивы VOLUME в Dockerfile можно указать каталог, который позже можно сделать общим. Во время запуска содержимое этого каталога в контейнере копируется в общий каталог. Как показано на рис. 20.1, такое связывание томов

586704.png 

Рис. 20.1. Реализация паттерна Immutable configuration (Неизменяемая конфигурация) с использованием тома Docker

является отличным способом передачи конфигурационной информации из контейнера с конфигурацией в контейнер приложения.

Рассмотрим пример. Создадим для окружения разработки образ Docker, который содержит ее конфигурацию и создает том /config. Создать такой образ можно с помощью Dockerfile-config, как показано в лис­тинге 20.1.

Листинг 20.1. Dockerfile, определяющий образ с конфигурацией

FROM scratch

 

ADD app-dev.properties /config/app.properties  

 

VOLUME /config                                 

Добавить заданное свойство.

Создать том и скопировать свойство в него.

Теперь, с помощью клиента командной строки Docker CLI, создадим сам образ и контейнер Docker, как показано в листинге 20.2.

Листинг 20.2. Сборка образа Docker с конфигурацией

docker build -t k8spatterns/config-dev-image:1.0.1 -f

                Dockerfile-config

docker create --name config-dev k8spatterns/config-dev-image:1.0.1 .

Заключительный шаг: запуск контейнера с приложением и связывание его с конфигурационным контейнером (листинг 20.3).

Листинг 20.3. Запуск контейнера с приложением и связывание его с конфигурационным контейнером

docker run --volumes-from config-dev k8spatterns/welcome-servlet:1.0

Образ с приложением ожидает найти файлы конфигурации в каталоге /config, в томе, смонтированном контейнером с конфигурацией. После перемещения приложения из окружения разработки в промышленное окружение вам останется только изменить команду запуска. Менять сам образ приложения не придется, вы просто свяжете контейнер приложения с контейнером, содержащим конфигурацию промышленного окружения, как показано в листинге 20.4.

Листинг 20.4. Использование другой конфигурации в промышленном окружении

docker build -t k8spatterns/config-prod-image:1.0.1 -f

              Dockerfile-config

docker create --name config-prod k8spatterns/

              config-prod-image:1.0.1 .

docker run --volumes-from config-prod k8spatterns/

              welcome-servlet:1.0

Init-контейнеры в Kubernetes

Совместное использование томов контейнерами в поде идеально подошло бы для связывания контейнеров приложений с конфигурационными контейнерами в Kubernetes. Но если мы решим перенести эту технику из Docker в мир Kubernetes, то обнаружим, что в настоящее время в Kubernetes нет поддержки контейнерных томов. Учитывая, насколько давно обсуждается необходимость этой поддержки, сложность ее реализации и ограниченность преимуществ, представляется маловероятным, что контейнерные тома появятся в ближайшее время.

Поэтому контейнеры могут совместно использовать внешние тома, но пока не могут напрямую использовать общие каталоги, находящиеся в самих контейнерах. Однако для реализации паттерна Immutable Configuration (Неизменяемая конфигурация) в Kubernetes можно использовать паттерн Init Containers (Init-контейнеры), описанный в главе 14, который может инициализировать пустой общий том во время запуска.

В примере с контейнерами Docker мы создали пустой образ с конфигурацией, не содержащий никаких файлов операционной системы. Нам ничего не нужно иметь в этом контейнере, кроме конфигурационных данных, передаваемых через тома Docker. Однако init-контейнеру в Kubernetes нужна некоторая помощь от базового образа, чтобы скопировать конфигурационные данные в общий том в поде. busybox — хороший выбор на роль такого базового образа, потому что он достаточно мал и позволяет использовать простую Unix-команду cp.

Но как происходит инициализация общих томов с конфигурациями? Рассмотрим пример. Для начала снова создадим образ с конфигурацией с помощью Dockerfile, как показано в листинге 20.5.

Листинг 20.5. Образ с конфигурацией для окружения разработки

FROM busybox

 

ADD dev.properties /config-src/demo.properties

 

ENTRYPOINT [ "sh", "-c", "cp /config-src/* $1", "--" ]  

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

Единственное отличие от примера 20.1 со стандартным образом Docker заключается в наличии базового образа и директивы ENTRYPOINT с кодом, который копирует файл свойств в каталог, указанный в аргументе команды запуска образа Docker. Теперь на этот образ можно сослаться в init-контейнере, внутри раздела .template.spec с определением развертывания Deployment (листинг 20.6).

Листинг 20.6. Определение развертывания, копирующего конфигурацию из init-контейнера в указанный каталог

initContainers:

- image: k8spatterns/config-dev:1

  name: init

  args:

  - "/config"

  volumeMounts:

  - mountPath: "/config"

    name: config-directory

containers:

- image: k8spatterns/demo:1

  name: demo

  ports:

  - containerPort: 8080

    name: http

    protocol: TCP

  volumeMounts:

  - mountPath: "/config"

    name: config-directory

volumes:

  - name: config-directory

    emptyDir: {}

Определение развертывания Deployment включает один том и два контейнера:

• Том volume config-directory имеет тип emptyDir, то есть на узле, где находится под, создается пустой каталог.

• Init-контейнер, вызываемый фреймворком Kubernetes во время запуска, конструируется из образа, который мы только что создали. Мы также определили единственный аргумент /config, который передается в ENTRYPOINT образа. Этот аргумент сообщает init-контейнеру, куда следует скопировать его содержимое. Каталог /config монтируется из тома config-directory.

• Контейнер приложения монтирует том config-directory, чтобы получить доступ к конфигурации, скопированной из init-контейнера.

Схема на рис. 20.2 иллюстрирует, как контейнер приложения получает доступ к конфигурационным данным, созданным init-контейнером в общем томе.

Теперь, чтобы заменить конфигурацию для окружения разработки конфигурацией для промышленного окружения, нужно лишь заменить образ init-контейнера. Для этого можно исправить определение YAML или произвести обновление с помощью команды kubectl. Однако было бы слишком утомительно править описание ресурса для каждого окружения. Если вы используете Red Hat OpenShift, корпоративный дистрибутив Kubernetes, паттерны OpenShift могут помочь в решении этой проблемы. Паттерны OpenShift дают возможность создавать разные описания ресурсов для разных окружений из одного паттерна.

586718.png 

Рис. 20.2. Реализация паттерна Immutable Configuration (Неизменяемая конфигурация) с помощью init-контейнера

Паттерны OpenShift

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

Листинг 20.7. Паттерн OpenShift, параметризуемый образом с конфигурацией

apiVersion: v1

kind: Template

metadata:

  name: demo

parameters:

  - name: CONFIG_IMAGE                       

    description: Name of configuration image

    value: k8spatterns/config-dev:1

objects:

- apiVersion: v1

  kind: DeploymentConfig

    // ....

    spec:

      template:

        metadata:

          // ....

          spec:

            initContainers:

            - name: init

              image: ${CONFIG_IMAGE}         

              args: [ "/config" ]

              volumeMounts:

              - mountPath: /config

                name: config-directory

            containers:

            - image: k8spatterns/demo:1

              // ...

              volumeMounts:

              - mountPath: /config

                name: config-directory

          volumes:

          - name: config-directory

            emptyDir: {}

Объявление параметра паттерна CONFIG_IMAGE.

Использование параметра паттерна.

Здесь показана только часть полного описания, но мы с легкостью сможем распознать параметр CONFIG_IMAGE в объявлении init-контейнера. Если создать этот паттерн в кластере OpenShift, мы сможем создать его экземпляр командой oc, как показано в листинге 20.8.

Листинг 20.8. Применение паттерна OpenShift для создания нового приложения

oc new-app demo -p CONFIG_IMAGE=k8spatterns/config-prod:1

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

Пояснение

Использование отдельных контейнеров с данными в паттерне Immutable Configuration (Неизменяемая конфигурация) — не самая простая задача. Однако этот паттерн предлагает некоторые уникальные преимущества:

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

• Конфигурацию, созданную таким способом, можно распространять через реестр контейнеров. Кроме того, такую конфигурацию можно проверить, даже не имея доступа к кластеру.

• Конфигурация считается неизменяемой, потому что хранится в образе контейнера: изменение конфигурации требует обновления версии и создания нового образа контейнера.

• Образы с конфигурационными данными удобнее, когда конфигурации слишком сложны или содержат слишком большие объемы конфигурационных данных, чтобы их можно было поместить в переменные окружения или в ресурсы ConfigMap.

Однако этот паттерн имеет и свои недостатки:

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

• Он не решает проблемы безопасности конфиденциальных конфигурационных данных.

• В случае с Kubernetes требуется дополнительная обработка init-контейнеров, а значит, придется управлять различными объектами Deployment для разных окружений.

В любом случае нужно тщательно оценить, действительно ли необходим такой сложный подход. Если неизменность не требуется, может быть, будет достаточно простого ресурса ConfigMap, как описано в главе 19 «Конфигурация в ресурсах».

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

Дополнительная информация

• Пример неизменяемой конфигурации (http://bit.ly/2HL95dp).

• Как имитировать --volumes-from в Kubernetes (http://bit.ly/2YbRhhy).

• Предложение по улучшению: поддержка томов образов в Kubernetes (http://bit.ly/2Wf0pjt).

• docker-flexvol: драйвер для Kubernetes, поддерживающий тома Docker (https://github.com/dims/docker-flexvol).

• Паттерны OpenShift (https://red.ht/2Ohh7vO).

Назад: Глава 19. Конфигурация в ресурсах
Дальше: Глава 21. Макет конфигурации