Привет, Хабр! Сегодня поговорим об опыте работы Cбера с Helm. Дело в том, что в Сбере широко используется контейнерная платформа OpenShift от RedHat, которая дополняет Kubernetes собственными возможностями, упрощает деятельность по разработке и сопровождению сервисов в промышленной эксплуатации. Платформа отличная, но у неё есть ряд проблем, о которых поговорим ниже. А справиться с ними можно при помощи Helm. О том, как нам помог этот пакетный менеджер, — читайте под катом.

OpenShift и проблемы платформы

С OpenShift команды Сбера работают достаточно давно. За это время нам удалось оценить ряд преимуществ платформы, но в то же время есть и проблемы:

  • Проекты OpenShift переходят в неконсистентное состояние при возникновении ошибок установки какой-либо сущности.

  • Отсутствуют удобные механизмы отката к предыдущим версиям. Если говорить простыми словами, то это ручной возврат к предыдущему состоянию инвентарных репозиториев и установка предшествующей поставки.

  • Сложно создавать гибкое описание структуры. Это особенно важно, когда на каких-то стендах разработки и тестирования используется более простая топология в связи с упрощёнными требованиями безопасности.

Проанализировав существующие инструменты, способные решить возникшие затруднения, мы решили выбрать пакетный менеджер для Kubernetes — Helm.

Для перехода на него нам потребовалось произвести модификацию производственных конвейеров, в рамках которой были выполнены следующие работы:

  • спроектирована структура хранения конфигурационных файлов Helm (Chart.yaml и values);

  • произведена замена исполняемого файла с OpenShift CLI на Helm;

  • добавлена автоматическая генерация обязательных файлов Readme и Chart.

После этого команды перевели манифесты OpenShift в Helm Charts для своих сервисов в рамках разработанной структуры.

Достоинства Helm

В ходе использования нового конвейера команды разработки и сопровождение оценили преимущества Helm:

  • Он позволяет создавать более гибкое описание структуры проектов OpenShift.

  • Накатывает только необходимую дельту изменений. Сравнивания между собой состояния текущего установленного релиза и нового релиза и свежей поставки, Helm выявляет необходимые к установки сущности.

  • Обеспечивает целостность установки. В проекте OpenShift релизы всегда остаются в консистентном состоянии. т. е. при возникновении ошибок при установке новых релизов откатывается весь релиз до предыдущего стабильного состояния.

  • Сохраняет истории установок. В любой момент можно обратиться к истории установок релизов и накатить необходимую версию.

  • Позволяет выполнить откат к предыдущей версии «в один клик». Благодаря наличию истории операция отката становится довольно простой, т. к. в секретах Helm хранится необходимое состояние и не требуется ручных изменений пользователя.

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

Канареечные релизы в практике Сбера

Ну а теперь переходим уже к сути статьи. Что такое канареечный релиз (canary release)? Так называют метод снижения риска внедрения новой версии продукта в промышленную эксплуатацию путём предоставления изменений небольшому подмножеству пользователей. С течением времени изменения становятся доступны всё большему числу пользователей, если с релизом всё хорошо. В конечном счёте новую версию продукта получают все пользователи.

Преимущества использования канареечных релизов:

  • сокращается время выхода обновлений на рынок;

  • пользователи быстрее получают новый функционал;

  • канареечные релизы не приводят к простоям;

  • команда разработчиков быстрее получает обратную связь от пользователей и может доработать продукт, дополнить функционал или исправить проблемы;

  • если тестирование пройдёт неудачно, то влияние будет на небольшое количество пользователей.

С чего всё началось

По канареечным релизам написано достаточно большое количество статей. Практически все они имеют одну схожую черту: заявленную необходимость разделения Stable‑ и Canary‑версий. Одна из реализаций этого — разделение наименования deployment в ручном режиме.

Перед нами встал вопрос: возможно ли реализовать канареечное внедрение без дополнительных манипуляций над Helm Chart для команд? Например, чтобы пользователь мог в любой момент запустить релиз в режиме Canary, не меняя конфигурационные файлы или не дорабатывая Helm Chart.

Давайте рассмотрим на простом примере, что необходимо для успешного канареечного внедрения.

Под автоматизацию решения отлично подходят Stateless‑приложения с обратной совместимостью версий.

В качестве примера будем использовать два дистрибутива:

1) Stable‑релиз:

ConfigMap.yml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: application-env-config
  labels:
    name: env-config
    app: {{ .Values.APP_NAME }}
data:
  TEST_LOGIN: "{{ .Values.TECH_LOGIN }}"
  TEST_DATA: "{{ .Values.TEST_DATA }}"

Service.yml
---
apiVersion: v1
kind: Service
metadata:
  name: {{ .Values.APP_NAME }}
  labels:
    app: {{ .Values.APP_NAME }}
