ADG Оставить заявку
Блог DevOps 4 мин чтения

Kubernetes 1.9: Workloads API в GA - проводим массовый рефакторинг YAML на apps/v1

Kubernetes 1.9 переводит Deployment, DaemonSet, StatefulSet и ReplicaSet в GA. Закрываем технический долг: меняем extensions/v1beta1 на apps/v1 во всех клиентских манифестах.

Контекст момента

Kubernetes 1.9 - Workloads API (Deployment, DaemonSet, StatefulSet, ReplicaSet) переведены в GA под apps/v1; Windows поддержка в beta; расширенный alpha CSI

Kubernetes 1.9 вышел в середине декабря. Главное событие - Workloads API наконец переведён в GA: Deployment, DaemonSet, StatefulSet и ReplicaSet теперь живут под apps/v1. Для повседневной эксплуатации это означает одно: последний повод держаться за extensions/v1beta1 в манифестах официально исчез, и мы решили разобраться с накопившимся техническим долгом сразу, не откладывая.

Что изменилось в apps/v1

Переход не просто переименование группы. В apps/v1 есть несколько ломающих отличий от extensions/v1beta1, которые надо учесть при рефакторинге.

Первое - selector стал обязательным и неизменяемым. В старом API selector мог отсутствовать - apiserver вычислял его из template.metadata.labels. В apps/v1 поле spec.selector обязательно и после создания объекта не меняется. Это правильно: неявный selector порождал ситуации когда Deployment подхватывал чужие поды при совпадении меток. Теперь это невозможно по контракту.

Второе - spec.replicas по умолчанию равен 1, а не 0. Звучит как мелочь, но если в манифесте не было явного replicas: 0, а вы рассчитывали на нулевой дефолт - получите сюрприз.

Третье - rolling update параметры переехали. В extensions/v1beta1 поле spec.rollbackTo существовало. В apps/v1 его нет - откат делается через kubectl rollout undo, что логичнее.

Для DaemonSet добавилось явное поле updateStrategy - раньше стратегия обновления в extensions/v1beta1 тоже работала, но менее предсказуемо.

Масштаб задачи

У нас несколько десятков managed-кластеров разной степени загруженности. Манифесты хранятся в git-репозиториях - у кого-то один монорепо на весь кластер, у кого-то разбиты по командам. YAML-файлов с extensions/v1beta1 набралось порядочно: Deployment почти везде, DaemonSet для агентов мониторинга и сбора логов, StatefulSet для баз данных.

Подход выбрали осторожный - не скриптом по всем репозиториям разом, а по одному кластеру, с проверкой. Причина простая: обязательный selector в apps/v1 означает что при пересоздании объекта через kubectl apply с изменённым selector кластер выдаст ошибку - нужно удалять и создавать заново. А это простой.

Поэтому план такой: обновляем apiVersion, добавляем явный selector, убираем устаревшие поля - затем делаем kubectl apply и смотрим что происходит. Для объектов где selector не совпадал с тем что генерировал apiserver раньше - сначала kubectl delete, потом apply.

Сам процесс рефакторинга

Типичный манифест Deployment до:

apiVersion: extensions/v1beta1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 3
  template:
    metadata:
      labels:
        app: api-server
    spec:
      containers:
        - name: api-server
          image: registry.example.com/api-server:1.4.2

После:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-server
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-server
  template:
    metadata:
      labels:
        app: api-server
    spec:
      containers:
        - name: api-server
          image: registry.example.com/api-server:1.4.2

Diff небольшой, но каждый файл нужно просмотреть руками: убедиться что метки в selector.matchLabels совпадают с template.metadata.labels, и что никаких неочевидных расхождений нет.

DaemonSet-манифесты оказались чуть сложнее - у некоторых в extensions/v1beta1 стратегия обновления была OnDelete, что давно считалось нормой для агентов node-exporter и filebeat. В apps/v1 явно прописали updateStrategy: type: RollingUpdate там где это безопасно, и оставили OnDelete где нода требует ручного дрейна перед обновлением агента.

Что с обратной совместимостью

extensions/v1beta1 в Kubernetes 1.9 никуда не делся - он deprecated, но работает. Apiserver принимает старые манифесты, конвертирует и хранит в новой версии внутри. Иллюзия совместимости полная - поэтому многие проекты годами живут на устаревших API без видимых проблем.

Но deprecated означает что API уберут в одном из следующих релизов без особых церемоний. Мы видели это с ThirdPartyResources в 1.8 - объявили deprecated в 1.7, убрали в 1.8, и несколько проектов в экосистеме получили срочный рефакторинг. Повторять этот опыт с Workloads API не хочется.

Где сейчас

Первые несколько кластеров прошли рефакторинг чисто. Сейчас разбираемся с репозиториями где манифесты генерируются из шаблонов - там правка в одном месте раскатывается на десяток объектов, что проще, но требует аккуратности при проверке selector-ов.

Параллельно обновили внутренние шаблоны и примеры для новых проектов - extensions/v1beta1 из них убрали совсем. Новые кластеры сразу стартуют с правильным API.

RBAC мы прошли похожим путём после 1.8 - тогда тоже был массовый прогон манифестов под rbac.authorization.k8s.io/v1. Workloads в каком-то смысле проще: меньше объектов требуют удаления и пересоздания, в основном достаточно добавить selector и поменять apiVersion. Но скучно не бывает.

Контакт

Нужна такая же инженерная работа?

Опишите задачу и контекст. Ответим в течение рабочего дня, при необходимости подпишем NDA.