🐳 Розділ 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 МБ.

Пов’язані питання