spec:
  ports:
    - name: 8787 tcp-port
      port: 8787
      protocol: TCP
      targetPort: 8787
  selector:
    app: {{ .Values.APP_NAME }}

Deployment.yml
apiVersion: apps/v1
kind: Deployment
metadata:
  name: {{ .Values.APP_NAME }}
  labels:
    app: {{ .Values.APP_NAME }}
spec:
  replicas: {{ .Values.Replicas }}
  selector:
    matchLabels:
      app: {{ .Values.APP_NAME }}
  template:
    metadata:
      labels:
        app: {{ .Values.APP_NAME }}
    spec:
      containers:
        - name: {{ .Values.APP_CONTAINER_NAME }}
          image: {{ .Values.REGISTRY_URL }}/stable-distr
          command:
- 'python'
- 'runserver.py'
          envFrom:
            - configMapRef:
                name: application-env-config
          imagePullPolicy: Always
          ports:
            - containerPort: {{ .Values.APPLICATION_WEBSERVER_PORT }}
              protocol: TCP
          resources:
            limits:
              cpu: {{ .Values.limits.cpu }}
              memory: {{ .Values.limits.memory }}
            requests:
              cpu: {{ .Values.requests.cpu }}
              memory: {{ .Values.requests.memory }}
  strategy:
    type: RollingUpdate
    rollingUpdate:
      maxUnavailable: 25%
      maxSurge: 25%
  revisionHistoryLimit: 10
  progressDeadlineSeconds: 600

2)  Canary-релиз

ConfigMap.yml
apiversion: v1
kind: ConfigMap
metadata:
  name: application-env-config
  labels:
   name: env-config
   app: {{ .Values,APP NAME }}
data:
  TEST_LOGIN: "{{ .Values,. TECH LOGIN }}"
  TEST_DATA: "{{ .Values.TEST DATA }}"

Service.yml
apiversion: v1
kind: Service
metadata:
  name: {{ .Values.APP NAME }}
  labels:
   app: {{ .Values.APP NAME }}
spec:
 ports:
  — name: 8787-tep-port
  port: 8787
  protocol: TCP
  targetPort: 8787
selector:
 app: {{ .Values.APP NAME }}

Deployment.yml
apiversion: apps/v1
kind: Deployment
metadata:
 name: {{ .Values.APP NAME }}
 labels:
  app: {{ .Values.APP NAME }}
spec:
 replicas: {{ .Values.Replicas }}
 selector:
  MatchLabels:
   app: {{ .Values.APP NAME }}
 template:
  metadata:
   labels:
    app: {{ .Values.APP NAME }}
 spec:
  containers:
   - name: {{ .Values.APP CONTAINER NAME }}
     image: {{ .Values.REGISTRY URL }}/canary-distr
    command :
     - ‘python!
     - ‘runserver.py'
    envFrom:
     - configMapRef:
      name: application-env-config
    imagePullPolicy: Always
    ports:
     - containerPort: {{ .Values. APPLICATION WEBSERVER PORT }}
       protocol: TCP
    resources:
     limits:
      cpu: {{ .Values.limits.cpu }}
      memory: {{ .Values.limits.memory }}
     requests:
      cpu: {{ .Values.requests.cpu }}
      memory: {{ .Values.requests.memory }}
strategy:
 type: Rollingupdate
 rollingUpdate:
  maxUnavailable: 25%
  maxsurge: 25%
revisionHistoryLimit: 10
progressDeadlineseconds: 600

В новом Canary‑релизе ConfigMap и Service идентичны Stable‑релизу, в Deployment поменялся image (изменилась функциональность сервиса). К слову, ситуация, когда новым релизом поставляется в основном новый функционал и меняется только Docker‑образ, достаточно распространена в мире разработки. Если же требуется без изменений шаблонов команды внедрить канареечный релиз, то придётся продублировать необходимые сущности в одном чарте, а также реализовать распределение трафика между релизами.

Алгоритм работы с канареечным релизом

Основные пункты работ для получения из исходного чарта команды модифицированного канареечного чарта:

  1. В 99% случаев наименования сущностей (metadata: name) будут идентичны. Соответственно, необходимо разграничивать одни сущности от других. Например, каждой metadata: name задавать постфикс на основе yaml содержимого данной сущности. Далее будем называть это инъекцией имён.

  2. Для сохранения текущего состояния приложения необходимо хранить сущности текущего релиза, чтобы при установке через helm текущий Stable‑релиз не был заменен.

  3. Объединять инъецированные сущности (установленного релиза и канареечного) в одно целое. Сущности должны сохраняться в единственном экземпляре, это позволит использовать общие сущности (например, ConfigMap и Secret), причём как в Сanary‑релизе, так и в Stable‑релизе.

  4. Для реализации канареечного внедрения средствами Istio на этапе deploy необходимо в каждом deployment дополнительно создавать поля version и генерировать дополнительные Istio‑сущности для распределения трафика между deploymet's. В нашем случае при использовании Istio данная задача выполняется с добавлением Virtual Service, Destination Rule.

  5. Реализовать механизм управления распределением трафика (weight‑параметр в virtual service).

