Вход на сайт

Просмотр новости

Найдите то, что Вас интересует

Kubernetes просто, часть 7: ConfigMap и Secret — как настраивать приложение, не пересобирая его каждый раз

Дата публикации: 20-10-2026 16:27:32

Как передать приложению настройки и пароли, не пересобирая Docker-образ? Разбираем ConfigMap и Secret на примере shop-api: переменные окружения, файлы внутри контейнера, обновление конфигурации и почему Base64 — не шифрование. Читать далее

Основное содержимое страницы с новостью.

Уровень сложностиПростой

Время на прочтение12 мин

Охват и читатели5K

Туториал

Предыдущие части серии:

В прошлой части мы разобрались с Gateway API и наконец собрали довольно приличную схему нашего мини-маркетплейса. У нас есть точка входа, маршруты, Service, Deployment и Pod. Трафик до приложений доходит, Kubernetes следит за количеством реплик.

Казалось бы, чего ещё не хватает?

Представим одну ситуацию.

Пятница, вечер. Наш shop-api отлично работает на тестовом стенде. Мы решили, что пора выпускать его в production. Образ собрали, Deployment применили, Pod запустились. Всё выглядит хорошо, пока не открываем логи.

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

Почему? Оказывается, где-то в настройках осталась строка:

DB_HOST=postgres-test

И теперь ради изменения одного адреса нам предлагают снова собирать Docker-образ.

Отличное занятие на вечер пятницы, не находите?

Сегодня разберёмся, как отделить само приложение от настроек, с которыми оно запускается. Заодно познакомимся с двумя объектами Kubernetes: ConfigMap и Secret.

Но сначала вопрос.

У нас один shop-api и три окружения: development, test и production. Нам действительно нужны три разных Docker-образа?

Пока не отвечайте. Давайте посмотрим, что между этими окружениями вообще меняется.

Один образ, три окружения

Предположим, наш shop-api читает три переменные окружения:

APP_MODE=development
LOG_LEVEL=debug
DB_HOST=postgres-dev

На тестовом стенде они выглядят так:

APP_MODE=test
LOG_LEVEL=debug
DB_HOST=postgres-test

А в production нам нужно:

APP_MODE=production
LOG_LEVEL=info
DB_HOST=postgres-service

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

Получается довольно простое разделение:

Docker-образ = что запускаем
Конфигурация = с какими настройками запускаем

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

Правильнее использовать один и тот же образ с разной конфигурацией для разных окружений. Иначе придётся следить, чтобы shop-api:test и shop-api:prod не начали жить собственной жизнью, отличаясь не только настройками, но и версиями приложения.

Вообще, Kubernetes не запрещает указать переменные прямо в Deployment:

env:
  - name: LOG_LEVEL
    value: "info"

И для одной-двух настроек это вполне нормально.

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

Хочется хранить конфигурацию отдельно, а в Pod просто указывать, откуда её взять.

Картинка 1

Картинка 1

И для этого в Kubernetes есть первый сегодняшний объект.

ConfigMap - настройки отдельно от приложения

ConfigMap - объект Kubernetes, предназначенный для хранения обычных, не секретных настроек.

Например, нашей конфигурации для production:

apiVersion: v1
kind: ConfigMap
metadata:
  name: shop-config
data:
  APP_MODE: "production"
  LOG_LEVEL: "info"
  DB_HOST: "postgres-service"

apiVersion, kind, metadata - всё это мы уже встречали, когда знакомились с YAML во второй части серии.

Теперь обратите внимание на:

kind: ConfigMap

Так мы сообщаем Kubernetes, какой именно объект создаём.

И на:

data:
  APP_MODE: "production"
  LOG_LEVEL: "info"
  DB_HOST: "postgres-service"

В data лежат пары «ключ - значение». В нашем примере это три строки.

Если сохранить всё в configmap.yaml и выполнить:

kubectl apply -f configmap.yaml

то в кластере появится объект shop-config. Проверить можно так:

kubectl get configmap shop-config

И тут важный момент, который легко пропустить.

Создание ConfigMap ещё не меняет настройки нашего приложения.

ConfigMap не запускает Pod, не пересобирает Docker-образ и вообще не знает, что конкретно означает LOG_LEVEL.

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

Как настройки попадают в Pod?

