Как стать автором
Обновить
697.22
Конференции Олега Бунина (Онтико)
Профессиональные конференции для IT-разработчиков

Как мигрировать с OpenShift на любой дистрибутив Kubernetes без единой правки

Уровень сложностиСложный
Время на прочтение12 мин
Количество просмотров577

После ухода известных вендоров у многих возникла задача импортозамещения и миграции между платформами контейнеризации. В этой статье разберём опыт решения этой задачи и как свести к минимуму зависимости приложений от конкретной версии и/или реализации Kubernetes. Рассмотрим проблемы, обсудим возможные пути и конкретные технологии для их решения и, главное, посмотрим, как всё это работает.

Статья написана по мотивам выступления Максима Чудновского, лидера по продукту Platform V Synapse Service Mesh СберТех на Saint Highload++, где он рассматривал кейс миграции с OpenShift на Platform V DropApp, но предложенные подходы могут быть использованы и для миграции на другие российские Kubernetes-платформы: Deckhouse, Штурвал, Боцман и так далее.

Помимо вариантов использования механизмов ETL, трансляции шаблонов в релизных конвейерах, рассматривается подход применения менеджера политик Kubelatte для того, чтобы мигрировать с OpenShift без единой правки кода

Что мы мигрируем

OpenShift — это комплексное программное решение для контейнеризации, созданное компанией Red Hat. Kubernetes — это ядро распределённых систем, в то время как OpenShift — это дистрибутив, содержащий дополнительный слой контроллеров, операторов для реализации платформенной функциональности. 

Ключевые сложности

→ Специализированные API

OpenShift обладает специализированным набором API:

  • Deployment Configs.

  • Routes.

  • Etc.

Для scheduling приложений могут использоваться Deployment Configs, для управления Ingress трафиком — Routes, для управления конфигурациями узлов — Machine Configs. Всего этого нет в ванильном Kubernetes, что подразумевает продумывание путей обхода.

→ Разные версии компонентов

Компания Red Hat не оказывает поддержку с 2022 года, что означает, что все это время обновления не доступны. Ванильный Kubernetes достаточно далеко ушёл вперёд, поэтому версии компонентов при миграции не будут совпадать. 

→ Разный набор платформенных функций

Платформа будет похожа, но всё равно разная, потому что где-то есть Service Mesh, где-то его нет; где-то будут демоны, которые обслуживают воркеры, где-то их не будет, и с этим нужно работать. В нашем случае история осложняется масштабом:

  • 4000+ сервисов;

  • 500+ команд;

  • 100+ кластеров;

OpenShift достаточно популярен, им пользуются сотни, если не тысячи команд — это тысячи сервисов, десятки тысяч yaml-файлов, сотни кластеров. И всё нужно мигрировать неинвазивным способом, чтобы никто не заметил подмены.

→ Целевые кластеры не всегда являются точной копией исходных

Важный момент — целевые кластеры не такие, как исходные. Например, у нас есть большой исходный кластер, в нём 300 машин, а целевых кластеров будет всего 10. Соответственно, нужно перенести приложение таким образом, чтобы они разместились уже в 10 кластерах.

Тривиальное решение

Есть мнение, что Kubernetes — самая лучшая база данных для yaml-файлов. По большому счёту мы решаем задачу, как перенести данные из одной базы данных в другую. Так делают давно. Подход для этого называется ETL (Extract, Transform, Load).

Архитектура решения

У нас есть релизный конвейер, APP, какая-то машинерия CI/CD, которая доставляет приложение до целевого кластера. При этом целевым кластером является OpenShift.

Мы можем снять с этого кластера бэкап и добавить к нему некоторые правила трансформации, а потом с чистой совестью загрузить это в новую базу данных, то есть задеплоить в целевой Kubernetes. Это максимально простое решение.

Детали реализации

→ Extract

Чтобы достать данные из Kubernetes, можно воспользоваться консольной утилитой kubectl.

Kubectl api-resources

  --verbs=list

  --namespaced

  -o name

| xargs  -n 1 kubectl get

  --show-kind

  --ignore-not-found

  -n <namespace>

Мы просто посмотрим API-ресурсы, которые доступны в кластере, потом по каждому API-ресурсу достанем данные и сохраним их на диск. В итоге получим большую кучу yaml-файлов, которую потом будем обрабатывать.

→ Transform

