Большинство приложений разработаны таким образом, что код, конфигурация и данные слабо связаны между собой.
Файлы конфигурации и данные не являются жестко закодированными как часть кода приложения.
Вместо этого конфигурация и данные загружаются из внешнего источника.
Это позволяет развертывать приложение в различных средах, не требуя изменений в исходном коде.
Kubernetes использует концепцию secrets и configmaps для отделения информации о конфигурации от образов контейнеров.
Приложениям часто требуется доступ к конфиденциальной информации.
Например, внутреннему веб-приложению для выполнения запроса к базе данных требуется доступ к учетным данным базы данных.
Для хранения такой конфиденциальной информации используются secrets.
☸️ Шифрование секретов Kubernetes | Используем secrets
Конфигмапы используются в тех случаях, когда данные не являются конфиденциальными.
Информация, содержащаяся в конфигмапе, не требует защиты, и данные хранятся в виде обычного текста.
Данные в секрете (secrets) не хранятся в виде обычного текста, а кодируются Base64.
На момент написания этой статьи Kubernetes имеет следующие известные ограничения для поддержки runtime обновления configmap и secrets без перезапуска:
- Configmap и/или secrets, когда они смонтированы как тома. Если эти ресурсы используются как переменные окружения, то обновление во время выполнения не поддерживается.
- Контейнер, использующий ConfigMap в качестве монтируемого тома subPath, не будет получать обновления ConfigMap.
Создадим секрет
Мы создадим фиктивный секрет, который будем использовать для тестирования обновления времени выполнения на нашей установке:
У меня уже есть SSH ключ, который я скопирую в текущий PATH, а затем создам секрет с помощью команды CLI:
# cp ~/.ssh/id_rsa ssh-privatekey # kubectl create secret generic secret1 --from-file=ssh-privatekey --type=kubernetes.io/ssh-auth secret/secret1 created
Убедитесь, что секрет создан:
kubectl get secret
Создадим ConfigMap
Давайте также создадим один ConfigMap для демонстрации:
]# cat file.txt samplevar: samplevalue ]# kubectl create configmap cm1 --from-file=file.txt configmap/cm1 created ]# kubectl get cm NAME DATA AGE cm1 1 3s
Итак, теперь у нас есть новый конфигмап “cm1”, созданный в нашем пространстве имен по умолчанию.
Создадим деплоймент
Нам понадобится deploymenty или набор подов, на которых мы сможем проверить шаги из этой статьи.
Я создам небольшой депйломент nginx и 1 replicaset и смонтирую наши configmap и секреты как volumeMounts.
apiVersion: apps/v1 apiVersion: apps/v1 kind: Deployment metadata: labels: app: nginx name: nginx spec: replicas: 1 selector: matchLabels: app: nginx template: metadata: labels: app: nginx spec: containers: - image: bcmt-registry:5000/secure-ssh:sudo name: nginx volumeMounts: - name: cm-volume mountPath: /etc/config - name: secret-volume mountPath: /etc/sshconfig volumes: - name: cm-volume configMap: name: cm1 - name: secret-volume secret: secretName: secret1
Запустим деплой:
kubectl create -f nginx.yml
Проверьте, запущен ли под nginx.
]# kubectl get pods NAME READY STATUS RESTARTS AGE nginx-578dcf9879-bpp5m 1/1 Running 0 64s
Сохраните и выйдите из файла.
Команда kubectl edit откроет configmap для записи, используя EDITOR по умолчанию в вашем дистрибутиве.
Теперь проверьте содержимое вашего configmap.
Мы установили его в файл /etc/config/file.txt
# kubectl exec -it nginx-686b7b4785-p92qz -- cat /etc/config/file.txt samplevar: value1
Как и ожидалось, новое значение переменной было добавлено без перезапуска пода:
# kubectl get pods NAME READY STATUS RESTARTS AGE nginx-686b7b4785-p92qz 1/1 Running 0 5m1s
Автоматическое обновление секретов без перезапуска пода
Аналогичным образом попробуем обновить секрет Kubernetes, который мы создали в предыдущих шагах, с помощью команды kubectl edit.
Для пояснения я создал новый SSH-ключ:
# ssh-keygen -t rsa -f new-ssh-privatekey -P ""
Затем преобразуйте закрытый ключ в формат base64
# cat new-ssh-privatekey | base64 --wrap=0 LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeENncFVHaXFPN2orN2wweWV5VEUyTHZRSWRqOW1TVndRc2hGVFVGUVE1TEVLeUlaClVsVWVHWWVXMWFycThHazA1UGNNaGRGWWUyQ1BSdTR3Yzgvc3grakJLRXdUcFVneGkvemZYcVhTaWExRkVucHkKTzBWQWlPOG9NQWg2RC9BbEswWDVDd0NxeG1SaC9KaVcxUTM3ZEN4djRmZnRZRjlUVjBTL3JPeUhqOTdUaUdFRwpMa2hIREpMaGVIUFVoVndNdzIyQnpDTEdtVE1YZVJTTk02TnNCNmhIOFNBamN1TTk0ZDhJZzJLRXh4aVVNY3FkCjd5SDhMYTFXVHZveTRzd3FnV0w5WnNScjNDZzY5TDRxTk56b0JoUEtybzI1a1RweE9iZG1PMzV3WUpTODFTNFYKNkRsdEFlV1FzNzZGR285LzI0b2Q2RVdpak14d281TjFKZVVsd1FJREFRQUJBb0lCQUN5WFBKME16Zlg1bmVvdAp3WFlBNjhhaEd6VTJrSitweFJWSlZZZTBXenloTm5yZnE0WHQxNFBTTU5XdG51NjcyOHhZNUwzZTB4Qm82T2trCjZGckxYM1lxVVE2S0RNVTczaGVHaW5pSGxZNjZsc01XbHJVbWp2OFI3cjdNam9MbEFtNE40QWxDUTVBSjdjUncKSTRtWFBod3dwZFptZDgyNm5jVnUyV3ZEOFNVaENtczQ0aldmcFVteFFNSjZuaUo4RkthdUp1MVc3ZEJubnhXUQpRY2Q2TUJuYU0xalVPUlRBWDdFVmR0Y0JwaFVDTU1vTnZSMEpmR040cGJ5elViSFN4MXFudlJOblNRQk1Pa2xlCjZueWNCL3p0M3hYVFpsekVtYVdoVVA0ajhSbDhqdkdiNVlCbVA0aDJPMlBhVUo3ZmJMWFk3VFk5ZEl1eE05ajcKd21NVldXRUNnWUVBNUE3YkV4RVdwcHBmMXAyUVNFeTFzRjBrNTQ4NkFvamFiLzF6MlIxYU40My9QaERTaWpKZgpXL1pJV2Q3NkplcjRSd2g1YmZsUTR1S3U1bzk4UjBBYVQ3TytValVjeC9jNmhjV2RxNUhTQS92SCtWamVHVjJhCmpWVkdTeXVmTzNTRnRJbnZZYUlpVUJ0WVVxYjQ1VFYyQWhkZnJucHRKbTFCeDA3VVIrWXJYMDBDZ1lFQTNEQzIKSkZwd09sR1RabGc4Y2RmRDl6NktvTmhha2RTcEZQUzIwQkQrZHNTL1FPRk9vWlloLzJIVFlDeVlpTStMRElOSAo3Q3pUL3RrbkNaN1RObTdUcHREU0VudE1GRm12eExERjU5ZXZGZy9tZ09MK3MzcUtwTzJSSUFVcTdpdVRLbzRUCkNmcm1SMHdjUHdLOTZ4SFBiLzlpNnAxbDdSRmpBb0tpTG9wWFRrVUNnWUJNckxuM0ZSMjZjZGlhL1dxUEJFdHAKdWtjNEd5MXp3TE5BUjhSMVVLc09WbzFrUHArcW12ajRvRHIvRERxcUdPL1VZZ01CZUhzN2JOOUU0U1QxaDVYUgpDaXVJMUJhVEhJbnVnOXhZM0xQeFp1dDY1K2YwTzBaRkVsQ0o0V2F0eEtWWFo3QzE4Sjc4czlUa0pRTTFmTjNxCkloV25RYjRFMTJMd01ZNnBoYmM3V1FLQmdRQ0szUUdScmFPSGMvamttNU1MTE1yK3UyZU1Cc1lmb0NFK0FSTGwKNTBIRHYxTHFaTzFGQkx6T0pYQzcvNFAzREFTaVVJemtTbVVzSE9EOHRUaDQ1SzRBVDBPY3VqdUJ2Z29Xbm5GQgpSSW03L1MwZWJZbTV3UGQ5Q2dIelVxNy9ZMlc5ZWJwU0dmUnVWSGFmMm1mUnZ2cTJwRFpLeGhjSXltVkpxUDhGCklPUHNqUUtCZ1FESnQ3bGxyNGIrV1pBUWNFbjdCUEdKaHZVUjF2SjN1V1QwZ3d0TUFvYXdXWk5QNm8vL3B5cXgKaVdRNjhBbFMvT0tuVXBKWEc1TTdCajEvM0N5azNRenU0RnNMbnZhVUp1UzdFUVN1L0F4dnhtZU9CR2p6Yy92YgoxTCt1b3Y3WjkvR0dSU1VaRy9mb1M1QkdDNW9hTDVRNXlZZkNLNzl1ZmFrNi9jZzZKS1FKbWc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo=
Теперь с помощью kubectl edit я отредактирую secret1 и добавлю обновленное значение для моего закрытого ключа:
# kubectl edit secret/secret1 # Please edit the object below. Lines beginning with a '#' will be ignored, # and an empty file will abort the edit. If an error occurs while saving this file will be # reopened with the relevant failures. # apiVersion: v1 data: ssh-privatekey: LS0tLS1CRUdJTiBSU0EgUFJJVkFURSBLRVktLS0tLQpNSUlFcEFJQkFBS0NBUUVBeENncFVHaXFPN2orN2wweWV5VEUyTHZRSWRqOW1TVndRc2hGVFVGUVE1TEVLeUlaClVsVWVHWWVXMWFycThHazA1UGNNaGRGWWUyQ1BSdTR3Yzgvc3grakJLRXdUcFVneGkvemZYcVhTaWExRkVucHkKTzBWQWlPOG9NQWg2RC9BbEswWDVDd0NxeG1SaC9KaVcxUTM3ZEN4djRmZnRZRjlUVjBTL3JPeUhqOTdUaUdFRwpMa2hIREpMaGVIUFVoVndNdzIyQnpDTEdtVE1YZVJTTk02TnNCNmhIOFNBamN1TTk0ZDhJZzJLRXh4aVVNY3FkCjd5SDhMYTFXVHZveTRzd3FnV0w5WnNScjNDZzY5TDRxTk56b0JoUEtybzI1a1RweE9iZG1PMzV3WUpTODFTNFYKNkRsdEFlV1FzNzZGR285LzI0b2Q2RVdpak14d281TjFKZVVsd1FJREFRQUJBb0lCQUN5WFBKME16Zlg1bmVvdAp3WFlBNjhhaEd6VTJrSitweFJWSlZZZTBXenloTm5yZnE0WHQxNFBTTU5XdG51NjcyOHhZNUwzZTB4Qm82T2trCjZGckxYM1lxVVE2S0RNVTczaGVHaW5pSGxZNjZsc01XbHJVbWp2OFI3cjdNam9MbEFtNE40QWxDUTVBSjdjUncKSTRtWFBod3dwZFptZDgyNm5jVnUyV3ZEOFNVaENtczQ0aldmcFVteFFNSjZuaUo4RkthdUp1MVc3ZEJubnhXUQpRY2Q2TUJuYU0xalVPUlRBWDdFVmR0Y0JwaFVDTU1vTnZSMEpmR040cGJ5elViSFN4MXFudlJOblNRQk1Pa2xlCjZueWNCL3p0M3hYVFpsekVtYVdoVVA0ajhSbDhqdkdiNVlCbVA0aDJPMlBhVUo3ZmJMWFk3VFk5ZEl1eE05ajcKd21NVldXRUNnWUVBNUE3YkV4RVdwcHBmMXAyUVNFeTFzRjBrNTQ4NkFvamFiLzF6MlIxYU40My9QaERTaWpKZgpXL1pJV2Q3NkplcjRSd2g1YmZsUTR1S3U1bzk4UjBBYVQ3TytValVjeC9jNmhjV2RxNUhTQS92SCtWamVHVjJhCmpWVkdTeXVmTzNTRnRJbnZZYUlpVUJ0WVVxYjQ1VFYyQWhkZnJucHRKbTFCeDA3VVIrWXJYMDBDZ1lFQTNEQzIKSkZwd09sR1RabGc4Y2RmRDl6NktvTmhha2RTcEZQUzIwQkQrZHNTL1FPRk9vWlloLzJIVFlDeVlpTStMRElOSAo3Q3pUL3RrbkNaN1RObTdUcHREU0VudE1GRm12eExERjU5ZXZGZy9tZ09MK3MzcUtwTzJSSUFVcTdpdVRLbzRUCkNmcm1SMHdjUHdLOTZ4SFBiLzlpNnAxbDdSRmpBb0tpTG9wWFRrVUNnWUJNckxuM0ZSMjZjZGlhL1dxUEJFdHAKdWtjNEd5MXp3TE5BUjhSMVVLc09WbzFrUHArcW12ajRvRHIvRERxcUdPL1VZZ01CZUhzN2JOOUU0U1QxaDVYUgpDaXVJMUJhVEhJbnVnOXhZM0xQeFp1dDY1K2YwTzBaRkVsQ0o0V2F0eEtWWFo3QzE4Sjc4czlUa0pRTTFmTjNxCkloV25RYjRFMTJMd01ZNnBoYmM3V1FLQmdRQ0szUUdScmFPSGMvamttNU1MTE1yK3UyZU1Cc1lmb0NFK0FSTGwKNTBIRHYxTHFaTzFGQkx6T0pYQzcvNFAzREFTaVVJemtTbVVzSE9EOHRUaDQ1SzRBVDBPY3VqdUJ2Z29Xbm5GQgpSSW03L1MwZWJZbTV3UGQ5Q2dIelVxNy9ZMlc5ZWJwU0dmUnVWSGFmMm1mUnZ2cTJwRFpLeGhjSXltVkpxUDhGCklPUHNqUUtCZ1FESnQ3bGxyNGIrV1pBUWNFbjdCUEdKaHZVUjF2SjN1V1QwZ3d0TUFvYXdXWk5QNm8vL3B5cXgKaVdRNjhBbFMvT0tuVXBKWEc1TTdCajEvM0N5azNRenU0RnNMbnZhVUp1UzdFUVN1L0F4dnhtZU9CR2p6Yy92YgoxTCt1b3Y3WjkvR0dSU1VaRy9mb1M1QkdDNW9hTDVRNXlZZkNLNzl1ZmFrNi9jZzZKS1FKbWc9PQotLS0tLUVORCBSU0EgUFJJVkFURSBLRVktLS0tLQo= kind: Secret metadata: creationTimestamp: "2022-06-01T06:19:37Z" name: secret1 namespace: default resourceVersion: "19944036" selfLink: /api/v1/namespaces/default/secrets/secret1 uid: fcc91312-72d7-479a-9353-419e1c8dff64 type: kubernetes.io/ssh-auth
Сохраните и выйдите из файла.
Как и ожидалось, новое значение переменной было назаначено без перезапуска пода:
# kubectl get pods NAME READY STATUS RESTARTS AGE nginx-686b7b4785-p92qz 1/1 Running 0 15m1s
Обновление ConfigMap/Secret как переменной окружения с перезапуском пода
Теперь, если вы определили какую-либо ConfigMap или Secret как переменную окружения, то их обновление автоматически перезапустит pod.
Вот пример:
Я создам еще один конфиг мап:
# kubectl create configmap cm2 --from-literal=color=blue configmap/cm2 created
Теперь я примаплю его к нашей установке nginx в качестве переменной окружения:
# kubectl set env deployment nginx --from=configmap/cm1 deployment.apps/nginx env updated
Теперь, как только я ввожу эту команду, я вижу, что мои поды nginx перезапускаются, чтобы добавить новую переменную окружения.
Заключение
Монтирование конфигмапов и секретов в виде томов внутри контейнера дает конечному пользователю большую гибкость, поскольку эти значения можно изменять и обновлять внутри приложения на лету.
Однако при использовании этого подхода есть одна загвоздка.
Когда мы указываем путь монтирования, не существующий внутри контейнера, например, /etc/config или /etc/sshconfig, он создается автоматически внутри контейнера.
Однако, если путь уже существует внутри контейнера, все текущие файлы и каталоги внутри этого пути будут перезаписаны.
Чтобы избежать этого, в разделе volumeMounts используется опция subPath.