Вспомним наш Deployment. В нём мы уже описывали, сколько Pod должно работать и какие контейнеры нужно запустить.

Теперь к описанию контейнера добавим ещё одно поле:

containers:
  - name: shop-api
    image: registry.example.com/shop-api:1.0
    envFrom:
      - configMapRef:
          name: shop-config

Здесь и дальше shop-api - учебный пример. Адрес образа условный, без настоящего приложения этот Deployment не предназначен для запуска.

Самое интересное находится в трёх строчках:

envFrom:
  - configMapRef:
      name: shop-config

Читается довольно просто: «Возьми значения из ConfigMap shop-config и передай их контейнеру как переменные окружения».

То есть внутри запущенного контейнера появятся:

APP_MODE=production
LOG_LEVEL=info
DB_HOST=postgres-service

Но давайте уточним ещё кое-что.

Kubernetes передаёт переменные окружения, а не решает за приложение, как ими пользоваться. Если наш backend вообще не читает LOG_LEVEL, то хоть десять раз передайте ему эту переменную - подробные логи сами собой не появятся.

А теперь небольшая проверка.

Вопрос: в ConfigMap лежат три параметра, но контейнеру нужен только LOG_LEVEL. Обязательно забирать всё содержимое?

Нет. Можно обратиться к конкретному ключу:

env:
  - name: LOG_LEVEL
    valueFrom:
      configMapKeyRef:
        name: shop-config
        key: LOG_LEVEL

Здесь name указывает на ConfigMap, а key - на нужное значение внутри неё.

Получается, у нас есть два подхода:

  • envFrom - передать сразу набор подходящих ключей ConfigMap как переменные окружения.

  • env + configMapKeyRef - выбрать одно конкретное значение.

Картинка 2

Картинка 2

Отлично. Режим приложения настроили, адрес базы вынесли, логирование тоже.

Но давайте вернёмся к нашей базе данных. Мы ведь указали только адрес. А как будем авторизовываться?

А пароль от базы куда положим?

Сейчас в конфигурации у нас есть:

DB_HOST=postgres-service

Но база наверняка попросит ещё хотя бы пароль.

И тут предлагаю выбрать один из трёх вариантов.

Куда запишем DB_PASSWORD?

А. Прямо в Docker-образ.

Б. В ту же ConfigMap, рядом с DB_HOST.

В. В отдельный объект для чувствительных данных.

Подумайте, прежде чем читать дальше.

С технической точки зрения положить пароль в ConfigMap можно. Kubernetes не скажет, что мы делаем что-то не то. Но здесь вопрос не в том, получится ли сохранить строку, а в том, где ей вообще место.

ConfigMap предназначена для обычных настроек. Для паролей, токенов и ключей есть другой объект.

Поэтому выбираем вариант В.

Secret - отдельный объект для чувствительных данных

Secret нужен для хранения небольших объёмов чувствительных данных: паролей, токенов, сертификатов и ключей.

На первый взгляд он очень похож на ConfigMap:

apiVersion: v1
kind: Secret
metadata:
  name: shop-secret
type: Opaque
stringData:
  DB_PASSWORD: "not-a-real-password"

Разберём новые поля.

kind: Secret - сообщаем Kubernetes, что создаём Secret.

type: Opaque - универсальный тип Secret. Есть и специальные типы, например для TLS-сертификатов, но пока остановимся на обычном варианте.

stringData - позволяет записать значение привычной строкой. При обработке объекта Kubernetes преобразует его в поле data, где значения представлены в Base64.

Как передать пароль контейнеру? Принцип уже знакомый:

env:
  - name: DB_PASSWORD
    valueFrom:
      secretKeyRef:
        name: shop-secret
        key: DB_PASSWORD

В самом Deployment пароль мы не записываем. Вместо этого указываем, из какого Secret и по какому ключу его нужно взять.

Заметьте, насколько похоже на configMapKeyRef. Отличается источник данных, а идея та же.

Теперь попробуем собрать все части вместе.

Собираем конфигурацию нашего shop-api

Нам нужны два Pod shop-api, общие настройки из ConfigMap и пароль из Secret.

Вот как это может выглядеть в одном Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: shop-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: shop-api
  template:
    metadata:
      labels:
        app: shop-api
    spec:
      containers:
        - name: shop-api
          image: registry.example.com/shop-api:1.0
          envFrom:
            - configMapRef:
                name: shop-config
          env:
            - name: DB_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: shop-secret
                  key: DB_PASSWORD