Обрабатывать очень просто. В нашем случае мы сделали CLI Tool с простой логикой. Один из примеров:

kind: Route

apiVersion: route.openshift.io/v1

metadata:

  name: grpc

spec:

  host: grpc-federation-discovery.local

  to:

    kind: Service

    name: ingressgateway-namespace

    weight: 100

  port:

    targetPort: https-5443

  tls:

    termination: passthrough

  wildcardPolicy: None

Есть kind: Route, который используется в OpenShift, чтобы управлять входящим трафиком. В Kubernetes есть аналог, он называется Ingress.

kind: Ingress
apiVersion: networking.k8s.io/v1
metadata:
  name: grpc
spec:
  ingressClassName: nginx
  rules:
    - host: grpc.cluster.ru
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: ingressgateway-namespace
                port:
                  name: https-5443

Задача сводится к тому, чтобы преобразовать Route в Ingress, переименовав некоторые поля, так как данных достаточно. По аналогии можно преобразовать DeploymentConfig в Deployment.

apiVersion: apps.openshift.io/v1
kind: DeploymentConfig
metadata:
  name: frontend
spec:
  replicas: 5
  selector:
    name: frontend
  template: { ... }
  strategy:
    type: Rolling
    rollingParams:
      maxUnavailable: 1
      maxSurge: 1

Интересный момент в том, что Red Hat много контрибьютят в Open Source, имеют влияние на развитие как Kubernetes, так и всего Cloud Native в комьюнити. Например, Deployment Config, который появился в OpenShift, стал прообразом Deployment в Kubernetes.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: frontend
spec:
  replicas: 1
  selector:
    matchLabels:
      name: frontend
  template: { ... }
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxSurge: 1
      maxUnavailable: 1

Поэтому здесь вообще особо думать не надо. Они практически одинаковы.

Но есть и более сложный сценарий — пример EnvoyFilter.

Service Mesh в целевом кластере другой, поэтому эту простыню yaml нужно распарсить и потом поменять пару строк, чтобы просто обновить версию и загрузить на целевой кластер.

Это вся трансформация, которая нужна. Но есть момент: скоуп определяется только итеративно и никогда не фиксируется полностью. Это важно, потому что мы не можем сказать, что мы решили задачу миграции.

→ Load

С точки зрения загрузки тоже всё тривиально. Все знают команду:

kubectl apply
     -f <path>
     -n <namespace>

Новые файлы мы загружаем в Kubernetes, а если кластеров несколько, то просто устанавливаем правильный контекст. 

Вывод

→ Плюсы:

  • Простота реализации

Никакой сложной логики, всё тривиально.

  • Не требуется участие команд разработки или авторов приложений. Сами перенесли приложение из старого кластера в новый. Это удобно.

→ Минусы:

  • Разрыв релизного конвейера

Теперь команды не работают с реальными промо-окружениями. Вместо этого они используют вымышленный кластер для рендеринга конфигураций, которые затем загружаются куда нужно. Соответственно, выкатить обновление и повторить весь путь становится дорого. 

  • Для prerender необходим отдельный кластер

Вдобавок нам всё равно нужен OpenShift. По факту только его управляющий слой, чтобы рендерить ресурсы. Кажется, что задачу тривиальным способом мы не решили, так как полностью от OpenShift не избавились.

Оптимистичное решение

При использовании Kubernetes никто не работает с сырыми ресурсами и плоскими yaml-файлами. Все используют шаблонизаторы, вроде Helm, либо Kustomize. Те, кто использует OpenShift, применяет встроенный механизм шаблонизации. Таким образом, если шаблонов не так много, и мы можем прогнать их и со всеми поработать, то можем предложить новое решение, которое можно назвать «трансляцией шаблонов».

Архитектура решения

Переносим всю магию про-трансформации в релизные конвейеры и модифицируем все CI/CD так, чтобы наше решение работало с шаблонами. Эти шаблоны нужно уметь модифицировать, чтобы их разворачивать в правильные yaml файлы, которые задеплоятся на целевой кластер Kubernetes.

Детали реализации

→ Extract

С точки зрения подготовки данных мы можем использовать prerender, доступный в шаблонизаторах.

В случае HELM это команда helm template, которая генерирует из шаблонов плоские yaml-файлы:

helm template --output-dir <path> <chart>

В случае, если это встроенный механизм OpenShift, есть команда oc process, которая делает примерно то же самое.

