Server-Side Apply в Kubernetes: декларативное управление полями и разграничение владения
По мере усложнения инфраструктуры Kubernetes декларативное управление объектами кластера перестаёт быть индивидуальной операцией одного инженера. В современном продакшене над одним и тем же объектом — будь то Deployment, Ingress или Custom Resource — одновременно работают сразу несколько систем: GitOps-операторы (ArgoCD, Flux), контроллеры автомасштабирования (HPA, VPA), сервис-меши (Istio, Linkerd) и скрипты CI/CD-пайплайнов. В этой мультиагентной среде классический подход к обновлению ресурсов начинает давать сбои, приводя к непреднамеренной перезаписи настроек и конфликтам состояния.
Решением проблемы стал масштабный перенос логики применения манифестов из клиентских утилит непосредственно в API-сервер Kubernetes. Технология Server-Side Apply (SSA), ставшая общедоступной (General Availability) в Kubernetes 1.22, принципиально меняет модель декларативного патчинга ресурсов.
Ограничения Client-Side Apply и проблема конфликтов
Исторически команда kubectl apply работала по схеме Client-Side Apply (CSA). Клиентская утилита вычисляла три патча на стороне компьютера разработчика или CI-сервера, сравнивая:
- Текущий отправляемый манифест;
- Последнее сохранённое состояние из специальной аннотации
kubectl.kubernetes.io/last-applied-configuration; - Живое состояние объекта (Live state), полученное от API-сервера.
Эта схема содержала ряд фундаментальных архитектурных недостатков:
- Ограничение размера аннотаций: Аннотация
last-applied-configurationсохраняла весь JSON-текст предыдущего манифеста. Для крупных объектов (CRD, большие ConfigMap) размер аннотации быстро превышал лимит аннотаций etcd в 262 Килобайта, что блокировало любые дальнейшие обновления. - Слепота к внешним контроллерам: Утилита на клиенте не понимала, какие именно поля объекта были изменены сторонними операторами. Если HPA обновлял число реплик
spec.replicas, а инспектор безопасности добавлял лейблы, последующийkubectl applyиз Git-репозитория мог молча сбросить эти изменения до значений из файла. - Отсутствие аудита ответственности: При возникновении сбоя невозможно было установить, какая именно система или команда перезаписала критическую конфигурацию ресурса.
Архитектура Server-Side Apply и структура managedFields
Server-Side Apply переносит всю математику слияния (Structured Merge and Diff) в API-сервер Kubernetes. При отправке запроса с заголовком Content-Type: application/apply-patch+yaml API-сервер принимает манифест как декларативное намерение и фиксирует право владения каждым конкретным полем.
В метаданных любого объекта Kubernetes появляется системная секция metadata.managedFields. Каждая запись в этой структуре описывает конкретного субъекта управления (Field Manager) и список полей, которые находятся под его контролем:
metadata:
managedFields:
- manager: argocd-application-controller
operation: Apply
apiVersion: apps/v1
fieldsType: FieldsV1
fieldsV1:
f:spec:
f:template:
f:spec:
f:containers:
k:{"name":"web"}:
f:image: {}
- manager: kube-controller-manager
operation: Update
fieldsType: FieldsV1
fieldsV1:
f:spec:
f:replicas: {}
В приведенном примере ArgoCD владеет полем образа контейнера (f:image), в то время как контроллер автомасштабирования kube-controller-manager управляет количеством реплик (f:replicas). Если ArgoCD отправляет обновленный манифест, не содержащий явного указания на replicas, Server-Side Apply не станет удалять или сбрасывать текущее количество реплик, поскольку эти поля официально принадлежат другому менеджеру.
Алгоритм разрешения конфликтов и механизм Force Ownership
Когда два разных менеджера пытаются одновременно управлять одним и тем же атомарным полем (например, оба пытаются задать разное значение spec.replicas), API-сервер блокирует операцию. Вместо молчаливого перезаписывания данных API-сервер возвращает клиенту ошибку со статусом HTTP 409 Conflict.
Структура ответа содержит детализированный JSON-объект, описывающий причину отклонения патча:
{
"kind": "Status",
"apiVersion": "v1",
"status": "Failure",
"message": "Apply failed with 1 conflict: conflict with "kube-controller-manager": .spec.replicas",
"reason": "Conflict",
"details": {
"causes": [
{
"reason": "Conflict",
"message": "conflict with "kube-controller-manager": .spec.replicas",
"field": ".spec.replicas"
}
]
},
"code": 409
}
У разработчика или контроллера есть два пути решения возникшего конфликта:
- Декларативное уступление (Adoption): Скорректировать свой манифест в Git-репозитории, удалив из него спорное поле и доверив управление им первичному менеджеру (например, передав
replicasпод управление HPA). - Принудительный перехват владения (Force Ownership): Отправить повторный запрос с флагом
force=true(kubectl apply --server-side --force-conflicts). В этом случае API-сервер переназначает право владения полем новому менеджеру, о чём обновляется запись вmanagedFields.
Практический чек-лист миграции на SSA
Переход на Server-Side Apply в существующих кластерах требует выполнения последовательных шагов:
- Аудит версии API-сервера: Убедитесь, что все кластеры обновлены до Kubernetes 1.22+ (желательно 1.26+ для использования улучшенной валидации OpenAPI v3).
- Явное задание Field Manager в CI/CD: Замените ключи вызова в пайплайнах с
kubectl apply -fнаkubectl apply --server-side --field-manager=gitops-pipeline -f manifest.yaml. Явное указание имени--field-managerобеспечивает точный аудит изменения конфигураций. - Очистка устаревших аннотаций: После успешного перехода на SSA можно удалить устаревшие клиентские аннотации
kubectl.kubernetes.io/last-applied-configuration, высвободив дисковое пространство в etcd. - Проверка OpenAPI-схем в CRD: Для пользовательских ресурсов (Custom Resource Definitions) убедитесь, что схемы содержат аннотации
x-kubernetes-preserve-unknown-fieldsили точные типы полей, чтобы алгоритм слияния API-сервера корректно обрабатывал вложенные карты и списки.
Server-Side Apply делает управление ресурсами кластера прозрачным, исключает случайные затирания конфигураций и закладывает надежную основу для мультиагентных GitOps-архитектур.