Наглядная схема конечного результата:

Представление с точки зрения Istio:

Реализация алгоритма

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

  • в качестве обеспечения непрерывной интеграции — Jenkins (Jenkins Pipeline);

  • для написания скриптов развёртывания — связка Ansible + Helm. Используя Ansible, мы также сможем использовать Python для решения некоторых пунктов реализации;

  • для канареечного внедрения используется Istio для распределения трафика.

Для успешной инъекции имён необходимо знать, где каждая сущность потенциально может использоваться. При переименовании сущности её необходимо переименовать во всех объектах, где она используется. Например, ConfigMap в Deployment может использоваться в секциях:

"@Deployment:/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name";
"@Deployment:/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name";
"@Deployment:/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name";
"@Deployment:/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name";
"@Deployment:/spec/template/spec/volumes/[]/configMap/name";
"@Deployment:/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name".

Вывод простой: нужно построить карту зависимостей для каждой сущности. Для построения будем использовать kubernetes‑json‑schema.

Мы реализовали построение нужной карты по следующему алгоритму.

Сначала читаем каждуюschema объекта, добавляем наименование сущности вlist(objects_link). Рекурсивно проходим каждое поле в сущности. Если в описании поля есть словоreferen и ключ поля== name, то добавляем вlist, в котором храним все зависимости (all_links). Формируем мапу на основании object_link иall_links.

Если название сущности (к примеру, ConfigMap) есть в зависимости («@DaemonSet:/spec/template/ spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name»), то данная зависимость нам подходит. Первичная карта зависимостей готова, далее добавляем зависимости по необходимости (те, которые не попали под условие). В итоге мы получили полную карту зависимостей.

