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.
Читайте также в нашем блоге:
olga_ryabukhina
Что значит "ванильный"?
WellsBart
Ванильная — оригинальная, немодифицированная версия ПО. В случае Kubernetes это, грубо говоря, бесплатная версия ПО с базовой функциональностью, которую нужно настраивать самому. В «ванильном» Kubernetes нет интеграции с инструментами мониторинга, безопасности, логирования и пр. и пр. — всем тем, что важно для production. Отсюда — популярность Kubernetes-платформ и managed-сервисов, у которых всё это есть «из коробки».
olga_ryabukhina
Спасибо!
shurup
https://en.wikipedia.org/wiki/Vanilla_software