oc process
     -f <template>
     --param-file=<env>

→ Тривиальный transform

Здесь повторяется всё, что делали в тривиальном решении.

→ Платформенный transform

Это тоже консольная утилита, но она обладает нетривиальной бизнес-логикой. В дистрибутиве Kubernetes, который называется OpenShift, есть очень много дополнительной платформенной функциональности. Например, Webhooks, которые что-то меняют в runtime, или, например, набор специализированных операторов. Чтобы эту логику повторить, нужно сделать глубокий реинжиниринг платформы и повторить все трансформации, но в дизайн-тайме, в релизном конвейере для того, чтобы задеплоить информацию в целевые кластеры.

Примером такой функциональности могут быть Webhooks, которые добавляют security-контексты на поды.

securityContext:
  seccompProfile:
   type: RuntimeDefault
  privileged: false
  runAsNonRoot: true
  runAsUser: 10001
  runAsGroup: 10001
  readOnlyRootFilesystem: true
  allowPrivilegeEscalation: false
  capabilities:
  drop:
    - ALL

Еще OpenShift очень любит добавлять runAsUser, runAsGroup. Делает он это из пула, который ассоциирован с namespace. Это полезная функциональность, её нужно повторить. 

Логика работы операторов еще более сложная.

apiVersion: maistra.io/v2
kind: ServiceMeshControlPlane
metadata:
  name: demo
spec:
  profiles:
  - demo

Эти семь строчек деплоят Service Mesh. Они разворачиваются в десяток yaml-файлов (Deployment, ConfigMap и прочее), чтобы работала платформенная функциональность.

Если мы это повторяем, то скоуп будет фиксированный, потому что мы провели реинжиниринг платформы, повторили всю её логику, и теперь нам больше нечего делать. Это сложно, но есть важный нюанс. Чаще всего платформенной функциональностью не пользуются команды разработки. Это больше инфраструктурная часть, поэтому объём использования сильно ниже.

→ Load

С точки зрения загрузки на кластер история точно такая же. Мы опять-таки получаем готовые yaml манифесты, которые можем загрузить на кластер. Получается команда:

kubectl apply
     -f <path>
     -n <namespace>

Вывод

→ Плюсы:

Сразу с CI/CD-системы мы деплоимся в целевой кластер. И OpenShift больше не нужен, то есть мы полностью завершили миграцию.

→ Минусы:

  • Высокая вариативность шаблонов: мы не можем работать с шаблонами, нам нужны prerender’ы.

  • Высокая сложность реализации: глубокий реинжиниринг OpenShift является сложной задачей , а если делать её со 100% точностью, то ещё и очень долгой. 

  • Требуется участие команд для модификации CI/CD.

Самый большой минус в том, что нам нужно модифицировать релизный конвейер команд. Понятно, что команда может сделать это сама, но когда счёт команд идёт на сотни тысяч, это колоссальное количество трудочасов, которые мы не хотели бы потратить.

Неожиданное решение

Мы уже знаем, что OpenShift изменяет ресурсы в runtime через Webhooks и операторы, чтобы реализовать свою платформенную функциональность. Но никто не запрещает повторить этот механизм, и точно так же в runtime все старые yaml-манифесты модифицировать таким образом, чтобы они стали пригодны для целевого кластера.

Kubelatte

Название происходит из слогана: Kubelatte — это Kubernetes as a Cup of Latte. Его основная задача — автоматизировать сложный сценарий использования кластера Kubernetes, чтобы инженер, который работает с кластером, делал это простым, понятным образом, а в свободное время наслаждался чашечкой любимого напитка.

По сути Kubelatte представляет собой Policy Engine для Kubernetes.

Что такое Policy Engine

→ Mutation

Когда вы деплоите что-то в Kubernetes и делаете команду kubectl apply, то прежде, чем ваш ресурс сохранится в хранилище кластера в базе данных etcd, можно вмешаться в процесс его обработки и как-то его либо провалидировать, либо замутировать. Это называется Webhooks, а сам механизм — admission controllers.

Мы можем вмешаться и, например, изменять приходящие ресурсы для того, чтобы всё работало из коробки. Policy Engine это и делает.

Это такой generic Webhook-сервер, который принимает admission-request от Kube API Server, и в этом запросе предлагает внести изменения перед тем, как исходный ресурс будет обработан соответствующим контроллером. Mutation менеджер изменяет в зависимости от загруженной политики, также он добавляет security контекст, какие-то полномочия, что-то убирает и отдаёт целевой ресурс.

