API-сервер Kubernetes хранит объекты, опираясь на совместимое с etcd хранилище (часто это и есть сам etcd). Каждый объект сериализуется с использованием определённой версии своего API-типа — например, представление ConfigMap версии v1. То, в каком виде объект хранится в кластере, Kubernetes описывает термином версия хранения.
Кроме того, API Kubernetes опирается на автоматическое преобразование. Например, если у вас есть HorizontalPodAutoscaler, то работать с ним можно, произвольно смешивая версии v1 и v2 соответствующего API. Kubernetes сам преобразует каждый вызов API, поэтому клиенты не видят, какая версия сериализована на самом деле.
Администраторам кластера важно понимать, что такое версия хранения объекта: именно она связывает представление объекта в API с тем, как он в действительности закодирован в хранилище. Это существенно в тех случаях, когда двоичное кодирование объекта имеет значение, — например, при шифровании данных при хранении или при выводе версий API из обращения.
У одного и того же API может быть несколько версий хранения, которые API-сервер затем приводит к схеме объекта. При этом у отдельного объекта в составе ресурса в каждый момент времени может быть только одна версия хранения. Это значит, что API-серверу известны двоичные представления объектов и он способен на лету преобразовать любую из сохранённых версий в представление объекта в API.
Версия самого объекта и версия хранения — совершенно разные вещи. Например, объекты API
версий v1alpha1 и v1beta1 для одного и того же ресурса будут закодированы в хранилище
одинаково, если между их сохранением версия хранения не менялась.
У каждого ресурса в каждый момент времени активна ровно одна версия хранения: любая запись объекта сохранит его именно в этой версии. Но версию хранения можно изменить, и тогда объекты окажутся сохранены в разных версиях. При этом отдельный объект в любой момент времени хранится только в одной версии.
При чтении API-сервер преобразует сохранённые данные в представление объекта в API. Благодаря этому старые версии хранения могут лежать сколь угодно долго, пока объект не обновляют. А запись, наоборот, при обновлении преобразует сохранённый объект в новое представление.
Пользовательские ресурсы определяются динамически и потому отличаются по работе с версией хранения от встроенных типов Kubernetes. У встроенных объектов кодирование для хранения обычно задаётся отдельно от их API-типов: сохранённый объект выступает промежуточным представлением, и конкретная версия ресурса не имеет значения — это просто поле в схеме объекта.
Для пользовательских же ресурсов одна из версий ресурса обязана быть назначена версией хранения. Именно схема, заданная этой версией пользовательского ресурса, и будет использована для кодирования ресурса на уровне хранилища. Подробнее о настройке API и версионировании смотрите в разделе про расширенные возможности CRD.
Для примера рассмотрим такой CustomResourceDefinition для crontabs:
apiVersion: apiextensions.k8s.io/v1
kind: CustomResourceDefinition
metadata:
name: crontabs.example.com
spec:
group: example.com
# список версий, которые поддерживает этот CustomResourceDefinition
versions:
- name: v1beta1
# каждую версию можно включить или выключить флагом served
served: true
# ровно одна версия должна быть отмечена как версия хранения
storage: true
schema:
openAPIV3Schema:
type: object
properties:
host:
type: string
port:
type: string
- name: v1
served: true
storage: false
schema:
openAPIV3Schema:
type: object
properties:
host:
type: string
port:
type: string
time:
type: string
conversion:
strategy: None
scope: Namespaced
names:
plural: crontabs
singular: crontab
kind: CronTab
shortNames:
- ct
Здесь версией хранения назначено определение API v1beta1: значит, любое создание или
обновление crontabs будет сохранено по схеме объекта из API v1beta1. На практике это
означает, что объект API версии v1 вообще никогда не сможет сохранить поле time,
поскольку его нет в определении версии хранения. Эта схема используется на уровне
хранилища как двоичное представление самого объекта. Попытка назначить версией хранения
сразу две версии считается недопустимой: это означало бы, что объекты одновременно
допустимо хранить по двум разным схемам данных.
После смены версии, используемой для хранения, все новые и обновляемые пользовательские ресурсы будут сохраняться уже в этой версии API. Отслеживание объекта или его получение по-прежнему будут работать: объект просто преобразуется из старой версии хранения, и это на него никак не влияет. Эффект дают только обновление и создание — они и используют новую версию хранения.
Существуют инструменты для шифрования данных при хранении в кластере, в первую очередь для секретов кластера. Это даёт дополнительный уровень защиты от утечки данных, поскольку в кластере хранятся уже зашифрованные данные. Соответственно, API-сервер расшифровывает данные при извлечении их из хранилища. Чтобы корректно декодировать объект, у API-сервера должен быть ключ для соответствующей версии хранения.
Версия хранения в этом случае — нечто большее, чем просто двоичное представление объекта. Версией хранения может выступать что угодно, из чего сохранённые данные так или иначе можно преобразовать в объект API.
Несколько версий хранения у одного ресурса могут создать администраторам кластера проблемы. Администратор не может удалить старые версии API для CRD, которые, возможно, уже не поддерживаются, пока не убедится, что ни один объект больше не использует связанную с ними версию хранения. При большом количестве объектов и без наглядной картины того, какие из них новые, а какие всё ещё лежат в старых версиях хранения, трудно понять, когда версию можно безопасно удалить. А если удалить версию преждевременно, объект может оказаться вообще нечитаемым.
Ещё один важный момент — использование ключей шифрования, о которых сказано выше. Поскольку версия хранения обновляется только при активном обращении к ресурсу, при ротации ключей и старый, и новый ключи шифрования должны оставаться в работе до тех пор, пока администратор не убедится, что каждый объект был записан хотя бы один раз. Это создаёт и риски для безопасности, и неудобства в работе, так как до этого момента ключ нельзя полностью вывести из обращения.
О том, как выполнить миграцию и без ручного вмешательства перевести все объекты на более новую версию хранения, читайте в разделе Storage Version Migration.