MAP
MAP = {

    "Scale": [

        "@HorizontalPodAutoscalerList:/[]/spec/scaleTargetRef/name",

        "@HorizontalPodAutoscaler:/spec/scaleTargetRef/name",

    ],
"Role": [
        "@RoleBindingList:/[]/roleRef/name",
        "@ClusterRoleBindingList:/[]/roleRef/name",
        "@RoleBinding:/roleRef/name",
        "@ClusterRoleBinding:/roleRef/name",
    ],
    "ConfigMap": [
        "@DaemonSet:/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@DaemonSet:/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@DaemonSet:/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@DaemonSet:/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@DaemonSet:/spec/template/spec/volumes/[]/configMap/name",
        "@DaemonSet:/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@ReplicationController:/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@ReplicationController:/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",         "@ReplicationController:/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/ name",         "@ReplicationController:/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/configMap/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@PodPreset:/spec/env/[]/valueFrom/configMapKeyRef/name",
        "@PodPreset:/spec/envFrom/[]/configMapRef/name",
        "@PodPreset:/spec/volumes/[]/configMap/name",
        "@PodPreset:/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@JobList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@JobList:/[]/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@JobList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@JobList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/configMap/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@ReplicaSetList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/configMap/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@PodPresetList:/[]/spec/env/[]/valueFrom/configMapKeyRef/name",
        "@PodPresetList:/[]/spec/envFrom/[]/configMapRef/name",
        "@PodPresetList:/[]/spec/volumes/[]/configMap/name",
        "@PodPresetList:/[]/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@Deployment:/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@Deployment:/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@Deployment:/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@Deployment:/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@Deployment:/spec/template/spec/volumes/[]/configMap/name",
        "@Deployment:/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",         "@CronJob:/spec/jobTemplate/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/ name",         "@CronJob:/spec/jobTemplate/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",         "@CronJob:/spec/jobTemplate/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/ name",         "@CronJob:/spec/jobTemplate/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/configMap/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@StatefulSetList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@StatefulSetList:/[]/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@StatefulSetList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@StatefulSetList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@StatefulSetList:/[]/spec/template/spec/volumes/[]/configMap/name",
        "@StatefulSetList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@NodeList:/[]/spec/configSource/configMap/name",
        "@NodeList:/[]/status/config/active/configMap/name",
        "@NodeList:/[]/status/config/assigned/configMap/name",
        "@NodeList:/[]/status/config/lastKnownGood/configMap/name",
        "@ReplicaSet:/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@ReplicaSet:/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@ReplicaSet:/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@ReplicaSet:/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/configMap/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@PodTemplateList:/[]/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@PodTemplateList:/[]/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@PodTemplateList:/[]/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@PodTemplateList:/[]/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/configMap/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/projected/sources/[]/configMap/name",         "@ReplicationControllerList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/ name",         "@ReplicationControllerList:/[]/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",         "@ReplicationControllerList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/ configMapKeyRef/name",         "@ReplicationControllerList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/configMap/name",
        "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@Job:/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@Job:/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@Job:/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@Job:/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@Job:/spec/template/spec/volumes/[]/configMap/name",
        "@Job:/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@Pod:/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@Pod:/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@Pod:/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@Pod:/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@Pod:/spec/volumes/[]/configMap/name",
        "@Pod:/spec/volumes/[]/projected/sources/[]/configMap/name",
"@PodTemplate:/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
"@PodTemplate:/template/spec/containers/[]/envFrom/[]/configMapRef/name",
"@PodTemplate:/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
"@PodTemplate:/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
"@PodTemplate:/template/spec/volumes/[]/configMap/name",         "@PodTemplate:/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@Node:/spec/configSource/configMap/name",
        "@Node:/status/config/active/configMap/name",
        "@Node:/status/config/assigned/configMap/name",
        "@Node:/status/config/lastKnownGood/configMap/name",
        "@DeploymentList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@DeploymentList:/[]/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@DeploymentList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@DeploymentList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@DeploymentList:/[]/spec/template/spec/volumes/[]/configMap/name",
"@DeploymentList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",         "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/containers/[]/env/[]/valueFrom/ configMapKeyRef/name",         "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",         "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/initContainers/[]/env/[]/valueFrom/ configMapKeyRef/name",         "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/ name",         "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/configMap/name",         "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/projected/sources/[]/configMap/ name",         "@DaemonSetList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@DaemonSetList:/[]/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@DaemonSetList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@DaemonSetList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/configMap/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@StatefulSet:/spec/template/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@StatefulSet:/spec/template/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@StatefulSet:/spec/template/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@StatefulSet:/spec/template/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/configMap/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/projected/sources/[]/configMap/name",
        "@PodList:/[]/spec/containers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@PodList:/[]/spec/containers/[]/envFrom/[]/configMapRef/name",
        "@PodList:/[]/spec/initContainers/[]/env/[]/valueFrom/configMapKeyRef/name",
        "@PodList:/[]/spec/initContainers/[]/envFrom/[]/configMapRef/name",
        "@PodList:/[]/spec/volumes/[]/configMap/name",
        "@PodList:/[]/spec/volumes/[]/projected/sources/[]/configMap/name",
    ],
    "Secret": [
        "@DaemonSet:/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@DaemonSet:/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@DaemonSet:/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@DaemonSet:/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@DaemonSet:/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@DaemonSet:/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@DaemonSet:/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@DaemonSet:/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@DaemonSet:/spec/template/spec/volumes/[]/iscsi/secretRef/name", "@DaemonSet:/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
"@DaemonSet:/spec/template/spec/volumes/[]/rbd/secretRef/name",
"@DaemonSet:/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
"@DaemonSet:/spec/template/spec/volumes/[]/storageos/secretRef/name",
"@ReplicationController:/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",         "@ReplicationController:/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@ReplicationController:/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@ReplicationController:/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@ReplicationController:/spec/template/spec/volumes/[]/storageos/secretRef/name",
        "@PodPreset:/spec/env/[]/valueFrom/secretKeyRef/name",
        "@PodPreset:/spec/envFrom/[]/secretRef/name",
        "@PodPreset:/spec/volumes/[]/cephfs/secretRef/name",
        "@PodPreset:/spec/volumes/[]/cinder/secretRef/name",
        "@PodPreset:/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@PodPreset:/spec/volumes/[]/flexVolume/secretRef/name",
        "@PodPreset:/spec/volumes/[]/iscsi/secretRef/name",
        "@PodPreset:/spec/volumes/[]/projected/sources/[]/secret/name",
        "@PodPreset:/spec/volumes/[]/rbd/secretRef/name",
        "@PodPreset:/spec/volumes/[]/scaleIO/secretRef/name",
        "@PodPreset:/spec/volumes/[]/storageos/secretRef/name",
        "@JobList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@JobList:/[]/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@JobList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@JobList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@JobList:/[]/spec/template/spec/volumes/[]/storageos/secretRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@ReplicaSetList:/[]/spec/template/spec/volumes/[]/storageos/secretRef/name",
        "@PodPresetList:/[]/spec/env/[]/valueFrom/secretKeyRef/name",
"@PodPresetList:/[]/spec/envFrom/[]/secretRef/name",
"@PodPresetList:/[]/spec/volumes/[]/cephfs/secretRef/name",
"@PodPresetList:/[]/spec/volumes/[]/cinder/secretRef/name",
"@PodPresetList:/[]/spec/volumes/[]/csi/nodePublishSecretRef/name",
"@PodPresetList:/[]/spec/volumes/[]/flexVolume/secretRef/name",         "@PodPresetList:/[]/spec/volumes/[]/iscsi/secretRef/name",
        "@PodPresetList:/[]/spec/volumes/[]/projected/sources/[]/secret/name",
        "@PodPresetList:/[]/spec/volumes/[]/rbd/secretRef/name",
        "@PodPresetList:/[]/spec/volumes/[]/scaleIO/secretRef/name",
        "@PodPresetList:/[]/spec/volumes/[]/storageos/secretRef/name",
        "@PersistentVolumeList:/[]/spec/cephfs/secretRef/name",
        "@PersistentVolumeList:/[]/spec/cinder/secretRef/name",
        "@PersistentVolumeList:/[]/spec/csi/controllerPublishSecretRef/name",
        "@PersistentVolumeList:/[]/spec/csi/nodePublishSecretRef/name",
        "@PersistentVolumeList:/[]/spec/csi/nodeStageSecretRef/name",
        "@PersistentVolumeList:/[]/spec/flexVolume/secretRef/name",
        "@PersistentVolumeList:/[]/spec/iscsi/secretRef/name",
        "@PersistentVolumeList:/[]/spec/rbd/secretRef/name",
        "@PersistentVolumeList:/[]/spec/scaleIO/secretRef/name",
        "@PersistentVolumeList:/[]/spec/storageos/secretRef/name",
        "@Deployment:/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@Deployment:/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@Deployment:/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@Deployment:/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@Deployment:/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@Deployment:/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@Deployment:/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@Deployment:/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@Deployment:/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@Deployment:/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@Deployment:/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@Deployment:/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@Deployment:/spec/template/spec/volumes/[]/storageos/secretRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",         "@CronJob:/spec/jobTemplate/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/ name",         "@CronJob:/spec/jobTemplate/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@CronJob:/spec/jobTemplate/spec/template/spec/volumes/[]/storageos/secretRef/name",
        "@StatefulSetList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@StatefulSetList:/[]/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@StatefulSetList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@StatefulSetList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@StatefulSetList:/[]/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@StatefulSetList:/[]/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@StatefulSetList:/[]/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@StatefulSetList:/[]/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
"@StatefulSetList:/[]/spec/template/spec/volumes/[]/iscsi/secretRef/name",
"@StatefulSetList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
"@StatefulSetList:/[]/spec/template/spec/volumes/[]/rbd/secretRef/name",
"@StatefulSetList:/[]/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
"@StatefulSetList:/[]/spec/template/spec/volumes/[]/storageos/secretRef/name",         "@PersistentVolume:/spec/cephfs/secretRef/name",
        "@PersistentVolume:/spec/cinder/secretRef/name",
        "@PersistentVolume:/spec/csi/controllerPublishSecretRef/name",
        "@PersistentVolume:/spec/csi/nodePublishSecretRef/name",
        "@PersistentVolume:/spec/csi/nodeStageSecretRef/name",
        "@PersistentVolume:/spec/flexVolume/secretRef/name",
        "@PersistentVolume:/spec/iscsi/secretRef/name",
        "@PersistentVolume:/spec/rbd/secretRef/name",
        "@PersistentVolume:/spec/scaleIO/secretRef/name",
        "@PersistentVolume:/spec/storageos/secretRef/name",
        "@ReplicaSet:/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@ReplicaSet:/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@ReplicaSet:/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@ReplicaSet:/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@ReplicaSet:/spec/template/spec/volumes/[]/storageos/secretRef/name",
        "@PodTemplateList:/[]/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@PodTemplateList:/[]/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@PodTemplateList:/[]/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@PodTemplateList:/[]/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/cephfs/secretRef/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/cinder/secretRef/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/iscsi/secretRef/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/rbd/secretRef/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@PodTemplateList:/[]/template/spec/volumes/[]/storageos/secretRef/name",         "@ReplicationControllerList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/ name",         "@ReplicationControllerList:/[]/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",         "@ReplicationControllerList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/ name",         "@ReplicationControllerList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/scaleIO/secretRef/name", "@ReplicationControllerList:/[]/spec/template/spec/volumes/[]/storageos/secretRef/name",
"@Job:/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
"@Job:/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
"@Job:/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
"@Job:/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",         "@Job:/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@Job:/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@Job:/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@Job:/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@Job:/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@Job:/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@Job:/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@Job:/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@Job:/spec/template/spec/volumes/[]/storageos/secretRef/name",
        "@Pod:/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@Pod:/spec/containers/[]/envFrom/[]/secretRef/name",
        "@Pod:/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@Pod:/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@Pod:/spec/volumes/[]/cephfs/secretRef/name",
        "@Pod:/spec/volumes/[]/cinder/secretRef/name",
        "@Pod:/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@Pod:/spec/volumes/[]/flexVolume/secretRef/name",
        "@Pod:/spec/volumes/[]/iscsi/secretRef/name",
        "@Pod:/spec/volumes/[]/projected/sources/[]/secret/name",
        "@Pod:/spec/volumes/[]/rbd/secretRef/name",
        "@Pod:/spec/volumes/[]/scaleIO/secretRef/name",
        "@Pod:/spec/volumes/[]/storageos/secretRef/name",
        "@PodTemplate:/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@PodTemplate:/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@PodTemplate:/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@PodTemplate:/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@PodTemplate:/template/spec/volumes/[]/cephfs/secretRef/name",
        "@PodTemplate:/template/spec/volumes/[]/cinder/secretRef/name",
        "@PodTemplate:/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@PodTemplate:/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@PodTemplate:/template/spec/volumes/[]/iscsi/secretRef/name",
        "@PodTemplate:/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@PodTemplate:/template/spec/volumes/[]/rbd/secretRef/name",
        "@PodTemplate:/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@PodTemplate:/template/spec/volumes/[]/storageos/secretRef/name",
        "@DeploymentList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@DeploymentList:/[]/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@DeploymentList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@DeploymentList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@DeploymentList:/[]/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@DeploymentList:/[]/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@DeploymentList:/[]/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@DeploymentList:/[]/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@DeploymentList:/[]/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@DeploymentList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@DeploymentList:/[]/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@DeploymentList:/[]/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@DeploymentList:/[]/spec/template/spec/volumes/[]/storageos/secretRef/name",         "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/ name",         "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",         "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/initContainers/[]/env/[]/valueFrom/ secretKeyRef/name",
"@CronJobList:/[]/spec/jobTemplate/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
"@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/cephfs/secretRef/name",         "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@CronJobList:/[]/spec/jobTemplate/spec/template/spec/volumes/[]/storageos/secretRef/name",
        "@DaemonSetList:/[]/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@DaemonSetList:/[]/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@DaemonSetList:/[]/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@DaemonSetList:/[]/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@DaemonSetList:/[]/spec/template/spec/volumes/[]/storageos/secretRef/name",
        "@StatefulSet:/spec/template/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@StatefulSet:/spec/template/spec/containers/[]/envFrom/[]/secretRef/name",
        "@StatefulSet:/spec/template/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@StatefulSet:/spec/template/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/cephfs/secretRef/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/cinder/secretRef/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/flexVolume/secretRef/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/iscsi/secretRef/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/projected/sources/[]/secret/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/rbd/secretRef/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/scaleIO/secretRef/name",
        "@StatefulSet:/spec/template/spec/volumes/[]/storageos/secretRef/name",
        "@PodList:/[]/spec/containers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@PodList:/[]/spec/containers/[]/envFrom/[]/secretRef/name",
        "@PodList:/[]/spec/initContainers/[]/env/[]/valueFrom/secretKeyRef/name",
        "@PodList:/[]/spec/initContainers/[]/envFrom/[]/secretRef/name",
        "@PodList:/[]/spec/volumes/[]/cephfs/secretRef/name",
        "@PodList:/[]/spec/volumes/[]/cinder/secretRef/name",
        "@PodList:/[]/spec/volumes/[]/csi/nodePublishSecretRef/name",
        "@PodList:/[]/spec/volumes/[]/flexVolume/secretRef/name",
        "@PodList:/[]/spec/volumes/[]/iscsi/secretRef/name",
        "@PodList:/[]/spec/volumes/[]/projected/sources/[]/secret/name",
        "@PodList:/[]/spec/volumes/[]/rbd/secretRef/name",
        "@PodList:/[]/spec/volumes/[]/scaleIO/secretRef/name",
        "@PodList:/[]/spec/volumes/[]/storageos/secretRef/name",
        "@Deployment:/spec/template/spec/volumes/[]/secret/secretName",
    ],
    "Service": [
        "@Route:/spec/to/name",
        "@DestinationRule:/spec/host",
        "@VirtualService:/spec/hosts/[]",
        "@VirtualService:/spec/http/[]/route/[]/destination/host",
    ],
"Gateway": [
        "@VirtualService:/spec/gateways/[]"
   ],
}