Чтобы всё настраивалось динамически, прямо в Webhook сервера встраивается оператор, работающий со своим набором CRD. В нашем случае это Templates и триггеры. Таким образом реализуется generic сценарий мутации.

→ Validation

Если мы хотим не мутировать ресурс, а просто проверить, используется подход, называемый валидацией.

Kube API Server спрашивает: «Дорогой Webhook, а можно ли сохранить такой ресурс и взять его в обработку?». И дальше в зависимости от политики, которую вы загрузили на Engine, решение будет принято. Для настройки мы используем ресурс, который мы называем Scope. 

→ Creation

Третий момент — creation или generation — это когда нужно по шаблону сгенерировать сразу некоторое количество ресурсов Kubernetes. 

Например, есть типовой сценарий использования, он требует пять yaml-файлов, вроде ConfigMap, Deployment и других. Мы можем заменить его одним файлом или вообще аннотацией, и упростить жизнь разработчикам. Для этого у нас тоже есть специальные CRD.

Архитектура решения

Удаляем со схемы лишнее и получаем целевое решение, которое должно полностью удовлетворить наши требования и стать эффективным подходом для решения подобного рода проблем.

Детали реализации

→ Политики мутации:

  • Шаблоны;

  • Триггеры — события для наступления мутации.

→ Политики генерации:

  • Шаблоны;

  • Triggers;

  • TriggerInstances.

Kubelatte Templates

→ Шаблоны всех целевых ресурсов

apiVersion: kubelatte.synapse.sber/v1alpha1
kind: Template
metadata:
  name: any-deployment
spec:
  apiVersion: networking.k8s.io/v1
  kind: Ingress  
  data: |-
      spec:
        template:
          spec:
            securityContext:
              runAsNonRoot: true

Это может быть либо полностью yaml-манифест, либо его часть, которая будет использована для мутации или генерации.

→ Сложные сценарии реализуются через GO-шаблонизацию

Можно сделать сложные сценарии через поддержку GO-шаблонизации.

data: |-
    spec:
      template:
        spec:
          containers:
           {{`{{% range .spec.template.spec.containers %}}`}}
            - name: {{`{{% .name %}}`}}
              imagePullPolicy: Always
           {{`{{% end %}}`}}

Мы можем рендерить шаблоны (Templates) любой сложности на основании входящих параметров. Дальше эти шаблоны нужно как-то применить в правильной точке, которую определяют триггеры.

Kubelatte триггеры

→ Определение точки мутации 

Триггеры определяют, когда и на каких ресурсах нужно запустить мутацию. 

apiVersion: kubelatte.synapse.sber/v1alpha1
kind: Trigger
metadata:
  name: mutation-any-deployment
spec:
  mutationConfigs:
    - match:
        kinds:
          - apiGroups:
              - '*’
            kinds:
              - Deployment
              - StatefulSet
      name: all-deployment

В данном случае я хочу, чтобы были замутированы все Deployments и StatefulSets, которые приходят в кластер.

→ Связь шаблонов с исходными ресурсами

Дополнительно к этому триггер обеспечивает связь шаблона с точкой мутации.

apiVersion: kubelatte.synapse.sber/v1alpha1
kind: Trigger
metadata:
  name: mutation-any-deployment
spec:
  mutationConfigs:
    - match:
        kinds:
          - apiGroups:
              - '*’
            kinds:
              - Deployment
              - StatefulSet
      name: all-deployment
      templateRefs:
        - ns/any-deployment

Мы хотим применить именно такой шаблон или несколько шаблонов из этого или другого namespace. И здесь же хотим задать стратегию применения этого шаблона.

→ Стратегии применения шаблонов — replace, merge, sideEffect

apiVersion: kubelatte.synapse.sber/v1alpha1
kind: Trigger
metadata:
  name: mutation-any-deployment
spec:
  mutationConfigs:
    - match:
        kinds:
          - apiGroups:
              - '*’
            kinds:
              - Deployment
              - StatefulSet
      name: all-deployment
      templateRefs:
        - ns/any-deployment
      updateStrategy: merge

Мы хотим заменить исходный ресурс на наш шаблон, или объединить его с нашим шаблоном, чтобы sideEffect’ом добавить свои ресурсы.

Kubelatte TriggerInstances

