Kubernetes

Теория: Конфигурация приложений

При работе с приложениями в Kubernetes возникает важная задача: как правильно передавать конфигурацию? Хардкодить значения в код неправильно, пересобирать образы для каждой среды (development, staging, production) неудобно. ConfigMap и Secret решают эту задачу, позволяя отделить конфигурацию от кода.

Проблема конфигурации

Представьте веб-приложение, которому нужны параметры подключения к базе данных, URL внешних API, настройки кеширования и feature flags. Эти параметры различаются между окружениями: в development используется локальная база данных, в production — отказоустойчивый кластер.

Традиционный подход — создавать разные Docker-образы для каждой среды. Но это приводит к проблемам: нельзя быть уверенным, что код в production идентичен протестированному в staging; изменение одного параметра требует полной пересборки образа; секретные данные попадают в слои образа и могут утечь.

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

Что такое ConfigMap

ConfigMap — это объект Kubernetes, который хранит конфигурационные данные в виде пар ключ-значение. Вы создаёте ConfigMap с нужными параметрами и подключаете его к Pod через переменные окружения или файлы.

Создадим простой ConfigMap с настройками приложения:

apiVersion: v1
kind: ConfigMap
metadata:
  name: app-config
data:
  DATABASE_HOST: "postgres.default.svc.cluster.local"
  DATABASE_PORT: "5432"
  CACHE_ENABLED: "true"
  LOG_LEVEL: "info"

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

Создайте ConfigMap несколькими способами:

# Из YAML-манифеста
kubectl apply -f app-config.yaml

# Из литеральных значений
kubectl create configmap app-config \
  --from-literal=DATABASE_HOST=postgres.default.svc.cluster.local \
  --from-literal=DATABASE_PORT=5432

# Из файла
echo "info" > log_level.txt
kubectl create configmap app-config --from-file=log_level.txt

# Из директории
kubectl create configmap app-config --from-file=config-dir/

Посмотрите на созданный ConfigMap:

# Список ConfigMap
kubectl get configmap

# Содержимое ConfigMap
kubectl describe configmap app-config

# Вывести в YAML
kubectl get configmap app-config -o yaml

# Редактировать в редакторе
kubectl edit configmap app-config

Использование ConfigMap в Pod

Существует два основных способа использования ConfigMap в Pod: как переменные окружения или как файлы в томе.

Переменные окружения (отдельные ключи)

Самый простой способ — монтировать значения из ConfigMap как переменные окружения:

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
    - name: app
      image: busybox
      command: ["sh", "-c", "echo $DATABASE_HOST:$DATABASE_PORT && sleep 3600"]
      env:
        - name: DATABASE_HOST
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DATABASE_HOST
        - name: DATABASE_PORT
          valueFrom:
            configMapKeyRef:
              name: app-config
              key: DATABASE_PORT

Здесь переменная DATABASE_HOST получает значение из ключа DATABASE_HOST в ConfigMap app-config.

Переменные окружения (все ключи сразу)

Если нужно импортировать все ключи сразу, используйте envFrom:

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
    - name: app
      image: busybox
      command: ["sh", "-c", "env && sleep 3600"]
      envFrom:
        - configMapRef:
            name: app-config

Все ключи из ConfigMap станут переменными окружения.

Важные нюансы envFrom:

  • Ключи импортируются как есть: database_host станет переменной database_host, не DATABASE_HOST
  • Ключи с недопустимыми символами (например, дефисами) будут пропущены — дефис недопустим в именах переменных окружения
  • Возможны конфликты с системными переменными окружения

Файлы в томе

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

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
    - name: app
      image: busybox
      command: ["sh", "-c", "cat /etc/config/DATABASE_HOST && sleep 3600"]
      volumeMounts:
        - name: config-volume
          mountPath: /etc/config
  volumes:
    - name: config-volume
      configMap:
        name: app-config

В контейнере появится директория /etc/config с файлами: DATABASE_HOST, DATABASE_PORT, CACHE_ENABLED, LOG_LEVEL. Каждый файл содержит соответствующее значение.

Можно выбрать конкретные ключи и задать имена файлов:

volumes:
  - name: config-volume
    configMap:
      name: app-config
      items:
        - key: DATABASE_HOST
          path: db-host.txt
        - key: DATABASE_PORT
          path: db-port.txt

Теперь в директории /etc/config будут файлы db-host.txt и db-port.txt.

ConfigMap для конфигурационных файлов

ConfigMap отлично подходит для хранения полных конфигурационных файлов. Например, конфигурация Nginx:

apiVersion: v1
kind: ConfigMap
metadata:
  name: nginx-config
data:
  default.conf: |
    server {
      listen 80;
      server_name example.com;

      location / {
        return 200 'Hello from ConfigMap!\n';
        add_header Content-Type text/plain;
      }
    }

Символ | позволяет хранить многострочный текст. Смонтируйте этот ConfigMap в Pod с Nginx:

apiVersion: v1
kind: Pod
metadata:
  name: nginx-pod
spec:
  containers:
    - name: nginx
      image: nginx:1.27
      volumeMounts:
        - name: nginx-config
          mountPath: /etc/nginx/conf.d
  volumes:
    - name: nginx-config
      configMap:
        name: nginx-config

Nginx прочитает конфигурацию из /etc/nginx/conf.d/default.conf при запуске.

Что такое Secret

Secret предназначен для хранения чувствительных данных: паролей, ключей API, сертификатов.

Важно понимать: Secret — это механизм доставки чувствительных данных в Pod, а не система безопасного хранения. По умолчанию значения хранятся в etcd в кодировке base64 — это не шифрование. Любой с доступом к API-серверу может их прочитать.