Шаблонизация с файлов конфигурации, работаем с уже «реальными» шаблонами:

Перед тем как инъектировать имена, требуется произвести подстановку переменных стенда, т. к. они будут необходимы для корректной работы Canary‑релиза и могут отличаться от Stable, в том числе и по составу (например, ConfigMap). Для этого перед основным запуском helm происходит этап валидации шаблонов (dry‑run). Далее все манипуляции производятся с результатом выполнения dry‑run, на котором все переменные стенда были подставлены и определены.

Инъектирования имён

На этапе инъекции имён решается проблема определения изменённых сущностей. Для каждой сущности нужно взять полный текст её описания и пропустить его через алгоритм хэширования (мы использовали md5). Для определения уникальности достаточно взять первые восемь символов от полученного результата. Это и послужит постфиксом при инъекции в имени сущности. Далее выполняем инъекцию имён для нашего Canary‑релиза с учётом построенной карты зависимостей.

Объединение релизов

На этом этапе мы объединяем текущий установленный Stable‑релиз и новый Canary в один чарт, чтобы уже произвести установку. Stable‑релиз с инъекцией имён мы достаём из секретов Helm. Туда его сохраняем на финальном этапе установки, когда трафик полностью перенаправлен на новый релиз, и признаём его Stable. Такую операцию выполняем не только при канареечном развёртывании, но и при любом другом (в том числе и откате), чтобы способ внедрения могла определять команда самостоятельно, а функционал продолжал работать.