На первый взгляд опять много YAML. Но если мысленно убрать знакомые поля Deployment, останутся всего две новые части.

envFrom берёт обычные настройки из shop-config.

secretKeyRef получает пароль из shop-secret.

А replicas: 2, как мы уже знаем, означает, что Kubernetes должен поддерживать два Pod нашего приложения.

Получается такая картина:

Deployment  -> сколько Pod нужно и как их запускать
ConfigMap   -> обычные настройки приложения
Secret      -> чувствительные данные
Приложение  -> читает настройки и использует их

Обратите внимание: ConfigMap и Secret не управляют Pod. Они только предоставляют данные, на которые ссылается описание контейнера.

И ещё один практический нюанс. Если обязательной ConfigMap или Secret нет в кластере, контейнер не сможет нормально стартовать с такой конфигурацией. Поэтому сначала создаём нужные объекты, а уже затем запускаем Pod, который на них ссылается.

Картинка 3

Картинка 3

Кажется, всё хорошо. Настройки отделили от образа, пароль положили в специальный объект.

И тут возникает закономерный вопрос.

Раз Secret называется Secret, значит пароль зашифрован?

Вот здесь Kubernetes может немного обмануть ожидания. Не специально, конечно, но название действительно звучит надёжно.

Посмотрим на пример:

data:
  DB_PASSWORD: "cGFzc3dvcmQ="

Набор символов выглядит непонятно. Можно подумать, что пароль теперь надёжно спрятан.

На самом деле cGFzc3dvcmQ= - это слово password, просто записанное в кодировке Base64. Превратить его обратно в обычный текст не составляет труда.

Base64 - это кодирование, а не шифрование.

Иными словами, если человек получил доступ к содержимому Secret, сама кодировка его не остановит.

Более того, без соответствующей настройки Kubernetes может хранить Secret в etcd без шифрования при хранении.

Значит ли это, что Secret бесполезен? Конечно нет. Он позволяет отделить чувствительные данные и применять к ним специальные правила доступа. Но название объекта само по себе не обеспечивает полную безопасность.

Поэтому для реального кластера важно:

  • Ограничивать доступ к Secret через RBAC.

  • Включать шифрование Secret при хранении (encryption at rest).

  • Не сохранять реальные пароли в открытом GitHub и не печатать их без необходимости в логах.

  • При необходимости использовать внешние хранилища секретов.

Сейчас углубляться в настройку RBAC и шифрования не будем. Здесь нам нужно запомнить главное: Secret предназначен для чувствительных данных, но безопасность не появляется автоматически только потому, что объект так называется.

Картинка 4

Картинка 4

С этим разобрались. Но у нас остаётся ещё один любопытный момент, причём уже про саму работу приложения.

Изменили ConfigMap - приложение сразу увидит новые настройки?

Представьте, что два Pod нашего shop-api уже работают.

В ConfigMap записано:

LOG_LEVEL=info

Оба контейнера получили это значение через envFrom, и приложение спокойно пишет обычные логи.

Но нам понадобилось подробнее разобраться в какой-то ошибке. Меняем настройку:

LOG_LEVEL=debug

Применяем обновлённую ConfigMap. Kubernetes принял изменение.

А теперь попробуйте предсказать результат.

Что произойдёт с работающими Pod?

А. Они сразу начнут использовать LOG_LEVEL=debug.

Б. ConfigMap обновится, но у запущенных контейнеров останется LOG_LEVEL=info.

В. Kubernetes автоматически пересоберёт Docker-образ.

Правильный ответ - Б.

Дело в том, что переменные окружения задаются при запуске контейнера. Если потом изменить ConfigMap, уже работающий процесс автоматически новые значения не получит.

А вот новые контейнеры, которые стартуют с этой конфигурацией, прочитают обновлённое значение.

Как быть, если мы хотим, чтобы оба Pod начали работать с LOG_LEVEL=debug? Для Deployment можно запустить пересоздание Pod командой:

kubectl rollout restart deployment/shop-api

Deployment постепенно заменит Pod, а новые контейнеры получат актуальную конфигурацию при запуске.

В процессе обновления вполне возможна такая ситуация:

