OpenShift, Rancher и другие зарубежные Kubernetes-платформы официально больше не поддерживаются в России. Многим компаниям приходится искать альтернативные решения для управления контейнеризированными приложениями — например, «ванильный» Kubernetes или российские платформы.
Хотя у Kubernetes-платформ одинаковая технологическая база, перейти с одной на другую непросто: миграция неизбежно сопряжена с различными трудностями, связанными с особенностями реализации компонентов. В этой статье рассмотрен пример переезда приложения из OpenShift в «ванильный» кластер Kubernetes. В конце статьи приведена таблица соответствия примитивов OpenShift и Kubernetes — с информацией о том, какие из этих примитивов требуют замены, а какие нет.

Есть инструменты, которые автоматизируют процесс миграции — например, move2kube. Однако они требуют отдельного рассмотрения и, соответственно, отдельной статьи. Здесь же мы сосредоточимся именно на «ручном» переносе приложения.
Исходные данные
Рассмотрим template OpenShift с простым веб-сервисом:
apiVersion: template.openshift.io/v1 kind: Template labels: nginx: master metadata: annotations: description: example-template iconClass: icon-nginx tags: web,example name: web-app-example objects: - apiVersion: apps/v1 kind: Deployment metadata: name: ${NAME} spec: replicas: ${{REPLICAS}} revisionHistoryLimit: 3 selector: matchLabels: app: ${NAME} template: metadata: labels: app: ${NAME} spec: containers: - image: camunda/camunda-bpm-platform:run-7.15.0 imagePullPolicy: Always name: camunda ports: - containerPort: 8080 name: http protocol: TCP resources: limits: memory: ${BACK_MEMORY} requests: cpu: ${BACK_CPU} memory: ${BACK_MEMORY} - command: - /usr/sbin/nginx - -g - daemon off; image: nginx:stable-alpine imagePullPolicy: Always lifecycle: preStop: exec: command: - /bin/bash - -c - sleep 5; kill -QUIT 1 name: nginx ports: - containerPort: 9000 name: http protocol: TCP resources: limits: memory: ${FRONT_MEMORY} requests: cpu: ${FRONT_CPU} memory: ${FRONT_MEMORY} volumeMounts: - mountPath: /etc/nginx/nginx.conf name: configs subPath: nginx.conf volumes: - configMap: name: ${NAME}-config name: configs - apiVersion: v1 kind: Service metadata: annotations: description: Exposes and load balances the application pods name: ${NAME}-service spec: ports: - name: http port: 9000 targetPort: 9000 selector: app: ${NAME} - apiVersion: v1 kind: ConfigMap metadata: name: ${NAME}-config data: nginx.conf: | user nginx; worker_processes 1; pid /run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; upstream backend { server 127.0.0.1:8080 fail_timeout=0; } server { listen 9000; server_name _; root /www; client_max_body_size 100M; keepalive_timeout 10s; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://backend; } } } - apiVersion: route.openshift.io/v1 kind: Route metadata: name: ${NAME}-route spec: host: ${DOMAIN}.apps-crc.testing port: targetPort: http to: kind: Service name: ${NAME}-service weight: 100 wildcardPolicy: None parameters: - description: Name for application from: '[A-Z]{8}' generate: expression name: NAME - description: Domain for application from: '[A-Z]{8}' generate: expression name: DOMAIN - description: Number of replicas from: '[0-9]{1}' generate: expression name: REPLICAS - description: Memory request and limit for frontend container from: '[A-Z0-9]{4}' generate: expression name: FRONT_MEMORY - description: CPU request for frontend container from: '[A-Z0-9]{3}' generate: expression name: FRONT_CPU - description: Memory request and limit for backend container from: '[A-Z0-9]{4}' generate: expression name: BACK_MEMORY - description: CPU request for backend container from: '[A-Z0-9]{3}' generate: expression name: BACK_CPU
Что внутри этого template:
Deployment приложения. В качестве примера использованы nginx, который выступает в роли фронтенда, и демонстрационная stateless-версия camunda в качестве бэкенда.
ConfigMap с конфигурацией для nginx, подключаемый в контейнер.
Route — принимает трафик на целевой домен снаружи кластера.
Service — направляет трафик непосредственно к Pod'ам с приложением.
Также есть возможность параметризации ряда настроек.
Значения параметров, используемых в template, находятся в файле values.env:
NAME=example-application DOMAIN=example REPLICAS=1 FRONT_MEMORY=128Mi FRONT_CPU=50m BACK_MEMORY=512Mi BACK_CPU=50m
Переменные подставляются в раздел parameters.
Миграция кластера
В качестве целевого может выступать любой кластер, в основе которого лежит оригинальный Kubernetes. Для этой статьи миграция выполнялась в кластер, развернутый с помощью платформы Deckhouse.
Чтобы переехать из OpenShift в Kubernetes-кластер необходимо:
Вынести описания всех сущностей из template в отдельные YAML-файлы, так как template — это специфичный для OpenShift объект.
Изменить параметризацию c помощью файла
values.yamlвместоvalues.env.Заменить Route на Ingress.
Deployment, Service и ConfigMap требуют меньше всего изменений. Для каждого из них нужно создать свой файл с описанием. Начнем с приложения в файле app.yaml:
apiVersion: apps/v1 kind: Deployment metadata: name: {{ .Chart.Name }} spec: replicas: {{ .Values.app.replicas }} revisionHistoryLimit: 3 selector: matchLabels: app: {{ .Chart.Name }} template: metadata: labels: app: {{ .Chart.Name }} spec: containers: - image: camunda/camunda-bpm-platform:run-7.15.0 imagePullPolicy: Always name: camunda ports: - containerPort: 8080 name: http protocol: TCP resources: limits: memory: {{ .Values.app.backend.memory }} requests: cpu: {{ .Values.app.backend.cpu }} memory: {{ .Values.app.backend.memory }} - command: - /usr/sbin/nginx - -g - daemon off; image: nginx:stable-alpine imagePullPolicy: Always lifecycle: preStop: exec: command: - /bin/bash - -c - sleep 5; kill -QUIT 1 name: nginx ports: - containerPort: 9000 name: http protocol: TCP resources: limits: memory: {{ .Values.app.frontend.memory }} requests: cpu: {{ .Values.app.frontend.cpu }} memory: {{ .Values.app.frontend.memory }} volumeMounts: - mountPath: /etc/nginx/nginx.conf name: configs subPath: nginx.conf volumes: - configMap: name: {{ .Chart.Name }}-config name: configs
Service разместим в файле service.yaml:
apiVersion: v1 kind: Service metadata: annotations: description: Exposes and load balances the application pods name: {{ .Chart.Name }} spec: ports: - name: http port: 9000 targetPort: 9000 selector: app: {{ .Chart.Name }}
ConfigMap — в файле configmap.yaml:
apiVersion: v1 kind: ConfigMap metadata: name: {{ .Chart.Name }}-config data: nginx.conf: | user nginx; worker_processes 1; pid /run/nginx.pid; events { worker_connections 1024; } http { include /etc/nginx/mime.types; default_type application/octet-stream; upstream backend { server 127.0.0.1:8080 fail_timeout=0; } server { listen 9000; server_name _; root /www; client_max_body_size 100M; keepalive_timeout 10s; location / { proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header Host $http_host; proxy_redirect off; proxy_pass http://backend; } } }
Теперь заменим values.env на values.yaml:
app: replicas: 1 host: example.kubernetes.testing backend: memory: 512Mi cpu: 50m frontend: memory: 128Mi cpu: 50m
Route — тоже объект OpenShift. Заменим его на привычный Ingress (файл ingress.yaml):
apiVersion: networking.k8s.io/v1 kind: Ingress metadata: annotations: kubernetes.io/ingress.class: nginx name: {{ .Chart.Name }} spec: rules: - host: {{ .Values.app.host }} http: paths: - path: / pathType: Prefix backend: service: name: {{ .Chart.Name }} port: number: 9000
На этом можно считать подготовку оконченной.
Созданный Helm-чарт готов к деплою в «ванильный» Kubernetes.
В нашем примере используется GitLab CI и werf, однако это не обязательное условие. Чарт совместим с любыми CI/CD-системами.
Запустим деплой командой werf converge:

Проверим, что все ресурсы действительно появились в кластере:

И, наконец, убедимся, что приложение работает корректно и доступно для пользователей:

Немного о DeploymentConfig
В OpenShift часто используется ресурс DeploymentConfig, который также стоит упомянуть. Это версия Deployment, «расширенная» за счет ресурсов ImageStream и BuildConfig: они предназначены для сборки образов и деплоя приложения в кластер.
Прямая замена DeploymentConfig на аналогичный ресурс в «ванильном» Kubernetes невозможна. Поэтому для сохранения функциональности, которую предоставляет связка DeploymentConfig + ImageStream + BuildConfig, потребуются дополнительные инструменты.
Чтобы перенести DeploymentConfig в Kubernetes, можно заменить его на Deployment, а неподдерживаемые функции реализовать сторонними инструментами — например, CI-системой, инструментом для сборки образов, а также внешним registry для их хранения.
selector: name: ...
Ниже — примерный список действий для превращения DeploymentConfig в Deployment.
apiVersion: apps.openshift.io/v1заменить наapiVersion: apps/v1.kind: DeploymentConfigзаменить наkind: Deployment.spec.selectorsзаменить сselector: name: ...наselector: matchLabels: name: ...Убедиться, что секция
spec.template.spec.containers.imageописана для каждого контейнера.Удалить секции
spec.triggers,spec.strategyиspec.test.
Обратите внимание, что эта инструкция не универсальна. Каждый конкретный случай стоит рассмотреть отдельно. Рекомендуем ознакомиться с официальной документацией по DeploymentConfig.
Подытожим
Перенос приложения из OpenShift в «ванильный» Kubernetes требует декомпозиции templates на отдельные YAML-ресурсы, а также замены ряда специфичных для OpenShift объектов на сущности K8s.
Ниже — краткая таблица соответствия ресурсов OpenShift и K8s, которая поможет при миграции:
OpenShift | Kubernetes |
Template | Отказываемся в пользу Helm chart |
DeploymentConfig | Меняем на Deployment (не забывая об особенностях, связанных с ImageStream и BuildConfig) |
Route | Меняем на Ingress |
Deployment/Statefulset/Daemonset | Не требуют изменений (только замена параметризации) |
Service/ConfigMap и т. д. | Не требуют изменений (только замена параметризации) |
Рассмотренный в статье пример переезда с OpenShift в кластер под управлением Deckhouse актуален в том числе и для бесплатной версии платформы (community edition). Мы уже не раз переносили рабочие нагрузки наших клиентов с OpenShift, Rancher и других зарубежных решений, накопили лучшие практики и готовы помочь с миграцией на Deckhouse.
P.S.
Читайте также в нашем блоге:
