🐳 Раздел 14 · Вопрос #16

В чём разница между ConfigMap и Secret

И ConfigMap, и Secret — это объекты Kubernetes, предназначенные для отделения конфигурации и параметров окружения от скомпилированного кода приложения (согласно III фактору мето...


🟢 Junior Level

30-секундный ответ

И ConfigMap, и Secret — это объекты Kubernetes, предназначенные для отделения конфигурации и параметров окружения от скомпилированного кода приложения (согласно III фактору методологии Twelve-Factor App).

Главная разница между ними заключается в степени конфиденциальности данных:

  • ConfigMap: предназначен для открытых, неконфиденциальных настроек (URL баз данных, порты сервисов, флаги фичей, логирование). Данные хранятся в виде обычного открытого текста.
  • Secret: предназначен для чувствительных данных и учётных записей (пароли СУБД, приватные SSH/TLS-ключи, API-токены, учетные записи Docker Registry). В манифестах значения кодируются в формат Base64.

Наглядная аналогия из жизни

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

Примеры базовых манифестов

1. ConfigMap (Открытый текст)

apiVersion: v1
kind: ConfigMap
metadata:
  name: order-service-config
data:
  DB_HOST: "postgres.prod.svc.cluster.local"
  DB_PORT: "5432"
  SPRING_PROFILES_ACTIVE: "prod"

2. Secret (Base64 кодирование)

apiVersion: v1
kind: Secret
metadata:
  name: order-service-secret
type: Opaque
data:
  # echo -n "secret123" | base64 -> c2VjcmV0MTIz
  DB_PASSWORD: c2VjcmV0MTIz

[!WARNING] Base64 — это НЕ шифрование! Это лишь обратимый алгоритм кодирования двоичных данных в текст. Любой разработчик, имеющий доступ к чтению секретов в кластере, может мгновенно декодировать пароль командой echo "c2VjcmV0MTIz" | base64 -d.


🟡 Middle Level

Способы внедрения в Pod: Переменные окружения vs Тома

                         [ ConfigMap / Secret ]
                                    │
         ┌──────────────────────────┴──────────────────────────┐
         ▼                                                     ▼
  [ 1. Environment Variables ]                          [ 2. Volume Mount ]
  - Значения фиксируются при старте                     - Монтируется как каталог с файлами
  - ❌ Не обновляются без перезапуска пода              - 🟢 Обновляются на лету без перезапуска!

1. Через переменные окружения (env / envFrom)

containers:
- name: java-app
  image: order-service:1.0
  env:
  - name: DATABASE_HOST
    valueFrom:
      configMapKeyRef:
        name: order-service-config
        key: DB_HOST
  - name: DATABASE_PASSWORD
    valueFrom:
      secretKeyRef:
        name: order-service-secret
        key: DB_PASSWORD

Критический недостаток: если вы измените пароль в Secret, значение переменной окружения в работающем Linux-процессе НЕ изменится. Требуется перезапуск пода (kubectl rollout restart).

2. Через монтирование тома (Volume Mounts)

containers:
- name: java-app
  volumeMounts:
  - name: config-dir
    mountPath: /app/config
    readOnly: true
volumes:
- name: config-dir
  configMap:
    name: order-service-config

Преимущество: Kubelet автоматически отслеживает изменения в ConfigMap и обновляет файлы на диске пода без перезапуска контейнера!


Секция stringData в Secret

Чтобы инженерам не приходилось вручную кодировать каждую строку через base64, Kubernetes поддерживает поле stringData:

apiVersion: v1
kind: Secret
metadata:
  name: my-secret
type: Opaque
stringData:
  DB_PASSWORD: "plain-text-password-here" # Пишется открытым текстом!

При сохранении манифеста API-сервер сам автоматически переводит все значения из stringData в Base64 и записывает их в поле data.


Флаг immutable: true

Начиная с Kubernetes 1.21, для обоих объектов доступен флаг неизменяемости:

immutable: true
  1. Защита от случайных правок: защищает продакшен-базы данных от сбоев из-за неверной правки конфига.
  2. Снижение нагрузки на кластер: Kubelet перестаёт непрерывно опрашивать API Server в поисках изменений в файлах конфигурации, радикально экономя CPU и память в кластерах с тысячами подов.

🔴 Senior Level

Иллюзия безопасности Secret и Encryption at Rest

По умолчанию Kubernetes хранит все объекты Secret в базе данных etcd в открытом незашифрованном виде (только Base64). Злоумышленник, получивший доступ к резервной копии etcd или диску мастер-ноды, получает доступ ко всем секретам кластера.

Как включить реальное шифрование (Encryption at Rest)

Настраивается на уровне kube-apiserver через конфигурационный файл --encryption-provider-config:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      # Рекомендуемый enterprise-стандарт: аппаратный KMS облака
      - kms:
          apiVersion: v2
          name: aws-kms-provider
          endpoint: unix:///var/run/kmsplugin/socket.sock
      # Либо локальное симметричное AES-GCM/AES-CBC шифрование
      - aescbc:
          keys:
            - name: key1
              secret: <32-byte-base64-key>
      - identity: {} # Запасной провайдер для чтения незашифрованных старых данных

Когда Kubelet обновляет смонтированный ConfigMap на диске контейнера, он не перезаписывает файлы напрямую. Если бы процесс Java читал файл в момент перезаписи, возникла бы ошибка частичного чтения (Torn Read). Kubernetes решает это с помощью трёхуровневой структуры символических ссылок:

/app/config/
├── application.yaml -> ..data/application.yaml
├── ..data -> ..2026_09_26_14_30_00.123456789 (Симлинк на актуальную папку)
└── ..2026_09_26_14_30_00.123456789/           (Каталог с реальными файлами)
    └── application.yaml

При обновлении конфига Kubelet загружает новые файлы в каталог с новой временной меткой и выполняет одну атомарную системную операцию замены симлинка ..data (rename()).


Production-стандарт: External Secrets Operator (ESO)

В зрелых enterprise-архитектурах секреты никогда не создаются вручную через YAML и не хранятся в Git. Используется External Secrets Operator:

apiVersion: external-secrets.io/v1beta1
kind: ExternalSecret
metadata:
  name: order-db-secret
spec:
  secretStoreRef:
    name: hashicorp-vault # Интеграция с Vault, AWS Secrets Manager или GCP Secret Manager
    kind: ClusterSecretStore
  target:
    name: order-service-db-creds # Имя создаваемого нативного K8s Secret
  data:
  - secretKey: password
    remoteRef:
      key: secret/data/production/database
      property: password

ESO самостоятельно выполняет ротацию ключей, синхронизацию и отзыв прав без вмешательства разработчика.


4 Tricky Questions

1. Если обновить значение в объекте ConfigMap, обновятся ли автоматически переменные окружения (env) и смонтированные тома (volumeMounts) внутри уже работающего контейнера?

Ответ:

  • Переменные окружения (env / envFrom): НЕ обновятся никогда. Переменные окружения формируются ядром Linux в момент создания процесса контейнера (execve) и остаются неизменными до полной гибели процесса. Чтобы приложение подхватило новые переменные, под необходимо принудительно перезапустить (kubectl rollout restart).
  • Смонтированные тома (volumeMounts): обновятся автоматически. Kubelet в рамках цикла синхронизации (по умолчанию от 10 до 60 секунд) обновит симлинк ..data в смонтированной директории. Однако само приложение (например, Spring Boot) должно уметь перечитывать файлы с диска (через @RefreshScope, Spring Cloud Kubernetes или собственный файловый вотчер).

2. В чём разница между полями data и stringData в спецификации Secret, и что произойдёт, если одно и то же имя ключа указано в обоих полях одновременно?

Ответ:

  • data: принимает значения, строго закодированные в Base64. Применяется в автоматизированных пайплайнах CI/CD.
  • stringData: принимает значения в виде обычного открытого текста (Plain Text). Это вспомогательное поле «только для записи» (Write-Only), созданное для удобства человека.
  • Конфликт ключей: если один и тот же ключ объявлен и в data, и в stringData, значение из stringData имеет абсолютный приоритет и перезаписывает значение из data. При чтении секрета из API поле stringData всегда удаляется, а итоговый результат возвращается строго в поле data в Base64.

3. Почему Java-библиотека java.nio.file.WatchService, настроенная на конкретный файл конфигурации /app/config/application.properties, перестаёт получать события обновления после первого же изменения ConfigMap?

Ответ: Kubelet обновляет файлы тома не модификацией байтов внутри существующего дескриптора файла, а атомарной подменой симлинка ..data на новую директорию. Старый файл, на который был подписан WatchService Java по его Linux Inode, фактически удаляется из дерева директорий. Ссылка на старый Inode становится «осиротевшей», и операционная система больше никогда не генерирует для неё событий ENTRY_MODIFY. Решение: подписывать WatchService не на конкретный целевой файл, а на родительскую директорию /app/config/, отслеживать событие пересоздания симлинка и переоткрывать файловый поток.


4. Каковы жесткие ограничения на максимальный размер данных в одном объекте ConfigMap или Secret, и чем обусловлен этот лимит?

Ответ: Максимальный размер любого объекта ConfigMap или Secret составляет строго 1 Мегабайт (1 MiB). Это ограничение продиктовано архитектурой хранилища базы данных etcd:

  • По умолчанию максимальный размер одного gRPC-запроса и одной транзакции в etcd жестко ограничен 1.5 МБ.
  • Хранение больших конфигурационных файлов, сертификатов или бинарных файлов в ConfigMap приводит к катастрофической фрагментации базы данных etcd, падению производительности консенсуса Raft и деградации всего кластера. Большие файлы должны передаваться через постоянные дисковые тома (PersistentVolumes) или внешние объектные хранилища (S3, MinIO).

🎯 Шпаргалка для интервью

Главное о ConfigMap и Secret

  • ConfigMap: открытые настройки (URL, порты, параметры); хранится в открытом тексте.
  • Secret: конфиденциальные данные (пароли, сертификаты, токены); в YAML пишется в Base64.
  • Безопасность: Base64 $\ne$ шифрование! Для безопасности etcd требуется Encryption at Rest.
  • Лимит размера: строго 1 МБ (ограничение etcd).
  • Переменные окружения (env): фиксируются при старте пода; не обновляются без перезапуска.
  • Тома (volumes): обновляются на лету Kubelet’ом через замену симлинков ..data.
  • stringData: позволяет писать секреты обычным текстом; API-сервер сам сделает Base64.
  • Enterprise-стандарт: External Secrets Operator (ESO) + HashiCorp Vault.

Сравнительная таблица

| Параметр | ConfigMap | Secret | | :— | :— | :— | | Формат в манифесте | Открытый текст UTF-8 | Base64 (data) или текст (stringData) | | Хранение в etcd | Plain Text | Plain Text (по умолчанию) / Зашифровано (AES/KMS) | | Встроенные типы | Нет (универсальный) | Opaque, kubernetes.io/tls, dockerconfigjson | | Назначение | Переменные сред, конфиги Spring | Пароли БД, токены, SSL-сертификаты |

Красные флаги на собеседовании (Чего говорить нельзя)

  • ❌ «Kubernetes Secret надёжно шифрует пароли с помощью Base64» — Base64 это кодирование, декодируется в одну строчку.
  • ❌ «Если изменить ConfigMap, переменные окружения в работающем Java-контейнере обновятся автоматически» — env vars не обновляются без рестарта пода.
  • ❌ «В ConfigMap можно положить JAR-файл или видеоролик размером 50 МБ» — лимит любого объекта равен ровно 1 МБ.

Связанные вопросы