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

После ухода известных вендоров у многих возникла задача импортозамещения и миграции между платформами контейнеризации. В этой статье разберём опыт решения этой задачи и как свести к минимуму зависимости приложений от конкретной версии и/или реализации 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.

c2cf8d5fd7c24b3b68e01385ac4587d9.jpg

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

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

→ Extract

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

Kubectl api-resources

  --verbs=list

  --namespaced

  -o name

| xargs  -n 1 kubectl get

  --show-kind

  --ignore-not-found

  -n 

Мы просто посмотрим 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.

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

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

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

8bba0530ef3324886036f9f751109c10.jpg

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

→ Load

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

kubectl apply
     -f 
     -n 

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

Вывод

→ Плюсы:

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

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

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

→ Минусы:

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

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

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

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

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

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

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

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

8b92b79372534665b2f42f4fb289a767.jpg

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

→ Extract

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

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

helm template --output-dir

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

oc process
     -f