Секреты используют в кластерах Kubernetes в разных целях. Одна из основных — разместить учетные данных сервисных аккаунтов, чтобы аутентифицировать рабочие нагрузки в кластерах API-сервером.
Команда VK Cloud Solutions перевела статью о необычном поведении секретов при их ручном создании: иногда они вроде бы создаются, но на самом деле исчезают.
В чем суть проблемы
Когда вы создаете сервисный аккаунт, Kubernetes автоматически создает секрет к нему. Потом вы указываете, что этот сервисный аккаунт надо использовать для вашей рабочей нагрузки. И Kubernetes монтирует в нее секрет.
Обычно это происходит автоматически. Но как-то раз Кристоф рассказал мне, что секреты можно создавать вручную, добавляя к ним аннотации и указывая имя сервисного аккаунта. В этом случае Kubernetes автоматически указывает в секрете для этого аккаунта токен сервисного аккаунта.
Но при таком подходе я заметил нечто странное. Если при создании секрета указывать имя сервисного аккаунта, которое не существует в пространстве имен, kubectl сообщает, что секрет создан, а на самом деле его нет! Возьмем в качестве примера такой секрет:
apiVersion: v1
kind: Secret
metadata:
name: build-robot-secret
annotations:
kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token
Сохраним его в файл с именем build-robot-secret.yaml и выполним kubectl create -f build-robot-secret.yaml; получаем:
kubectl create -f base-secret.yaml
secret/build-robot-secret created
Но если потом выполнить команду kubectl get secrets, секрета нигде не видно!
kubectl get secrets
NAME TYPE DATA AGE
default-token-nxrbt kubernetes.io/service-account-token 3 40m
В чем же дело
Итак, мы столкнулись с загадкой: почему наш секрет, который представляет собой допустимый YAML-файл, создается, но тут же исчезает после создания? Сначала я подумал, что созданный секрет просто не отображается. Чтобы это проверить, можно посмотреть содержимое базы данных etcd-кластера. Для кластера это «классическое» хранилище информации, так что логично поискать именно там.
Чтобы проверка проходила легче, в настройках кластера я выключаю аутентификацию etcd, используя набор методов etcd-noauth и kube-security-lab. С этой настройкой можно повторно выполнить цикл создания секрета и с помощью etcdctl посмотреть, что отображается или не отображается в etcd. Структура у базы данных достаточно простая, так что мы просто попросим ее показать содержимое /registry/secrets/default, то есть все секреты в пространстве имен по умолчанию.
etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=172.18.0.3:2379 get /registry/secrets/default --prefix --keys-only
/registry/secrets/default/default-token-nxrbt
Видим только токен нашего сервисного аккаунта по умолчанию. Теперь мы знаем, что секрет не сохранился.
А что случится, если мы изменим существующий секрет, который указывает на корректный сервисный аккаунт, так, чтобы он указывал на недопустимый аккаунт?
Создадим корректный аккаунт:
apiVersion: v1
kind: Secret
metadata:
name: extra-default-secret
annotations:
kubernetes.io/service-account.name: default
type: kubernetes.io/service-account-token
И если после этого выполним команду kubectl get secrets, то увидим, что он там:
❯ kubectl get secrets
NAME TYPE DATA AGE
default-token-nxrbt kubernetes.io/service-account-token 3 5h20m
extra-default-secret kubernetes.io/service-account-token 3 4s
Так. Теперь посмотрим, что случится, если мы выполним команду kubectl edit secret extra-default-secret и изменим поле service-account.name на test? Оказывается, после редактирования секрет исчезает! Если заглянуть в etcd, как мы это сделали выше, становится ясно — наш секрет исчез и из datastore.
Почему это произошло
Я думаю, во всем виноват один из контроллеров, которые в Kubernetes используются для управления состоянием кластера.
Множество контроллеров работают в связке «контроллер-менеджер». Их задача — выполнять циклы наблюдения за состоянием определенного класса объектов, которые проверяют желаемое состояние. Потом контроллеры меняют конфигурацию кластера, чтобы это желаемое состояние сохранялось.
Вот что можно обнаружить, просматривая документацию по теме «Сервисные аккаунты в Kubernetes»:
TokenController входит в состав kube-controller-manager. Он работает в асинхронном режиме. Он:
Следит за созданием ServiceAccount и создает соответствующий Secret токена ServiceAccount для доступа к API.
Следит за удалением ServiceAccount и удаляет все соответствующие Secrets токенов ServiceAccount.
Следит за добавлением Secret токена ServiceAccount, проверяет наличие исходного ServiceAccount и при необходимости добавляет токен к Secret.
Следит за удалением Secret и при необходимости удаляет ссылку из соответствующего ServiceAccount.
В документации не упоминается, что он удаляет секреты, не соответствующие существующему сервисному аккаунту. Но, судя по всему, это делает именно он. Как это проверить? Здесь нам должен прийти на помощь аудит в Kubernetes. Поскольку в Kubernetes все контроллеры отправляют запросы через API-сервер, мы можем посмотреть, что происходит, когда пытаемся создать наш build-robot-secret.
Команда kubectl create, раньше выполнявшаяся на этом кластере, подтверждает нашу гипотезу. Происходят три события.
Событие create по инициативе пользователя kubernetes-admin — это имя первого пользователя по умолчанию в кластере kubeadm.
Событие get по инициативе tokens-controller. Что интересно, здесь в качестве «пользователя» выступает system:kube-controller-manager, но агент пользователя указывает, что это tokens-controller.
Событие delete по инициативе tokens-controller, которое выглядит так:
{
«kind": "Event",
«apiVersion": «audit.k8s.io/v1",
"level": «RequestResponse",
"auditID": "ef0f48eb-a606-4f50-afcd-517268a5cd2e",
"stage": «ResponseComplete",
"requestURI": "/api/v1/namespaces/default/secrets/build-robot-secret",
"verb": "delete",
"user": {
"username": «system:kube-controller-manager",
«groups": [
"system:authenticated"
]
},
«sourceIPs": [
"172.18.0.3"
],
"userAgent": "kube-controller-manager/v1.21.1 (linux/amd64) kubernetes/5e58841/tokens-controller",
«objectRef": {
"resource": "secrets",
"namespace": «default",
"name": "build-robot-secret",
"apiVersion": "v1"
},
"responseStatus": {
"metadata": {},
"status": "Success",
"code": 200
},
"requestObject": {
"kind": "DeleteOptions",
"apiVersion": "v1",
"preconditions": {
"uid": "16c9f476-34fe-48a0-b623-100f7de93d0f"
}
},
Заключение
Как я уже упоминал в начале статьи, сенсации не произошло. И все-таки интересно углубиться в работу контроллеров Kubernetes. Чтобы поддерживать кластер в желаемом состоянии, они постоянно наблюдают за ресурсами и корректируют их.
Есть, конечно, редко встречающийся сценарий, когда секреты разрешено редактировать, но запрещено удалять. В остальных случаях описанная ситуация практически ни на что не влияет. И все-таки, может быть, это поможет кому-то разобраться, почему без проблем созданный секрет не сохраняется в кластере!
Команда VK Cloud Solutions перевела статью о необычном поведении секретов при их ручном создании: иногда они вроде бы создаются, но на самом деле исчезают.
В чем суть проблемы
Когда вы создаете сервисный аккаунт, Kubernetes автоматически создает секрет к нему. Потом вы указываете, что этот сервисный аккаунт надо использовать для вашей рабочей нагрузки. И Kubernetes монтирует в нее секрет.
Обычно это происходит автоматически. Но как-то раз Кристоф рассказал мне, что секреты можно создавать вручную, добавляя к ним аннотации и указывая имя сервисного аккаунта. В этом случае Kubernetes автоматически указывает в секрете для этого аккаунта токен сервисного аккаунта.
Но при таком подходе я заметил нечто странное. Если при создании секрета указывать имя сервисного аккаунта, которое не существует в пространстве имен, kubectl сообщает, что секрет создан, а на самом деле его нет! Возьмем в качестве примера такой секрет:
apiVersion: v1
kind: Secret
metadata:
name: build-robot-secret
annotations:
kubernetes.io/service-account.name: build-robot
type: kubernetes.io/service-account-token
Сохраним его в файл с именем build-robot-secret.yaml и выполним kubectl create -f build-robot-secret.yaml; получаем:
kubectl create -f base-secret.yaml
secret/build-robot-secret created
Но если потом выполнить команду kubectl get secrets, секрета нигде не видно!
kubectl get secrets
NAME TYPE DATA AGE
default-token-nxrbt kubernetes.io/service-account-token 3 40m
В чем же дело
Итак, мы столкнулись с загадкой: почему наш секрет, который представляет собой допустимый YAML-файл, создается, но тут же исчезает после создания? Сначала я подумал, что созданный секрет просто не отображается. Чтобы это проверить, можно посмотреть содержимое базы данных etcd-кластера. Для кластера это «классическое» хранилище информации, так что логично поискать именно там.
Чтобы проверка проходила легче, в настройках кластера я выключаю аутентификацию etcd, используя набор методов etcd-noauth и kube-security-lab. С этой настройкой можно повторно выполнить цикл создания секрета и с помощью etcdctl посмотреть, что отображается или не отображается в etcd. Структура у базы данных достаточно простая, так что мы просто попросим ее показать содержимое /registry/secrets/default, то есть все секреты в пространстве имен по умолчанию.
etcdctl --insecure-transport=false --insecure-skip-tls-verify --endpoints=172.18.0.3:2379 get /registry/secrets/default --prefix --keys-only
/registry/secrets/default/default-token-nxrbt
Видим только токен нашего сервисного аккаунта по умолчанию. Теперь мы знаем, что секрет не сохранился.
А что случится, если мы изменим существующий секрет, который указывает на корректный сервисный аккаунт, так, чтобы он указывал на недопустимый аккаунт?
Создадим корректный аккаунт:
apiVersion: v1
kind: Secret
metadata:
name: extra-default-secret
annotations:
kubernetes.io/service-account.name: default
type: kubernetes.io/service-account-token
И если после этого выполним команду kubectl get secrets, то увидим, что он там:
❯ kubectl get secrets
NAME TYPE DATA AGE
default-token-nxrbt kubernetes.io/service-account-token 3 5h20m
extra-default-secret kubernetes.io/service-account-token 3 4s
Так. Теперь посмотрим, что случится, если мы выполним команду kubectl edit secret extra-default-secret и изменим поле service-account.name на test? Оказывается, после редактирования секрет исчезает! Если заглянуть в etcd, как мы это сделали выше, становится ясно — наш секрет исчез и из datastore.
Почему это произошло
Я думаю, во всем виноват один из контроллеров, которые в Kubernetes используются для управления состоянием кластера.
Множество контроллеров работают в связке «контроллер-менеджер». Их задача — выполнять циклы наблюдения за состоянием определенного класса объектов, которые проверяют желаемое состояние. Потом контроллеры меняют конфигурацию кластера, чтобы это желаемое состояние сохранялось.
Вот что можно обнаружить, просматривая документацию по теме «Сервисные аккаунты в Kubernetes»:
TokenController входит в состав kube-controller-manager. Он работает в асинхронном режиме. Он:
Следит за созданием ServiceAccount и создает соответствующий Secret токена ServiceAccount для доступа к API.
Следит за удалением ServiceAccount и удаляет все соответствующие Secrets токенов ServiceAccount.
Следит за добавлением Secret токена ServiceAccount, проверяет наличие исходного ServiceAccount и при необходимости добавляет токен к Secret.
Следит за удалением Secret и при необходимости удаляет ссылку из соответствующего ServiceAccount.
В документации не упоминается, что он удаляет секреты, не соответствующие существующему сервисному аккаунту. Но, судя по всему, это делает именно он. Как это проверить? Здесь нам должен прийти на помощь аудит в Kubernetes. Поскольку в Kubernetes все контроллеры отправляют запросы через API-сервер, мы можем посмотреть, что происходит, когда пытаемся создать наш build-robot-secret.
Команда kubectl create, раньше выполнявшаяся на этом кластере, подтверждает нашу гипотезу. Происходят три события.
Событие create по инициативе пользователя kubernetes-admin — это имя первого пользователя по умолчанию в кластере kubeadm.
Событие get по инициативе tokens-controller. Что интересно, здесь в качестве «пользователя» выступает system:kube-controller-manager, но агент пользователя указывает, что это tokens-controller.
Событие delete по инициативе tokens-controller, которое выглядит так:
{
«kind": "Event",
«apiVersion": «audit.k8s.io/v1",
"level": «RequestResponse",
"auditID": "ef0f48eb-a606-4f50-afcd-517268a5cd2e",
"stage": «ResponseComplete",
"requestURI": "/api/v1/namespaces/default/secrets/build-robot-secret",
"verb": "delete",
"user": {
"username": «system:kube-controller-manager",
«groups": [
"system:authenticated"
]
},
«sourceIPs": [
"172.18.0.3"
],
"userAgent": "kube-controller-manager/v1.21.1 (linux/amd64) kubernetes/5e58841/tokens-controller",
«objectRef": {
"resource": "secrets",
"namespace": «default",
"name": "build-robot-secret",
"apiVersion": "v1"
},
"responseStatus": {
"metadata": {},
"status": "Success",
"code": 200
},
"requestObject": {
"kind": "DeleteOptions",
"apiVersion": "v1",
"preconditions": {
"uid": "16c9f476-34fe-48a0-b623-100f7de93d0f"
}
},
Заключение
Как я уже упоминал в начале статьи, сенсации не произошло. И все-таки интересно углубиться в работу контроллеров Kubernetes. Чтобы поддерживать кластер в желаемом состоянии, они постоянно наблюдают за ресурсами и корректируют их.
Есть, конечно, редко встречающийся сценарий, когда секреты разрешено редактировать, но запрещено удалять. В остальных случаях описанная ситуация практически ни на что не влияет. И все-таки, может быть, это поможет кому-то разобраться, почему без проблем созданный секрет не сохраняется в кластере!
Куда исчезают секреты Kubernetes
Секреты используют в кластерах Kubernetes в разных целях. Одна из основных — разместить учетные данных сервисных аккаунтов, чтобы аутентифицировать рабочие нагрузки в кластерах API-сервером. Команда...
habr.com