Для сущностей, которые отличаются в Stable‑ и Canary‑релизах, имена сущностей после инъекций будут отличаться в части постфиксов — они будут дублироваться в итоговом общем чарте. Для реализации «канарейки» необходимо в каждый deployment добавить метку для дальнейшего разграничения трафика — labels: version: v1 и v2 в примере ниже. На основе этих меток будут заданы правила DestinationRule.

Итоговый результат на тестовых дистрибутивах будет следующий:

deployment.yml
---
apiVersion: v1
data:
  TEST_DATA: dGVzdA==
  TEST_LOGIN: dGVzdA==
kind: ConfigMap
metadata:
  labels:
    app: test_app
    name: env-config
  name: application-env-config-a9ed3f8f
---
apiVersion: v1
kind: Service
metadata:
  labels:
    app: test_app
  name: test_app-fc37f001
spec:
  ports:
  - name: 878 - tcp-port
    port: 8787
    protocol: TCP
    targetPort: 8787
  selector:
    app: test_app
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: test_app
  name: test_app-9de2a4a6
spec:
  progressDeadlineSeconds: 600
  replicas: 2
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: test_app
      version: v1
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: test_app
        version: v1
    spec:
      containers:
      - command:
        - python
        - runserver.py
    envFrom
        - configMapRef:
            name: application-env-config-a9ed3f8f
        image: registry.test.docker.ru/stable-distra23860df1ba1e9b403295d4daacc072e017917f2ce7150f36284ae04ef38659
        imagePullPolicy: Always
        name: test_container
        ports:
        - containerPort: 8787
          protocol: TCP
        resources:
          limits:
            cpu: 1
            memory: 1000Mi
          requests:
            cpu: 1
            memory: 1000Mi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  labels:
    app: test_app
  name: test_app-9808b06a