ConfigMap:  LOG_LEVEL=debug
Старый Pod: LOG_LEVEL=info
Новый Pod:  LOG_LEVEL=debug

И тут важно не перепутать две вещи: настройка в Kubernetes уже обновилась, но приложение ещё не обязательно начало её использовать.

А если приложение читает настройки из файла?

До этого мы передавали настройки через переменные окружения. Указали LOG_LEVEL=info, Kubernetes передал значение контейнеру, приложение его прочитало. Всё хорошо

Но есть приложения, которым переменные вообще не интересны. Они привыкли искать готовый файл с настройками в определённом месте.

Допустим, наш shop-api при запуске открывает:

/etc/shop/config.yaml

И ожидает увидеть внутри вот это:

log_level: info
features:
  recommendations: true

Вопрос: как этот файл должен оказаться внутри контейнера?

Можно добавить его в Docker-образ. Но тогда ради смены log_level придётся снова собирать образ. А мы ведь всю статью пытаемся уйти именно от этого

Можно попробовать передать путь к файлу на нашем компьютере. Только вот контейнер работает в Kubernetes и не видит файлы на нашем ноутбуке сам по себе. Да и завтра Pod может запуститься уже на другой ноде.

Значит, нам нужно сделать две вещи:

  1. Передать Kubernetes содержимое файла, чтобы оно не зависело от того, где лежит файл на нашем компьютере.

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

Вот здесь ConfigMap нам и пригодится. Давайте пройдём весь путь на простом примере.

Шаг 1. Берём обычный файл с настройками

Представьте, что у нас уже есть config.yaml в папке проекта. Самый обычный файл, который мы открываем в редакторе:

log_level: info
features:
  recommendations: true

Переписывать его содержимое ещё раз в большой манифест совсем не обязательно.

Откроем терминал в той же папке и выполним:

kubectl create configmap shop-file-config --from-file=config.yaml

Прочитаем команду по частям:

  • create configmap shop-file-config - создаём ConfigMap с именем shop-file-config.

  • --from-file=config.yaml - берём данные из нашего файла config.yaml.

Всё. Kubernetes получил содержимое файла и сохранил его в ConfigMap. Проверить, что объект появился, можно командой:

kubectl get configmap shop-file-config

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

То есть пока ConfigMap - это, грубо говоря, сохранённая копия наших настроек. Но внутри Pod её ещё нет

Шаг 2. Говорим, куда положить настройки внутри контейнера

Вспомним, что приложение ищет файл строго по пути:

/etc/shop/config.yaml

Kubernetes уже знает, что должно быть внутри файла. Осталось объяснить ему, где приложение должно этот файл увидеть.

Для этого используются volumes и volumeMounts. Звучит громоздко, но идея довольно простая.

Посмотрите на фрагмент описания Pod:

spec:
  containers:
    - name: shop-api
      image: registry.example.com/shop-api:1.0
      volumeMounts:
        - name: app-config
          mountPath: /etc/shop
          readOnly: true
  volumes:
    - name: app-config
      configMap:
        name: shop-file-config

Это фрагмент spec Pod. Если используем Deployment, он должен находиться внутри spec.template.spec. Образ shop-api по-прежнему условный.

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

Сначала посмотрим на нижний блок:

volumes:
  - name: app-config
    configMap:
      name: shop-file-config

Здесь мы говорим Kubernetes: «У нас будет том app-config. Данные для него возьми из ConfigMap shop-file-config».

app-config - просто придуманное нами имя тома. Оно нужно, чтобы дальше на него сослаться.

Теперь верхняя часть:

volumeMounts:
  - name: app-config
    mountPath: /etc/shop
    readOnly: true

А это уже инструкция для контейнера: «Подключи том app-config в папку /etc/shop и не разрешай контейнеру изменять файлы в нём».

И вот вся цепочка:

Наш компьютер                 Kubernetes                  Контейнер shop-api
config.yaml  --from-file-->  ConfigMap shop-file-config
                                      |
                                 volume app-config
                                      |
                                 mountPath: /etc/shop
                                      |
                              /etc/shop/config.yaml

Откуда взялось имя config.yaml в конце пути?

Из команды --from-file=config.yaml. Kubernetes сохранил содержимое под ключом config.yaml, а при подключении ConfigMap как тома этот ключ становится именем файла.