Файл состоит из шести строчек, содержательной из которых является лишь имя.

apiVersion: kubelatte.synapse.sber/v1alpha1
kind: TriggerInstance
metadata:
annotations:
  kblt/creation-ingress: enabled
spec: {}

→ Аналог Trigger для ситуации, когда должен быть создан новый ресурс

Это триггер для ситуации, когда у нас нет исходного ресурса. То есть мы ничего не мутируем, нам не к чему привязаться, нет исходного Deployment и прочее. Но мы хотим сгенерировать бандл конфигурации и в дальнейшем его использовать.

→ Ресурс-якорь для работы встроенной уборки мусора

TriggerInstances выступают своеобразными якорями для работы встроенной в Kubernetes уборки мусорок. Есть механизм, он называется Owner Reference. У вас есть базовый ресурс, вы его используете в качестве владельца (owner), все дочерние, которые вы создали через Policy Engine, привязываются к нему. Поэтому если удалить основной ресурс, всё остальное тоже удалится.

Вывод

→ Плюсы:

  • Нет разрыва релизного конвейера.

  • Не требуется дополнительных кластеров.

  • Не требуется участие команд и модификация CI/CD.

  • Доступно как централизованный сервис.

→ Минусы:

Требуется Policy Engine, как Kubelatte. А его нужно настраивать, а лучше ещё разработать. Это может стать проблемой для применения такого подхода.

Kuberlatte в Open Source

Чтобы вам было проще, мы выложили Kubelatte в Open Source. Он доступен на площадке Gitverse.

Вы можете повторить сценарий миграции или придумать собственные сценарии. 

Обратите внимание на дополнительные возможности Kuberlatte:

→ Generic Sidecar Injector:

Во-первых, мы реализуем шаблон Generic Sidecar Injector. Очень часто по этому шаблону реализуется платформенная функциональность. Мы хотим добавить контейнер для журналирования, контейнер для Service Mesh, работу с секретами, например с HashiCorp Vault и прочее. Эти шаблоны можно централизованно вести и управлять на Policy Engine, чем сильно снизить затраты на сопровождение. В эту же историю идёт автоматизация типовых сценариев использования.

→ Service Mesh «usage» Injector:

Представим, что у нас 100 команд, для которых есть требование — трафик от приложения должен покидать кластер через специальный компонент, Egress Gateway (выходной шлюз). Для этого нужно сделать 10 конфигураций на Istio. Если есть Policy Engine, можно повесить одну аннотацию и все эти конфигурации сгенерируются автоматически. Это нравится всем: разработчику, потому что нужна аннотация; DevOps или AppSec, потому что не нужно проверять каждое приложение, а только политику. Помимо таких сценариев, можно внедрять Gateway и централизованно управлять раутингом.

→ Контроль целостности конфигураций

Могут быть разные сценарии:

  • Scheduling. Например, мы хотим настроить scheduling наших кодов. Разработчик об этом думать не должен. Этим может заняться владелец кластера, так как у него может быть гетерогенный кластер с разными узлами. Управлять этим процессом можно через политику. 

  • Network Policies. Например, вы создали namespace и сразу закрыли сетевой доступ по дефолту и к нему, и из него, чтобы было безопаснее. Потом открываете. Всё это настраивается через политику.

  • Automatic Resource Allocation. Создали namespace, и к нему сразу же создаётся ресурс квота, чтобы не превысить установленные границы.

Таких сценариев очень много. Они ограничиваются только нашим опытом и фантазией. Поэтому обязательно заходите на Gitverse, заходите в репозиторий Kubelatte, создавайте issues, делитесь идеями и предлагайте что-то новое. Всё это будет добавлено, потому что технологии без людей не живут.

Итоги

→ Миграция — сложный процесс и стоит осознанно подходить к задаче

К любой задаче нужно подходить так, как будто она сложная. Мы из примера увидели, что тривиальные и оптимистичные решения не подошли, так как вскрылись подводные камни. Поэтому нужно осознанно подходить к задаче и выбирать правильное средство для решения.

→ Policy Engine хорошо подходит для задач миграции и доступен в Open Source.

→ Policy Engine решает множество дополнительных задач в кластерах Kubernetes.

Теги:
Хабы:
+20
Комментарии3

Публикации

Информация

Сайт
www.ontico.ru
Дата регистрации
Дата основания
Численность
51–100 человек
Местоположение
Россия