Чем Secret отличается от ConfigMap:

  • Значения кодируются в base64 (но это не защита!)
  • Kubernetes может шифровать Secret в etcd (требует отдельной настройки)
  • Secret не выводится в kubectl describe pod
  • Secret монтируется в tmpfs (память), не пишется на диск ноды
  • Можно ограничить доступ через RBAC (система прав доступа в Kubernetes, рассмотрим в отдельном уроке)

Создадим Secret с параметрами подключения к базе данных. Используйте stringData — не нужно вручную кодировать в base64:

apiVersion: v1
kind: Secret
metadata:
  name: database-secret
type: Opaque
stringData:
  username: postgres
  password: supersecret

Если видите чужой манифест с data вместо stringData — там значения уже в base64:

apiVersion: v1
kind: Secret
metadata:
  name: database-secret
type: Opaque
data:
  username: cG9zdGdyZXM=
  password: c3VwZXJzZWNyZXQ=

Кодирование и декодирование:

# Закодировать значение
echo -n "postgres" | base64
# Результат: cG9zdGdyZXM=

# Декодировать значение
echo "cG9zdGdyZXM=" | base64 --decode
# Результат: postgres

Создавайте Secret из командной строки:

# Из литеральных значений (рекомендуемый способ)
kubectl create secret generic database-secret --from-literal=username=postgres --from-literal=password=supersecret

# Для TLS-сертификатов
kubectl create secret tls tls-secret --cert=path/to/cert.pem --key=path/to/key.pem

# Для доступа к приватному Docker registry
kubectl create secret docker-registry registry-secret --docker-server=registry.example.com --docker-username=user --docker-password=password

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

# Список Secret
kubectl get secret

# Подробная информация (значения скрыты)
kubectl describe secret database-secret

# Вывести значение (декодированное)
kubectl get secret database-secret -o jsonpath='{.data.password}' | base64 --decode

# Редактировать в редакторе
kubectl edit secret database-secret

Использование Secret в Pod

Secret используется так же, как ConfigMap: через переменные окружения или файлы.

Переменные окружения:

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
    - name: app
      image: busybox
      command: ["sh", "-c", "echo $DATABASE_PASSWORD && sleep 3600"]
      env:
        - name: DATABASE_USERNAME
          valueFrom:
            secretKeyRef:
              name: database-secret
              key: username
        - name: DATABASE_PASSWORD
          valueFrom:
            secretKeyRef:
              name: database-secret
              key: password

Приложение получит переменные DATABASE_USERNAME и DATABASE_PASSWORD с расшифрованными значениями.

Файлы в томе:

apiVersion: v1
kind: Pod
metadata:
  name: app-pod
spec:
  containers:
    - name: app
      image: busybox
      command: ["sh", "-c", "cat /etc/secrets/password && sleep 3600"]
      volumeMounts:
        - name: secret-volume
          mountPath: /etc/secrets
          readOnly: true
  volumes:
    - name: secret-volume
      secret:
        secretName: database-secret

В директории /etc/secrets появятся файлы username и password с расшифрованными значениями. Используйте readOnly: true для дополнительной безопасности.

Основные типы Secret

Kubernetes поддерживает несколько типов Secret для разных сценариев. Вот основные:

  • Opaque — базовый тип для произвольных данных. Используется по умолчанию.

  • kubernetes.io/tls — для хранения TLS-сертификатов и ключей. Требует ключи tls.crt и tls.key.

  • kubernetes.io/dockerconfigjson — для аутентификации в приватных Docker-реестрах:

    kubectl create secret docker-registry registry-secret --docker-server=registry.example.com --docker-username=user --docker-password=password

    Используйте этот Secret в Pod для загрузки приватных образов:

    apiVersion: v1
    kind: Pod
    metadata:
      name: app-pod
    spec:
      imagePullSecrets:
        - name: registry-secret
      containers:
        - name: app
          image: registry.example.com/myapp:1.0
  • kubernetes.io/basic-auth — для хранения имени пользователя и пароля.

Полный список типов — в документации Kubernetes.

Обновление конфигурации

Когда вы обновляете ConfigMap или Secret, возникает вопрос: как обновить работающие Pod? Kubernetes не перезапускает Pod автоматически при изменении ConfigMap/Secret.

Если конфигурация смонтирована как том, изменения появятся в контейнере через некоторое время (обычно 1-2 минуты). Но приложение должно само отслеживать изменения файлов и перечитывать конфигурацию. Большинство приложений этого не делают.

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

Чтобы принудительно обновить Pod, можно инициировать rolling update у Deployment, даже если образ не менялся. Для этого достаточно изменить любую часть шаблона Pod. Kubernetes воспринимает это как новую версию приложения и пересоздаёт Pod.

Один из простых способов — изменить аннотацию:

kubectl patch deployment app-deployment \
  -p '{"spec":{"template":{"metadata":{"annotations":{"configmap-version":"2"}}}}}'

Что происходит после этой команды:

  1. API Server принимает обновлённый манифест Deployment. Шаблон Pod внутри Deployment теперь отличается от предыдущего.
  2. Deployment Controller замечает изменение и создаёт новый ReplicaSet.
  3. Scheduler размещает новые Pod, kubelet запускает контейнеры уже с обновлённой конфигурацией.
  4. После успешного старта новых Pod старые постепенно останавливаются.

Снаружи это выглядит как «перезапуск», но внутри — полноценное безопасное rolling-обновление без простоя сервиса.

Рекомендуемые программы

+7 800 100 22 47

бесплатно по РФ

+7 495 085 21 62

бесплатно по Москве

108813 г. Москва, вн.тер.г. поселение Московский,
г. Московский, ул. Солнечная, д. 3А, стр. 1, помещ. 20Б/3
ОГРН 1217300010476
ИНН 7325174845