Теперь всё сошлось: приложение открывает /etc/shop/config.yaml и получает наши настройки. При этом исходный Docker-образ менять не пришлось.

Проверим, разобрались ли мы

Допустим, в volumeMounts мы заменили:

mountPath: /etc/shop

на:

mountPath: /app/settings

Где теперь появится файл?

Правильно, по пути /app/settings/config.yaml. Мы изменили папку, куда подключается том, а имя файла осталось прежним.

Ещё один момент. Когда мы подключаем том в /etc/shop, уже существующие файлы образа в этой папке на время монтирования оказываются скрыты. Поэтому не стоит наугад подключать ConfigMap поверх папки, где лежит что-то важное.

Картинка 5

Картинка 5

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

И напоследок - тонкость, связанная с нашим экспериментом выше. Когда ConfigMap подключена как том, содержимое файла может обновляться в работающем Pod спустя некоторое время, без пересоздания контейнера. Но это ещё не гарантирует, что само приложение автоматически перечитает файл и начнёт использовать новые настройки.

А если мы поменяли config.yaml только у себя на компьютере, Kubernetes вообще об этом не узнает, пока мы отдельно не обновим ConfigMap.

Вот теперь у нас есть оба варианта: приложению нужны переменные - передаём переменные, приложению нужен файл - подключаем ConfigMap как том. Главное, чтобы приложение понимало выбранный способ настройки

Что в итоге?

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

ConfigMap хранит обычные настройки: режим работы, уровень логирования, адреса других сервисов и так далее.

Secret предназначен для чувствительных значений: паролей, токенов, ключей. При этом Base64 - не шифрование, а значит, о безопасности данных нужно думать отдельно.

Оба объекта позволяют не зашивать настройки в Docker-образ. Мы можем передать их через переменные окружения или подключить как файлы.

Но самое главное, на мой взгляд, вот что:

Docker-образ определяет, что мы запускаем. ConfigMap и Secret помогают указать, с какими настройками. А само приложение должно уметь эти настройки прочитать и использовать.

И теперь, когда наш мини-маркетплейс научился не только принимать трафик, но и получать конфигурацию отдельно от образа, мы стали ещё на шаг ближе к тому, как приложения действительно разворачивают в Kubernetes.

Проверьте себя

Как обычно, несколько вопросов, чтобы убедиться, что всё уложилось в голове.

  1. Почему мы не стали делать отдельный Docker-образ для development, test и production?

  2. Чем отличается envFrom от configMapKeyRef?

  3. Почему пароль от базы лучше не класть в ConfigMap?

  4. Если в Secret значение выглядит как Base64, можно ли считать его зашифрованным?

  5. Почему работающий Pod не увидит обновление переменной окружения после изменения ConfigMap?

  6. При каком способе передачи конфигурации изменения могут появиться в файле без пересоздания Pod?

  7. И главный вопрос: если приложение не умеет читать переданные ему настройки, сможет ли Kubernetes заставить его использовать их правильно?

Схожие новости

#Наименование новостиТональностьИнформативностьДата публикации
1Kubernetes без Kubernetes: запускаем Pod с помощью Podman06.7617-10-2026
2Kubernetes Secrets Types, Management, Best Practices & Solutions07.8317-08-2026
3Когда Docker Compose уже мало, а Kubernetes еще слишком много: знакомство с Uncloud. Часть 1010.4216-10-2026
4Multi Cluster Kubernetes Configuration Management Guide07.1618-08-2026
5Что сломается в вашем сервисе завтра, если сегодня вы запустите контейнеры без проверки образов06.3724-07-2026
6Когда Docker Compose уже мало, а Kubernetes еще слишком много: знакомство с Uncloud. Часть 1010.4216-10-2026
7Как безопасно работать с контейнеризацией: 10 правил011.323-09-2026
8Контейнеризация без лишней сложности: зачем бизнесу управляемая Kubernetes-платформа015.4306-10-2026
9쿠버네티스 구성 모범 사례013.0225-11-2025
10Статичный сайт с Vite + Caddy + Docker: пошаговое руководство07.1603-08-2026

Классификация: Мнения. Схожих патентов: 0. Схожих новостей: 10. Тональность: 0. Информативность: 12.86. Источник: habr.com.