spec:
  progressDeadlineSeconds: 600
  replicas: 2
  revisionHistoryLimit: 10
  selector:
    matchLabels:
      app: test_app
      version: v2
  strategy:
    rollingUpdate:
      maxSurge: 25%
      maxUnavailable: 25%
    type: RollingUpdate
  template:
    metadata:
      labels:
        app: test_app
        version: v2
    spec:
      containers:
      - command:
        - python
        - runserver.py
        envFrom:
        - configMapRef:
            name: application-env-config-a9ed3f8f
image: registry.test.docker.ru/canary-distr256a0d0591003d4453632506a0c8e0c5743a6cdfda3169103ad54b5050d8f6f99c
        imagePullPolicy: Always
        name: test_container
        ports:
        - containerPort: 8787
          protocol: TCP
        resources:
          limits:
            cpu: 1
            memory: 1000Mi
          requests:
            cpu: 1
            memory: 1000Mi

Всё, дополнительные манипуляции с сущностями Stable-релиза и канареечного релиза завершены. Осталось лишь донастроить Istio для управления трафиком.

Реализация распределения трафика средствами Istio

Предполагаем, что Ingeress, Gateway заранее настроены, и знаем host, по которому будет доступен сервис.

Осталось добавить две сущности Istio.

Destination Rule:

destination_rule.yml
apiVersion: networking.istio.io/v1alpha3
kind: DestinationRule
metadata:
  name: dr-canary
spec:
  exportTo: [.]
  host: {{ SERVICE_NAME }}
  subsets:
  - name: v1
    labels:
      version: v1
  - name: v2
    labels:
      version: v2

и Virtual Service:

virtual_service.yml
apiVersion: networking.istio.io/v1alpha3
kind: VirtualService
metadata:
  name: vs-canary
spec:
  exportTo:
    - .
  hosts: {{ CANARY_INGRESS_HOSTS }}
  gateways:
    - {{ CANARY_INGRESS_GATEWAY }}
  http:
  - route:
    - destination:
        host: {{ SERVICE_NAME }}
        subset: v1
      weight: {{ 100  - CANARY_WEIGHT }}
    - destination:
        host: {{ SERVICE_NAME }}
        subset: v2
      weight: {{ CANARY_WEIGHT }}

SERVICE_NAME — имя полученного после модификаций сервиса. В нашем примере — test_app-fc37f001.

CANARY_INGRESS_HOSTS — точка входа в приложение.

CANARY_INGRESS_GATEWAY — наименование gateway.

CANARY_WEIGHT — % для канареечного трафика.

После добавления данных сущностей готов финальный Helm-чарт для установки с указанием распределения трафика. Т. к. в части Stable-релиза чарт не имеет различий (т. к. его взяли из сохранённого состояния), он не будет пересоздаваться. Соответственно, на нашем проекте появится новый Deployment с Canary-релизом, куда перенаправится указанное кол-во трафика.

В приведённом примере реализации параметр CANARY_WEIGHT задаётся в Jenkins-джобе при запуске канареечного деплоя. Следовательно, может управляться как вручную, так и автоматизированно, если настроены соответствующие проверки. Постепенно увеличивая значения параметра CANARY_WEIGHT, полностью переводит весь трафик на Canary-релиз.

Фиксация Stable-релиза:

После того как на Canary-релиз постепенно переведено 100% трафика и принято решение об успешном внедрении, выполняем последнюю модификацию, чтобы перевести его в Stable-релиз. Для этого из Helm Charts убираем лишние сущности, накатываем Stable-релиз и сохраняем его в секреты OpenShift для последующих Canary-установок.

Вышеописанное решение прекрасно работает для stateless-приложений. Но мы столкнулись с необходимостью перевести на Canary-релизы stateful-приложение, в котором использовались sticky sessions (крайне не рекомендовано к использованию). Но и эту проблему нам удалось решить. Так, нужно создать EnvoyFilter, где по формуле определяется, куда попадёт пользователь (Canary-релиз или Stable), и в запрос добавляется соответствующий header. Далее в Virtual Service идёт обработка по данному header.

Что в итоге?

У нас получились канареечные внедрения с плавным переключением трафика без простоя, сохранились все преимущества Helm. Причём командам не потребуется модифицировать свои Helm Charts под каждый канареечный релиз, что, согласитесь, удобно.

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

.

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

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

Комментарии (7)


  1. v0rdych
    30.01.2023 16:42
    +2

    Т.е. за деньги, заплаченные за OpenShift, пришлось самостоятельно выкорчевывать часть его функционала и менять этот функционал на Helm?

    А это потому, что RedHat запросил много денег за дополнительные фичи, или потому что перестал отвечать на запросы?


    1. Thomas_Hanniball
      30.01.2023 20:26

      Сам Сбер под санкциями + компания RedHat теперь не работает с компаниями из РФ и РБ. Так что полагаться на какие-либо решения от RedHat сейчас нельзя. Вот и приходится самостоятельно выкручиваться.


    1. Inlore
      30.01.2023 21:51
      +3

      Helm - это просто шаблонизатор манифестов для кубера. Вообще без разницы, что там будут описаны кастомные сущности опеншифта - после прогона шаблонизатором они будут такими же валидными манифестами, которые "скушает" кластеровый api

      Про использование helm также написано в официальной документации опеншифта

      Предположение, что helm что-то заменяет или с его применением что-то надо выпиливать - ошибочно. helm расширяет возможности работы с манифестами как для кубера, так и для опеншифта (который, по сути, тот же кубер, просто кастомизированный)


    1. Sber Автор
      31.01.2023 17:51
      +1

      Выкорчёвывать какую-либо функциональность из OpenShift не пришлось. Возможно, не использовать — это верно.
      Решили в своё время использовать все преимущества Helm как пакетного менеджера, которыми OpenShift не обладает.
      Для канареечных релизов на Helm пришлось сделать небольшую доработку, чтобы исключить ручные манипуляции Helm-чартов для команд при внедрении новых канареечных релизов.
      Данное решение также подходит для стандартного Kubernetes.


      1. v0rdych
        31.01.2023 21:39

        А почему тогда все еще OpenShift, а не стандартный K8s?


        1. Hanamime
          02.02.2023 10:09
          +1

          В данной статье рассказали о техническом решении автоматизации канареечных релизов для Helm. Она не касается планов или работ по использованию той или иной платформы, т.к. в данной теме это не так важно.


  1. evg_krsk
    31.01.2023 09:57
    -2

    Ощущение, что на хабр стал писать ChatGPT.