Я Игорь Юрченко, backend-разработчик Сбера, в этой статье расскажу о нашем опыте автоматизации деплоя потоков Apache NiFi.

Apache NiFi — инструмент для управления потоками данных между автоматизированными системами (реализует подход ETL — extract, transform, load). Документация: https://nifi.apache.org/documentation/v1 (на момент написания статьи актуальна версия 2.x, но тут речь про 1.x). Физически это Java-приложение с графическим web-интерфейсом, в котором настраивается поток — в общем случае набор процессоров, которые получают на вход какие-то данные от предыдущего процессора или из внешней системы, обрабатывают их определённым образом и передают следующему процессору или во внешнюю систему. Процессор — готовый модуль с параметрами интеграции и/или обработки данных (например, строка подключения к БД, или схема трансформации данных). То есть ETL настраивается графически, без написания кода. NiFi обладает возможностями горизонтального масштабирования (ноды кластера имеют одинаковую копию настроек потока, обрабатывают данные параллельно), и расширения (пользователь может писать custom процессоры и использовать их в потоках наравне со штатными). Из коробки поддерживается множество внешних систем и протоколов передачи данных.

Apache NiFi Registry — инструмент версионирования потоков, Java-приложение с web-интерфейсом, интегрировано с NiFi. Что-то вроде системы контроля версий исходного кода, но проще. Пользователь может сохранять в Registry, просматривать и восстанавливать старые версии потока. Документация: https://nifi.apache.org/docs/nifi-registry-docs.

Оба инструмента имеют REST API.

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

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

NiFi сам по себе уже является low-code платформой, а описанный в статье подход ещё больше упрощает его использование. Он основан на Scenario 2, Case 4 (Two NiFi Registries using the NiPyAPI) из блога https://pierrevillard.com/2018/2018-04-09-automate-workflow-deployment-in-apache-nifi-with-the-nifi-registry/ для NiFi и Registry версии 1.24 (на момент написания статьи). Реализован в виде библиотеки nifi-deployer.jar (далее коротко - «джарник») и является частью Jenkins pipeline деплоя (сам pipeline тут не описан, но в общих чертах это — git checkout и Jinja2 templating шаблона и метаданных потока, запуск джарника с параметрами — их именами). Можно реализовать на любом языке с поддержкой JSON и HTTP, смысл — вызываем в определённом порядке методы NiFi REST API.

Термины:

  1. поток — версионированная (сохранённая в Registry) process group в NiFi

  2. шаблон — файл описания потока в формате JSON, совместимый с NiFi REST API (содержит процессоры и controller services, их свойства, связи, вложенные process groups и т.д.). Шаблон кастомизируется метаданными и превращается в поток при деплое (1 шаблон * N метаданных = N потоков). Не путать с template в NiFi, ниже описана разница

  3. метаданные — файл параметров потока в формате JSON. Параметры могут быть бизнесовые (фильтры загружаемых сущностей, даты, Avro-схемы и т.д. — статические значения, одинаковые между стендами) и технические (HTTP URL, JDBC connection strings, Kafka brokers — отличаются между стендами, дополнительно кастомизируются через Ansible). Также метаданные делятся на variables (которые в NiFi можно заполнять в отдельном окне, а ссылки на них писать в свойства компонентов выражениями Expression Language — ${variable}, они разрешаются самим NiFi при работе потока), и так называемые api-data (разрешаются не в NiFi, а при деплое — значения заранее подставляются в свойства процессоров, не поддерживающих Expression Language)

В статье не описаны установка и настройка NiFi + Registry (в т.ч. настройки аутентификации пользователей). В интернете можно найти материалы, а тут предполагаем, что это уже сделано.

Общее описание подхода

Процесс разработки и деплоя:

  1. Создаем/меняем process group в Dev NiFi (или на localhost); группа может быть на любом уровне вложенности

  2. Коммитим изменения в Dev Registry (при необходимости создаем bucket, https://nifi.apache.org/docs/nifi-registry-docs/html/user-guide.html#create-a-bucket) — ручное действие

  3. Экспортируем шаблон из Registry в локальный файл, вместе с набором переменных (метаданными) этого потока в отдельном файле для удобства изменения — автоматизировано. Теоретически это можно делать и вручную (https://nifi.apache.org/docs/nifi-registry-docs/html/user-guide.html#export-a-flow-version), но Registry экспортирует процессоры в случайном порядке, автоматизация позволяет отсортировать процессоры в файле, а это минимизирует Git diff

  4. Вручную правим шаблон — подставляем элементы api-data в нужные свойства процессоров (отсюда следует важная рекомендация, подробно рассмотренная в примере ниже — свойства, поддерживающие Expression Language в NiFi, лучше реализовать через variables, п.ч. они экспортируются в готовом виде ${variable}, их не надо вручную менять после экспорта; а свойства, не поддерживающие Expression Language, экспортируются с конкретными значениями, их нужно вручную менять на конструкцию вида [[ api-data ]]; также в файле шаблона нужно восстановить удаляемые при коммите в Registry sensitive-свойства (пароли и т.д.), подставить в них элементы [[ api-data ]] или ссылки вида #{param} на parameter context, в зависимости от принятого в команде способа управления секретами — про parameter context/provider будет следующая статья; перечень sensitive свойств можно установить по элементам propertyDescriptors — даже если свойство не экспортировано в элементе properties, в propertyDescriptors оно будет описано, как "sensitive": true, тогда его "name" должно стать ключом в properties)

  5. Вручную правим файл метаданных (в технических variables подставляем Ansible-переменные вида {{ var }} - HTTP URL-ы, строки подключения к БД и т.д.; добавляем массив api-data с необходимыми элементами api — то, что в шаблоне описано в виде [[ api-data ]])

  6. Коммитим шаблон и метаданные в Git — ручное действие. Шаблон и метаданные могут храниться в разных репозиториях и ветках, любой Git flow на ваш вкус, можно merge, revert, diff, blame

  7. Импортируем файл шаблона в Prod Registry, при этом создаются bucket и flow — автоматизировано (тут же элементы метаданных api-data подставляются в файл шаблона — текстовая замена по конструкциям вида [[ api-data ]])

  8. Импортируем flow из Prod Registry в Prod NiFi (в т.ч. если потока еще нет в NiFi, он создается) — автоматизировано

  9. Обновляем значения переменных потока из файла метаданных — автоматизировано

Пункты до «коммитим в Git» включительно выполняются традиционно разработчиком, но могут и другими ролями, технических стоп-факторов нет. Дальше — Jenkins pipeline-ом, то есть любым человеком, кто хочет просто и быстро задеплоить на прод поток по определённому шаблону (с нужным функционалом), с определёнными параметрами — аналитики, devops-инженеры, сотрудники бизнес-подразделений. Для достижения своих целей им достаточно иметь только описание доступных шаблонов, и по каждому шаблону описание его параметров.

Почему именно такой процесс, а не через templates? На первый взгляд это проще, без дополнительного компонента Registry. Но обновление process group в NiFi может быть комплексным — процессоры не только меняют конфигурацию, но и добавляются/удаляются (а при этом останавливаются), меняются connections между ними и т.д. Чтобы все это делать консистентно, как раз и существует Registry — это его прямая обязанность. А templates при импорте на канву не останавливают процессоры. Как мне кажется, они больше предназначены для создания "snippets",  переиспользования одного набора процессоров в разных потоках в одном инстансе NiFi (ускорение разработки — делаем один поток, его часть сохраняем в виде template, используем в другом потоке в этом же NiFi).

Template в NiFi (НЕ «шаблон» в терминах этой статьи):

Скрытый текст

На любом содержимом канвы (один или несколько процессоров или process groups, не обязательно связанных) в контекстном меню пункт Create template:

Созданный шаблон отображается в главном меню в пункте Templates:

Элемент Template верхней панели инструментов позволяет импортировать его на канву:

Пример создания потока

Предположим, группе разработки пришло требование — загрузить данные из REST API в БД. На Dev-стенде создаём поток:

Здесь и далее под dev/prod стендами для демонстрации понимаются группы с соответствующими именами на одном физическом стенде.

Пользователь источника (REST API) указан в свойстве процессора GetHTTP, не поддерживающем Expression Language:

Секретные свойства (пароли) указаны в явном виде, без ссылок на parameter context (защита стандартной маскировкой в Web-интерфейсе — отображается «Sensitive value set» после ввода секрета):

Адрес источника (REST API) и путь к файлу truststore указаны в variables (ссылки на них из свойств компонентов с помощью Expression Language; конкретные значения на скриншотах замаскированы):

 Сохраняем поток в Dev Registry:

Экспортируем шаблон из Dev Registry:

java -jar nifi-deployer-0.36-SNAPSHOT.jar -e bucket.name=default flow.name=http-2-db-dev export.path=./http-2-db-1.24.json parent.group.id=cba7fcb9-0186-1000-0000-0000451bf611 nifi.url=https://.../nifi-api registry.url=https://.../nifi-registry-api user=... password=... ssl.keystore.filename=./nifi.p12

Лог экспорта:

Скрытый текст
19.05.2026 14:39:02.142 [main] INFO ApiRegistry - get bucket ID for bucket name default
19.05.2026 14:39:02.142 [main] DEBUG HttpClient - GET https://.../nifi-registry-api/buckets
19.05.2026 14:39:03.384 [main] INFO ApiRegistry - using bucket 2cf0ac20-3ccd-41ed-97c1-97baecf45dfc
19.05.2026 14:39:03.384 [main] INFO ApiRegistry - get flow ID for flow name http-2-db-dev and bucketId 2cf0ac20-3ccd-41ed-97c1-97baecf45dfc
19.05.2026 14:39:03.384 [main] DEBUG HttpClient - GET https://.../nifi-registry-api/buckets/2cf0ac20-3ccd-41ed-97c1-97baecf45dfc/flows
19.05.2026 14:39:03.699 [main] INFO ApiRegistry - using flow 20be5260-9249-4626-99d8-e95b7286836e
19.05.2026 14:39:03.700 [main] DEBUG HttpClient - GET https://.../nifi-registry-api/buckets/2cf0ac20-3ccd-41ed-97c1-97baecf45dfc/flows/20be5260-9249-4626-99d8-e95b7286836e/versions/latest
19.05.2026 14:39:04.275 [Thread-21] INFO getVersionControlInfo$ - get version control info for http-2-db-dev recursively from cba7fcb9-0186-1000-0000-0000451bf611
19.05.2026 14:39:20.650 [main] INFO ApiNiFiGeneric - get variable registry from process group c9143fc0-c492-3fa7-95ae-81c7de3245af
19.05.2026 14:39:20.650 [main] DEBUG HttpClient - GET https://.../nifi-api/process-groups/c9143fc0-c492-3fa7-95ae-81c7de3245af/variable-registry
19.05.2026 14:39:20.854 [main] INFO getMetadataFromNiFi$ - get nested process groups for c9143fc0-c492-3fa7-95ae-81c7de3245af
19.05.2026 14:39:20.855 [main] DEBUG HttpClient - GET https://.../nifi-api/flow/process-groups/c9143fc0-c492-3fa7-95ae-81c7de3245af
19.05.2026 14:39:20.990 [main] INFO writeTemplateAndMetadata$ - write template http-2-db-1.24.json
19.05.2026 14:39:21.003 [main] INFO writeMetadata$ - write metadata http-2-db-1.24-var.json
19.05.2026 14:39:21.062 [main] INFO exportTemplateAndMetadataFromRegistry$ - version 1 exported

Исправим файл шаблона:

  1. Для доступа к REST API на Dev-стенде в потоке используется имя пользователя «rest_api_test_user», но на других стендах оно может быть другим — это параметр. Свойство Username процессора GetHTTP не поддерживает Expression Language (задано в NiFi явно, не через variable), так что в файле шаблона заменим его на конструкцию вида [[ restApiUser ]] (строка 685 — "Username" : "[[ restApiUser ]]"). Ключ restApiUser может быть любым, задаётся автором шаблона (по-хорошему должен адекватно описывать суть параметра). Две квадратные скобки — специальный синтаксис, по которому выполняется текстовая замена этих параметров при деплое

  2. Секретные свойства компонентов не экспортируются из NiFi в Registry, соответственно и в шаблон не попадают. В данном потоке это пароль базовой HTTP-аутентификации в REST API, пароль БД и пароль truststore для установления SSL-соединения с REST API. Используются соответственно в процессоре GetHTTP, в controller service DBCPConnectionPool и StandardSSLContextService. Найдём их в файле шаблона по строке "sensitive" : true. Они описаны в элементе propertyDescriptors соответствующего компонента. Из элемента name возьмём ключ, добавим его в элемент properties, а в качестве значения укажем ту же конструкцию [[ … ]] с адекватным именем параметра, например для пароля REST API это может быть restApiPassword. Таким образом, в properties процессора GetHTTP добавим "Password" : "[[ restApiPassword ]]"

Исправим файл метаданных:

  1. все параметры шаблона (name из [[ name ]]) добавим в массив api-data на верхнем уровне метаданных, для каждого укажем value — реальное значение, которое при деплое подставится в шаблон вместо [[ name ]]

  2. для самого процесса деплоя нужны общие параметры, независимые от шаблона — имя потока, с которым он будет импортирован в NiFi Registry, и целевой путь потока в NiFi. Эти параметры не используются в файле шаблона, но зарезервированы для использования джарником, то есть их имена должны быть одинаковыми во всех файлах метаданных для всех шаблонов. Добавим их с нужными значениями: registryFlowName = http-2-db, processGroupPath = tmp>>prod>>http-2-db. Эти значения должны быть уникальны в рамках одного стенда NiFi, чтобы при деплое не перепутались два логически разных потока.

Исправленные файлы шаблона и метаданных:

Скрытый текст
{
  "bucket" : {
    "allowBundleRedeploy" : false,
    "allowPublicRead" : false,
    "createdTimestamp" : 1690978653562,
    "description" : "",
    "identifier" : "2cf0ac20-3ccd-41ed-97c1-97baecf45dfc",
    "link" : {
      "href" : "buckets/2cf0ac20-3ccd-41ed-97c1-97baecf45dfc",
      "params" : {
        "rel" : "self"
      }
    },
    "name" : "default",
    "permissions" : {
      "canDelete" : true,
      "canRead" : true,
      "canWrite" : true
    }
  },
  "externalControllerServices" : { },
  "flow" : {
    "bucketIdentifier" : "2cf0ac20-3ccd-41ed-97c1-97baecf45dfc",
    "bucketName" : "default",
    "createdTimestamp" : 1779182540682,
    "description" : "",
    "identifier" : "20be5260-9249-4626-99d8-e95b7286836e",
    "link" : {
      "href" : "buckets/2cf0ac20-3ccd-41ed-97c1-97baecf45dfc/flows/20be5260-9249-4626-99d8-e95b7286836e",
      "params" : {
        "rel" : "self"
      }
    },
    "modifiedTimestamp" : 1779182541552,
    "name" : "http-2-db-dev",
    "type" : "Flow",
    "versionCount" : 1
  },
  "flowContents" : {
    "comments" : "",
    "componentType" : "PROCESS_GROUP",
    "connections" : [ {
      "backPressureDataSizeThreshold" : "1 GB",
      "backPressureObjectThreshold" : 10000,
      "bends" : [ {
        "x" : 1152.0,
        "y" : 480.0
      } ],
      "componentType" : "CONNECTION",
      "destination" : {
        "comments" : "",
        "groupId" : "ad86f407-8631-318b-8230-76d1d7164043",
        "id" : "b0cf515e-e112-3ed4-8ebf-cabcb17e004c",
        "instanceIdentifier" : "b0cf515e-e112-3ed4-8c40-e0d8585aef1b",
        "name" : "Funnel",
        "type" : "FUNNEL"
      },
      "flowFileExpiration" : "0 sec",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "identifier" : "c773dc4f-2492-3c20-875c-574bc3782390",
      "instanceIdentifier" : "c773dc4f-2492-3c20-8278-9856e76bcb46",
      "labelIndex" : 1,
      "loadBalanceCompression" : "DO_NOT_COMPRESS",
      "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
      "name" : "",
      "partitioningAttribute" : "",
      "prioritizers" : [ ],
      "selectedRelationships" : [ "failure", "retry" ],
      "source" : {
        "comments" : "",
        "groupId" : "ad86f407-8631-318b-8230-76d1d7164043",
        "id" : "32d35b0d-daab-3189-9f86-71fbe584a6c7",
        "instanceIdentifier" : "32d35b0d-daab-3189-b8ae-148fcd2e1c26",
        "name" : "PutSQL",
        "type" : "PROCESSOR"
      },
      "zIndex" : 0
    }, {
      "backPressureDataSizeThreshold" : "1 GB",
      "backPressureObjectThreshold" : 10000,
      "bends" : [ {
        "x" : 848.0,
        "y" : 408.0
      } ],
      "componentType" : "CONNECTION",
      "destination" : {
        "comments" : "",
        "groupId" : "ad86f407-8631-318b-8230-76d1d7164043",
        "id" : "b0cf515e-e112-3ed4-8ebf-cabcb17e004c",
        "instanceIdentifier" : "b0cf515e-e112-3ed4-8c40-e0d8585aef1b",
        "name" : "Funnel",
        "type" : "FUNNEL"
      },
      "flowFileExpiration" : "0 sec",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "identifier" : "fd782ed4-aef7-3e38-8248-01640a2604fa",
      "instanceIdentifier" : "fd782ed4-aef7-3e38-bfa8-58ab86467cb2",
      "labelIndex" : 1,
      "loadBalanceCompression" : "DO_NOT_COMPRESS",
      "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
      "name" : "",
      "partitioningAttribute" : "",
      "prioritizers" : [ ],
      "selectedRelationships" : [ "unmatched" ],
      "source" : {
        "comments" : "",
        "groupId" : "ad86f407-8631-318b-8230-76d1d7164043",
        "id" : "32b96eda-8796-3cbd-8845-9c13d368f451",
        "instanceIdentifier" : "32b96eda-8796-3cbd-8320-bb8c728aeaf1",
        "name" : "ExtractText",
        "type" : "PROCESSOR"
      },
      "zIndex" : 0
    }, {
      "backPressureDataSizeThreshold" : "1 GB",
      "backPressureObjectThreshold" : 10000,
      "bends" : [ {
        "x" : 648.0,
        "y" : 336.0
      } ],
      "componentType" : "CONNECTION",
      "destination" : {
        "comments" : "",
        "groupId" : "ad86f407-8631-318b-8230-76d1d7164043",
        "id" : "32b96eda-8796-3cbd-8845-9c13d368f451",
        "instanceIdentifier" : "32b96eda-8796-3cbd-8320-bb8c728aeaf1",
        "name" : "ExtractText",
        "type" : "PROCESSOR"
      },
      "flowFileExpiration" : "0 sec",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "identifier" : "aad4cb51-0cdd-3e21-aba6-16b3a82cb98f",
      "instanceIdentifier" : "aad4cb51-0cdd-3e21-9176-6b0862368880",
      "labelIndex" : 1,
      "loadBalanceCompression" : "DO_NOT_COMPRESS",
      "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
      "name" : "",
      "partitioningAttribute" : "",
      "prioritizers" : [ ],
      "selectedRelationships" : [ "success" ],
      "source" : {
        "comments" : "",
        "groupId" : "ad86f407-8631-318b-8230-76d1d7164043",
        "id" : "e70f7c86-42f6-352a-b9b4-f02c9678ab24",
        "instanceIdentifier" : "e70f7c86-42f6-352a-866a-17792a25c0bb",
        "name" : "GetHTTP",
        "type" : "PROCESSOR"
      },
      "zIndex" : 0
    }, {
      "backPressureDataSizeThreshold" : "1 GB",
      "backPressureObjectThreshold" : 10000,
      "bends" : [ {
        "x" : 1088.0,
        "y" : 336.0
      } ],
      "componentType" : "CONNECTION",
      "destination" : {
        "comments" : "",
        "groupId" : "ad86f407-8631-318b-8230-76d1d7164043",
        "id" : "32d35b0d-daab-3189-9f86-71fbe584a6c7",
        "instanceIdentifier" : "32d35b0d-daab-3189-b8ae-148fcd2e1c26",
        "name" : "PutSQL",
        "type" : "PROCESSOR"
      },
      "flowFileExpiration" : "0 sec",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "identifier" : "3bff9b4b-de39-38cd-9d35-684df2049fd3",
      "instanceIdentifier" : "3bff9b4b-de39-38cd-b18f-cb7e8c81d733",
      "labelIndex" : 1,
      "loadBalanceCompression" : "DO_NOT_COMPRESS",
      "loadBalanceStrategy" : "DO_NOT_LOAD_BALANCE",
      "name" : "",
      "partitioningAttribute" : "",
      "prioritizers" : [ ],
      "selectedRelationships" : [ "matched" ],
      "source" : {
        "comments" : "",
        "groupId" : "ad86f407-8631-318b-8230-76d1d7164043",
        "id" : "32b96eda-8796-3cbd-8845-9c13d368f451",
        "instanceIdentifier" : "32b96eda-8796-3cbd-8320-bb8c728aeaf1",
        "name" : "ExtractText",
        "type" : "PROCESSOR"
      },
      "zIndex" : 0
    } ],
    "controllerServices" : [ {
      "bulletinLevel" : "WARN",
      "bundle" : {
        "artifact" : "nifi-dbcp-service-nar",
        "group" : "org.apache.nifi",
        "version" : "1.24.0"
      },
      "comments" : "",
      "componentType" : "CONTROLLER_SERVICE",
      "controllerServiceApis" : [ {
        "bundle" : {
          "artifact" : "nifi-standard-services-api-nar",
          "group" : "org.apache.nifi",
          "version" : "1.24.0"
        },
        "type" : "org.apache.nifi.dbcp.DBCPService"
      } ],
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "identifier" : "bed8843c-c85e-3b34-844d-0804c6d35999",
      "instanceIdentifier" : "bed8843c-c85e-3b34-887a-548f4db09724",
      "name" : "DBCPConnectionPool",
      "properties" : {
        "dbcp-min-idle-conns" : "0",
        "Max Wait Time" : "500 millis",
        "Database Driver Class Name" : "org.postgresql.Driver",
        "dbcp-min-evictable-idle-time" : "30 mins",
        "Max Total Connections" : "8",
        "dbcp-max-conn-lifetime" : "-1",
        "Database Connection URL" : "${jdbcUrl}",
        "dbcp-time-between-eviction-runs" : "-1",
        "Database User" : "${dbUser}",
        "dbcp-soft-min-evictable-idle-time" : "-1",
        "database-driver-locations" : "/opt/drivers/postgres",
        "dbcp-max-idle-conns" : "8",
        "Password" : "[[ dbPassword ]]"
      },
      "propertyDescriptors" : {
        "kerberos-password" : {
          "displayName" : "Kerberos Password",
          "identifiesControllerService" : false,
          "name" : "kerberos-password",
          "sensitive" : true
        },
        "dbcp-min-idle-conns" : {
          "displayName" : "Minimum Idle Connections",
          "identifiesControllerService" : false,
          "name" : "dbcp-min-idle-conns",
          "sensitive" : false
        },
        "Max Wait Time" : {
          "displayName" : "Max Wait Time",
          "identifiesControllerService" : false,
          "name" : "Max Wait Time",
          "sensitive" : false
        },
        "Database Driver Class Name" : {
          "displayName" : "Database Driver Class Name",
          "identifiesControllerService" : false,
          "name" : "Database Driver Class Name",
          "sensitive" : false
        },
        "dbcp-min-evictable-idle-time" : {
          "displayName" : "Minimum Evictable Idle Time",
          "identifiesControllerService" : false,
          "name" : "dbcp-min-evictable-idle-time",
          "sensitive" : false
        },
        "kerberos-principal" : {
          "displayName" : "Kerberos Principal",
          "identifiesControllerService" : false,
          "name" : "kerberos-principal",
          "sensitive" : false
        },
        "Max Total Connections" : {
          "displayName" : "Max Total Connections",
          "identifiesControllerService" : false,
          "name" : "Max Total Connections",
          "sensitive" : false
        },
        "kerberos-credentials-service" : {
          "displayName" : "Kerberos Credentials Service",
          "identifiesControllerService" : true,
          "name" : "kerberos-credentials-service",
          "sensitive" : false
        },
        "dbcp-max-conn-lifetime" : {
          "displayName" : "Max Connection Lifetime",
          "identifiesControllerService" : false,
          "name" : "dbcp-max-conn-lifetime",
          "sensitive" : false
        },
        "Validation-query" : {
          "displayName" : "Validation query",
          "identifiesControllerService" : false,
          "name" : "Validation-query",
          "sensitive" : false
        },
        "Database Connection URL" : {
          "displayName" : "Database Connection URL",
          "identifiesControllerService" : false,
          "name" : "Database Connection URL",
          "sensitive" : false
        },
        "dbcp-time-between-eviction-runs" : {
          "displayName" : "Time Between Eviction Runs",
          "identifiesControllerService" : false,
          "name" : "dbcp-time-between-eviction-runs",
          "sensitive" : false
        },
        "Database User" : {
          "displayName" : "Database User",
          "identifiesControllerService" : false,
          "name" : "Database User",
          "sensitive" : false
        },
        "kerberos-user-service" : {
          "displayName" : "Kerberos User Service",
          "identifiesControllerService" : true,
          "name" : "kerberos-user-service",
          "sensitive" : false
        },
        "dbcp-soft-min-evictable-idle-time" : {
          "displayName" : "Soft Minimum Evictable Idle Time",
          "identifiesControllerService" : false,
          "name" : "dbcp-soft-min-evictable-idle-time",
          "sensitive" : false
        },
        "database-driver-locations" : {
          "displayName" : "Database Driver Location(s)",
          "identifiesControllerService" : false,
          "name" : "database-driver-locations",
          "resourceDefinition" : {
            "cardinality" : "MULTIPLE",
            "resourceTypes" : [ "URL", "FILE", "DIRECTORY" ]
          },
          "sensitive" : false
        },
        "dbcp-max-idle-conns" : {
          "displayName" : "Max Idle Connections",
          "identifiesControllerService" : false,
          "name" : "dbcp-max-idle-conns",
          "sensitive" : false
        },
        "Password" : {
          "displayName" : "Password",
          "identifiesControllerService" : false,
          "name" : "Password",
          "sensitive" : true
        }
      },
      "scheduledState" : "DISABLED",
      "type" : "org.apache.nifi.dbcp.DBCPConnectionPool"
    }, {
      "bulletinLevel" : "WARN",
      "bundle" : {
        "artifact" : "nifi-ssl-context-service-nar",
        "group" : "org.apache.nifi",
        "version" : "1.24.0"
      },
      "comments" : "",
      "componentType" : "CONTROLLER_SERVICE",
      "controllerServiceApis" : [ {
        "bundle" : {
          "artifact" : "nifi-standard-services-api-nar",
          "group" : "org.apache.nifi",
          "version" : "1.24.0"
        },
        "type" : "org.apache.nifi.ssl.SSLContextService"
      } ],
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "identifier" : "d1c57bde-4f3b-3ad9-ae74-5b44fb96cd0a",
      "instanceIdentifier" : "d1c57bde-4f3b-3ad9-9800-79565a41624c",
      "name" : "StandardSSLContextService",
      "properties" : {
        "Truststore Type" : "JKS",
        "SSL Protocol" : "TLSv1.3",
        "Truststore Filename" : "${truststoreFilename}",
        "Truststore Password" : "[[ truststorePassword ]]"
      },
      "propertyDescriptors" : {
        "Truststore Type" : {
          "displayName" : "Truststore Type",
          "identifiesControllerService" : false,
          "name" : "Truststore Type",
          "sensitive" : false
        },
        "SSL Protocol" : {
          "displayName" : "TLS Protocol",
          "identifiesControllerService" : false,
          "name" : "SSL Protocol",
          "sensitive" : false
        },
        "Keystore Type" : {
          "displayName" : "Keystore Type",
          "identifiesControllerService" : false,
          "name" : "Keystore Type",
          "sensitive" : false
        },
        "Truststore Filename" : {
          "displayName" : "Truststore Filename",
          "identifiesControllerService" : false,
          "name" : "Truststore Filename",
          "resourceDefinition" : {
            "cardinality" : "SINGLE",
            "resourceTypes" : [ "FILE" ]
          },
          "sensitive" : false
        },
        "Keystore Password" : {
          "displayName" : "Keystore Password",
          "identifiesControllerService" : false,
          "name" : "Keystore Password",
          "sensitive" : true
        },
        "key-password" : {
          "displayName" : "Key Password",
          "identifiesControllerService" : false,
          "name" : "key-password",
          "sensitive" : true
        },
        "Truststore Password" : {
          "displayName" : "Truststore Password",
          "identifiesControllerService" : false,
          "name" : "Truststore Password",
          "sensitive" : true
        },
        "Keystore Filename" : {
          "displayName" : "Keystore Filename",
          "identifiesControllerService" : false,
          "name" : "Keystore Filename",
          "resourceDefinition" : {
            "cardinality" : "SINGLE",
            "resourceTypes" : [ "FILE" ]
          },
          "sensitive" : false
        }
      },
      "scheduledState" : "DISABLED",
      "type" : "org.apache.nifi.ssl.StandardSSLContextService"
    } ],
    "defaultBackPressureDataSizeThreshold" : "1 GB",
    "defaultBackPressureObjectThreshold" : 10000,
    "defaultFlowFileExpiration" : "0 sec",
    "flowFileConcurrency" : "UNBOUNDED",
    "flowFileOutboundPolicy" : "STREAM_WHEN_AVAILABLE",
    "funnels" : [ {
      "componentType" : "FUNNEL",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "identifier" : "b0cf515e-e112-3ed4-8ebf-cabcb17e004c",
      "instanceIdentifier" : "b0cf515e-e112-3ed4-8c40-e0d8585aef1b",
      "position" : {
        "x" : 824.0,
        "y" : 456.0
      }
    } ],
    "identifier" : "ad86f407-8631-318b-8230-76d1d7164043",
    "inputPorts" : [ ],
    "instanceIdentifier" : "c9143fc0-c492-3fa7-95ae-81c7de3245af",
    "labels" : [ {
      "componentType" : "LABEL",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "height" : 32.0,
      "identifier" : "46bed543-1d68-3b65-ab16-31d5f1da3581",
      "instanceIdentifier" : "46bed543-1d68-3b65-818e-320e3ed71636",
      "label" : "1. получить даные из REST API",
      "position" : {
        "x" : 248.0,
        "y" : 120.0
      },
      "style" : {
        "font-size" : "16px"
      },
      "width" : 256.0,
      "zIndex" : 0
    }, {
      "componentType" : "LABEL",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "height" : 32.0,
      "identifier" : "dec730c0-3afc-3d8d-9146-9b6835417fb4",
      "instanceIdentifier" : "dec730c0-3afc-3d8d-a59b-abdef726e8cf",
      "label" : "3. сохранить полученные данные в БД",
      "position" : {
        "x" : 1136.0,
        "y" : 120.0
      },
      "style" : {
        "font-size" : "16px"
      },
      "width" : 312.0,
      "zIndex" : 0
    }, {
      "componentType" : "LABEL",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "height" : 48.0,
      "identifier" : "c5f3ebbc-ac78-3f57-9a08-167295392e1d",
      "instanceIdentifier" : "c5f3ebbc-ac78-3f57-aa21-052f9e0e4cfd",
      "label" : "2. перенести содержимое \nв атрибут flow file-а (для PutSQL)",
      "position" : {
        "x" : 672.0,
        "y" : 104.0
      },
      "style" : {
        "font-size" : "16px"
      },
      "width" : 272.0,
      "zIndex" : 0
    } ],
    "logFileSuffix" : "",
    "name" : "http-2-db-dev",
    "outputPorts" : [ ],
    "position" : {
      "x" : 659.999887235238,
      "y" : 320.00000615823
    },
    "processGroups" : [ ],
    "processors" : [ {
      "autoTerminatedRelationships" : [ ],
      "backoffMechanism" : "PENALIZE_FLOWFILE",
      "bulletinLevel" : "WARN",
      "bundle" : {
        "artifact" : "nifi-standard-nar",
        "group" : "org.apache.nifi",
        "version" : "1.24.0"
      },
      "comments" : "",
      "componentType" : "PROCESSOR",
      "concurrentlySchedulableTaskCount" : 0,
      "executionNode" : "ALL",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "identifier" : "32b96eda-8796-3cbd-8845-9c13d368f451",
      "instanceIdentifier" : "32b96eda-8796-3cbd-8320-bb8c728aeaf1",
      "maxBackoffPeriod" : "10 mins",
      "name" : "ExtractText",
      "penaltyDuration" : "30 sec",
      "position" : {
        "x" : 672.0,
        "y" : 152.0
      },
      "properties" : {
        "Enable Unicode Predefined Character Classes" : "false",
        "Permit Whitespace and Comments in Pattern" : "false",
        "Enable Unicode-aware Case Folding" : "false",
        "sql.args.1.value" : "(?s)(^.*$)",
        "Enable DOTALL Mode" : "false",
        "Enable Unix Lines Mode" : "false",
        "extract-text-enable-named-groups" : "false",
        "Maximum Buffer Size" : "10 MB",
        "Enable Canonical Equivalence" : "false",
        "Enable Case-insensitive Matching" : "false",
        "Enable Multiline Mode" : "false",
        "Maximum Capture Group Length" : "1024",
        "sql.args.1.type" : "12",
        "Enable Literal Parsing of the Pattern" : "false",
        "Character Set" : "UTF-8",
        "Include Capture Group 0" : "true",
        "extract-text-enable-repeating-capture-group" : "false"
      },
      "propertyDescriptors" : {
        "Enable Unicode Predefined Character Classes" : {
          "displayName" : "Enable Unicode Predefined Character Classes",
          "identifiesControllerService" : false,
          "name" : "Enable Unicode Predefined Character Classes",
          "sensitive" : false
        },
        "Permit Whitespace and Comments in Pattern" : {
          "displayName" : "Permit Whitespace and Comments in Pattern",
          "identifiesControllerService" : false,
          "name" : "Permit Whitespace and Comments in Pattern",
          "sensitive" : false
        },
        "Enable Unicode-aware Case Folding" : {
          "displayName" : "Enable Unicode-aware Case Folding",
          "identifiesControllerService" : false,
          "name" : "Enable Unicode-aware Case Folding",
          "sensitive" : false
        },
        "sql.args.1.value" : {
          "displayName" : "sql.args.1.value",
          "identifiesControllerService" : false,
          "name" : "sql.args.1.value",
          "sensitive" : false
        },
        "Enable DOTALL Mode" : {
          "displayName" : "Enable DOTALL Mode",
          "identifiesControllerService" : false,
          "name" : "Enable DOTALL Mode",
          "sensitive" : false
        },
        "Enable Unix Lines Mode" : {
          "displayName" : "Enable Unix Lines Mode",
          "identifiesControllerService" : false,
          "name" : "Enable Unix Lines Mode",
          "sensitive" : false
        },
        "extract-text-enable-named-groups" : {
          "displayName" : "Enable named group support",
          "identifiesControllerService" : false,
          "name" : "extract-text-enable-named-groups",
          "sensitive" : false
        },
        "Maximum Buffer Size" : {
          "displayName" : "Maximum Buffer Size",
          "identifiesControllerService" : false,
          "name" : "Maximum Buffer Size",
          "sensitive" : false
        },
        "Enable Canonical Equivalence" : {
          "displayName" : "Enable Canonical Equivalence",
          "identifiesControllerService" : false,
          "name" : "Enable Canonical Equivalence",
          "sensitive" : false
        },
        "Enable Case-insensitive Matching" : {
          "displayName" : "Enable Case-insensitive Matching",
          "identifiesControllerService" : false,
          "name" : "Enable Case-insensitive Matching",
          "sensitive" : false
        },
        "Enable Multiline Mode" : {
          "displayName" : "Enable Multiline Mode",
          "identifiesControllerService" : false,
          "name" : "Enable Multiline Mode",
          "sensitive" : false
        },
        "Maximum Capture Group Length" : {
          "displayName" : "Maximum Capture Group Length",
          "identifiesControllerService" : false,
          "name" : "Maximum Capture Group Length",
          "sensitive" : false
        },
        "sql.args.1.type" : {
          "displayName" : "sql.args.1.type",
          "identifiesControllerService" : false,
          "name" : "sql.args.1.type",
          "sensitive" : false
        },
        "Enable Literal Parsing of the Pattern" : {
          "displayName" : "Enable Literal Parsing of the Pattern",
          "identifiesControllerService" : false,
          "name" : "Enable Literal Parsing of the Pattern",
          "sensitive" : false
        },
        "Character Set" : {
          "displayName" : "Character Set",
          "identifiesControllerService" : false,
          "name" : "Character Set",
          "sensitive" : false
        },
        "Include Capture Group 0" : {
          "displayName" : "Include Capture Group 0",
          "identifiesControllerService" : false,
          "name" : "Include Capture Group 0",
          "sensitive" : false
        },
        "extract-text-enable-repeating-capture-group" : {
          "displayName" : "Enable repeating capture group",
          "identifiesControllerService" : false,
          "name" : "extract-text-enable-repeating-capture-group",
          "sensitive" : false
        }
      },
      "retriedRelationships" : [ ],
      "retryCount" : 10,
      "runDurationMillis" : 0,
      "scheduledState" : "ENABLED",
      "schedulingPeriod" : "0 sec",
      "schedulingStrategy" : "EVENT_DRIVEN",
      "style" : { },
      "type" : "org.apache.nifi.processors.standard.ExtractText",
      "yieldDuration" : "1 sec"
    }, {
      "autoTerminatedRelationships" : [ ],
      "backoffMechanism" : "PENALIZE_FLOWFILE",
      "bulletinLevel" : "WARN",
      "bundle" : {
        "artifact" : "nifi-standard-nar",
        "group" : "org.apache.nifi",
        "version" : "1.24.0"
      },
      "comments" : "",
      "componentType" : "PROCESSOR",
      "concurrentlySchedulableTaskCount" : 1,
      "executionNode" : "PRIMARY",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "identifier" : "e70f7c86-42f6-352a-b9b4-f02c9678ab24",
      "instanceIdentifier" : "e70f7c86-42f6-352a-866a-17792a25c0bb",
      "maxBackoffPeriod" : "10 mins",
      "name" : "GetHTTP",
      "penaltyDuration" : "30 sec",
      "position" : {
        "x" : 248.0,
        "y" : 152.0
      },
      "properties" : {
        "redirect-cookie-policy" : "default",
        "Filename" : "file",
        "URL" : "${url}",
        "Connection Timeout" : "30 sec",
        "Data Timeout" : "30 sec",
        "SSL Context Service" : "d1c57bde-4f3b-3ad9-ae74-5b44fb96cd0a",
        "Username" : "[[ restApiUser ]]",
        "Follow Redirects" : "false",
        "Password" : "[[ restApiPassword ]]"
      },
      "propertyDescriptors" : {
        "Proxy Host" : {
          "displayName" : "Proxy Host",
          "identifiesControllerService" : false,
          "name" : "Proxy Host",
          "sensitive" : false
        },
        "redirect-cookie-policy" : {
          "displayName" : "Redirect Cookie Policy",
          "identifiesControllerService" : false,
          "name" : "redirect-cookie-policy",
          "sensitive" : false
        },
        "proxy-configuration-service" : {
          "displayName" : "Proxy Configuration Service",
          "identifiesControllerService" : true,
          "name" : "proxy-configuration-service",
          "sensitive" : false
        },
        "Filename" : {
          "displayName" : "Filename",
          "identifiesControllerService" : false,
          "name" : "Filename",
          "sensitive" : false
        },
        "User Agent" : {
          "displayName" : "User Agent",
          "identifiesControllerService" : false,
          "name" : "User Agent",
          "sensitive" : false
        },
        "Proxy Port" : {
          "displayName" : "Proxy Port",
          "identifiesControllerService" : false,
          "name" : "Proxy Port",
          "sensitive" : false
        },
        "URL" : {
          "displayName" : "URL",
          "identifiesControllerService" : false,
          "name" : "URL",
          "sensitive" : false
        },
        "Connection Timeout" : {
          "displayName" : "Connection Timeout",
          "identifiesControllerService" : false,
          "name" : "Connection Timeout",
          "sensitive" : false
        },
        "Data Timeout" : {
          "displayName" : "Data Timeout",
          "identifiesControllerService" : false,
          "name" : "Data Timeout",
          "sensitive" : false
        },
        "SSL Context Service" : {
          "displayName" : "SSL Context Service",
          "identifiesControllerService" : true,
          "name" : "SSL Context Service",
          "sensitive" : false
        },
        "Username" : {
          "displayName" : "Username",
          "identifiesControllerService" : false,
          "name" : "Username",
          "sensitive" : false
        },
        "Accept Content-Type" : {
          "displayName" : "Accept Content-Type",
          "identifiesControllerService" : false,
          "name" : "Accept Content-Type",
          "sensitive" : false
        },
        "Follow Redirects" : {
          "displayName" : "Follow Redirects",
          "identifiesControllerService" : false,
          "name" : "Follow Redirects",
          "sensitive" : false
        },
        "Password" : {
          "displayName" : "Password",
          "identifiesControllerService" : false,
          "name" : "Password",
          "sensitive" : true
        }
      },
      "retriedRelationships" : [ ],
      "retryCount" : 10,
      "runDurationMillis" : 0,
      "scheduledState" : "ENABLED",
      "schedulingPeriod" : "10 sec",
      "schedulingStrategy" : "TIMER_DRIVEN",
      "style" : { },
      "type" : "org.apache.nifi.processors.standard.GetHTTP",
      "yieldDuration" : "1 sec"
    }, {
      "autoTerminatedRelationships" : [ "success" ],
      "backoffMechanism" : "PENALIZE_FLOWFILE",
      "bulletinLevel" : "WARN",
      "bundle" : {
        "artifact" : "nifi-standard-nar",
        "group" : "org.apache.nifi",
        "version" : "1.24.0"
      },
      "comments" : "",
      "componentType" : "PROCESSOR",
      "concurrentlySchedulableTaskCount" : 1,
      "executionNode" : "ALL",
      "groupIdentifier" : "ad86f407-8631-318b-8230-76d1d7164043",
      "identifier" : "32d35b0d-daab-3189-9f86-71fbe584a6c7",
      "instanceIdentifier" : "32d35b0d-daab-3189-b8ae-148fcd2e1c26",
      "maxBackoffPeriod" : "10 mins",
      "name" : "PutSQL",
      "penaltyDuration" : "30 sec",
      "position" : {
        "x" : 1136.0,
        "y" : 152.0
      },
      "properties" : {
        "Support Fragmented Transactions" : "true",
        "putsql-sql-statement" : "insert into table values (?)",
        "Batch Size" : "100",
        "Obtain Generated Keys" : "false",
        "JDBC Connection Pool" : "bed8843c-c85e-3b34-844d-0804c6d35999",
        "database-session-autocommit" : "false",
        "rollback-on-failure" : "false"
      },
      "propertyDescriptors" : {
        "Support Fragmented Transactions" : {
          "displayName" : "Support Fragmented Transactions",
          "identifiesControllerService" : false,
          "name" : "Support Fragmented Transactions",
          "sensitive" : false
        },
        "putsql-sql-statement" : {
          "displayName" : "SQL Statement",
          "identifiesControllerService" : false,
          "name" : "putsql-sql-statement",
          "sensitive" : false
        },
        "Transaction Timeout" : {
          "displayName" : "Transaction Timeout",
          "identifiesControllerService" : false,
          "name" : "Transaction Timeout",
          "sensitive" : false
        },
        "Batch Size" : {
          "displayName" : "Batch Size",
          "identifiesControllerService" : false,
          "name" : "Batch Size",
          "sensitive" : false
        },
        "Obtain Generated Keys" : {
          "displayName" : "Obtain Generated Keys",
          "identifiesControllerService" : false,
          "name" : "Obtain Generated Keys",
          "sensitive" : false
        },
        "JDBC Connection Pool" : {
          "displayName" : "JDBC Connection Pool",
          "identifiesControllerService" : true,
          "name" : "JDBC Connection Pool",
          "sensitive" : false
        },
        "database-session-autocommit" : {
          "displayName" : "Database Session AutoCommit",
          "identifiesControllerService" : false,
          "name" : "database-session-autocommit",
          "sensitive" : false
        },
        "rollback-on-failure" : {
          "displayName" : "Rollback On Failure",
          "identifiesControllerService" : false,
          "name" : "rollback-on-failure",
          "sensitive" : false
        }
      },
      "retriedRelationships" : [ ],
      "retryCount" : 10,
      "runDurationMillis" : 0,
      "scheduledState" : "ENABLED",
      "schedulingPeriod" : "10 sec",
      "schedulingStrategy" : "TIMER_DRIVEN",
      "style" : { },
      "type" : "org.apache.nifi.processors.standard.PutSQL",
      "yieldDuration" : "1 sec"
    } ],
    "remoteProcessGroups" : [ ]
  },
  "flowEncodingVersion" : "1.0",
  "snapshotMetadata" : {
    "author" : "nifi",
    "bucketIdentifier" : null,
    "comments" : "",
    "flowIdentifier" : null,
    "link" : {
      "href" : "buckets/2cf0ac20-3ccd-41ed-97c1-97baecf45dfc/flows/20be5260-9249-4626-99d8-e95b7286836e/versions/1",
      "params" : {
        "rel" : "content"
      }
    },
    "timestamp" : 1779182541044,
    "version" : 0
  }
}
Скрытый текст
{
  "name": "http-2-db",
  "variables": [
    {
      "variable": {
        "name": "dbUser",
        "value": "db_prod_user"
      }
    },
    {
      "variable": {
        "name": "jdbcUrl",
        "value": "jdbc:postgresql://db_host:db_port/db?prepareThreshold=0&searchpath=schema"
      }
    },
    {
      "variable": {
        "name": "truststoreFilename",
        "value": "/home/user/path/to/file.jks"
      }
    },
    {
      "variable": {
        "name": "url",
        "value": "https://rest_api_host:rest_api_port/api/v1/some/resource"
      }
    }
  ],
  "api-data": [
    {
      "api": {
        "name": "registryFlowName",
        "value": "http-2-db"
      }
    },
    {
      "api": {
        "name": "processGroupPath",
        "value": "tmp>>prod>>http-2-db"
      }
    },
    {
      "api": {
        "name": "restApiUser",
        "value": "rest_api_prod_user"
      }
    },
    {
      "api": {
        "name": "restApiPassword",
        "value": "rest_api_prod_password"
      }
    },
    {
      "api": {
        "name": "dbPassword",
        "value": "db_prod_password"
      }
    },
    {
      "api": {
        "name": "truststorePassword",
        "value": "truststore_prod_password"
      }
    }
  ]
}

Деплоим в прод:

java -jar nifi-deployer-0.36-SNAPSHOT.jar -i bucket.name=default import.version.file=./http-2-db-1.24.json import.variable.file=./http-2-db-1.24-var.json nifi.url=https://.../nifi-api registry.url=https://.../nifi-registry-api user=... password=... ssl.keystore.filename=./nifi.p12

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

Скрытый текст
31.05.2026 12:43:59.914 [main] INFO deployTemplateAndMetadata2RegistryAndNiFi$ - read template from file ./http-2-db-1.24.json
31.05.2026 12:43:59.925 [main] INFO deployTemplateAndMetadata2RegistryAndNiFi$ - read metadata from file ./http-2-db-1.24-var.json
31.05.2026 12:44:00.138 [main] INFO deployTemplateAndMetadata2RegistryAndNiFi$ - parse template from file ./http-2-db-1.24.json
31.05.2026 12:44:00.188 [main] INFO ApiRegistry - get all flow versions from Registry
31.05.2026 12:44:00.189 [main] INFO ApiRegistry - get bucket ID for bucket name default
31.05.2026 12:44:00.189 [main] DEBUG HttpClient - GET https://.../nifi-registry-api/buckets
31.05.2026 12:44:00.196 [main] INFO AuthHandler - getting token from https://...
31.05.2026 12:44:00.698 [main] INFO ApiRegistry - using bucket 2cf0ac20-3ccd-41ed-97c1-97baecf45dfc
31.05.2026 12:44:00.698 [main] INFO ApiRegistry - get flow ID for flow name http-2-db and bucketId 2cf0ac20-3ccd-41ed-97c1-97baecf45dfc
31.05.2026 12:44:00.698 [main] DEBUG HttpClient - GET https://.../nifi-registry-api/buckets/2cf0ac20-3ccd-41ed-97c1-97baecf45dfc/flows
31.05.2026 12:44:01.097 [main] INFO ApiRegistry - create flow http-2-db
31.05.2026 12:44:01.105 [main] DEBUG HttpClient - POST https://.../nifi-registry-api/buckets/2cf0ac20-3ccd-41ed-97c1-97baecf45dfc/flows
31.05.2026 12:44:01.451 [main] INFO ApiRegistry - using flow d46dcc26-9ed3-4b9c-9f73-1f36738be47c
31.05.2026 12:44:01.451 [main] DEBUG HttpClient - GET https://.../nifi-registry-api/buckets/2cf0ac20-3ccd-41ed-97c1-97baecf45dfc/flows/d46dcc26-9ed3-4b9c-9f73-1f36738be47c/versions
31.05.2026 12:44:01.579 [main] INFO importFlow2Registry$ - update flow http-2-db in Registry
31.05.2026 12:44:01.580 [main] DEBUG HttpClient - POST https://.../nifi-registry-api/buckets/2cf0ac20-3ccd-41ed-97c1-97baecf45dfc/flows/d46dcc26-9ed3-4b9c-9f73-1f36738be47c/versions
31.05.2026 12:44:01.969 [main] INFO importFlow2Registry$ - ./http-2-db-1.24.json imported to Registry
31.05.2026 12:44:01.970 [main] INFO importGroupFromRegistry2NiFi$ - search/create recursively tmp>>prod>>http-2-db
31.05.2026 12:44:01.971 [main] DEBUG HttpClient - GET https://.../nifi-api/flow/process-groups/root
31.05.2026 12:44:01.971 [main] INFO AuthHandler - getting token from https://...
31.05.2026 12:44:02.595 [main] DEBUG HttpClient - GET https://.../nifi-api/flow/process-groups/cba7fcb9-0186-1000-0000-0000451bf611
31.05.2026 12:44:02.896 [Thread-21] INFO vci.getVersionControlInfo$ - get version control info for http-2-db recursively from root
31.05.2026 12:50:15.502 [main] DEBUG HttpClient - GET https://.../nifi-api/controller/registry-clients
31.05.2026 12:50:15.826 [main] INFO importGroupFromRegistry2NiFi$ - registry b463120f-018c-1000-0000-00006c563b27
31.05.2026 12:50:15.830 [main] DEBUG HttpClient - POST https://.../nifi-api/process-groups/3f508567-019e-1000-ffff-ffffc155c0cd/process-groups
31.05.2026 12:50:16.342 [main] INFO importGroupFromRegistry2NiFi$ - imported https://.../nifi?processGroupId=7d70f7a3-019e-1000-ffff-ffffdde8afc7 from Registry to NiFi

Поток создан в Registry:

 Поток создан в NIFi:

В свойство Username процессора GetHTTP установлено значение из файла метаданных:

 Изменение шаблона:

Скрытый текст

Для изменения существующего шаблона порядок действий в целом аналогичен созданию, только после выгрузки файла шаблона у разработчика есть его предыдущая версия — с ней можно сравнить новую. В свойствах компонентов, не поддерживающих Expression Language, в новой версии экспортированы конкретные значения, а в старой прописаны элементы api-data. Sensitive-свойства компонентов в новой версии не экспортированы (не сохраняются из NiFi в Registry), а в старой версии они также прописаны элементами api-data. Всё это видно в git diff и поддаётся относительно лёгкому восстановлению:

Возможные проблемы при деплое

В потоке сделаны изменения, но не закоммичены в Registry (в интерфейсе это отображается серой звездочкой и сообщением «Locally modified Versioned Process Group») — NiFi в этом состоянии не разрешает менять поток, чтобы не потерять потенциально полезные изменения в нём, вызов REST API возвращает 409 Conflict. Это невозможно разрулить автоматизированно. Нужно вручную откатить или закоммитить изменения потока в Registry перед деплоем (пункт Version контекстного меню). Пример:

Кроме того, NiFi не позволяет удалять очереди, в которых есть файлы (и соответственно процессоры, соединённые с этими очередями). Это также нужно разруливать вручную — запустить процессоры и прокачать все данные в потоке перед деплоем, если при обновлении должны удалиться процессоры или измениться их связи.

Заключение

Дополнительные возможности джарника:

  1. режим импорта только метаданных. Цель — быстрое обновление параметров потока, без пересоздания процессоров и связей между ними. Для этого не требуется файл шаблона, только файл метаданных и имя потока (параметр джарника parent.group.id)

  2. подстановка метаданных, общих для всех потоков (чтобы не писать одинаковые значения во множестве файлов) — реализовано параметром джарника api.data.default, метаданные оттуда добавляются к пользовательским метаданным при отсутствии по ключам. Возможный пример — адрес системы мониторинга, куда все потоки на стенде должны отправлять статистики/метрики своей работы (если эта фича реализована в шаблонах потоков). Этот адрес отличается между стендами, но бизнес-пользователи скорее всего не хотят его знать и писать в своих метаданных, так что логично добавить автоматически в Jenkins при сборке дистрибутива потока.

Заметка по длительности импорта: для обновления потока в NiFi нужно знать его UUID (идентификатор в методах REST API). Но джарник знает лишь имя в Registry (registryFlowName), так что рекурсивно обходит все process groups в NiFi в поисках той, которая связана с этим потоком в Registry. При количестве групп верхнего уровня около 300 время обхода составляет порядка нескольких минут. Это можно оптимизировать параметром джарника parent.group.id — искать группы не от корня, а от указанной. Если потоки в NiFi организованы в виде дерева, и если мы заранее знаем, в какую ветку должен попасть наш поток, можно её указать в этом параметре.

Особенности и ограничения:

  1. процесс обновления версии потока и переменных в NiFi асинхронный — отправили запрос, периодически проверяем статус. Чтобы не зависнуть навечно, джарник имеет параметры — интервал и количество итераций проверки

  2. если версионированная process group ссылается на controller services, объявленные в родительской не-версионированной группе, они не сохраняются в Registry при коммите, соответственно не экспортируются в файлы и не импортируются в целевой стенд, пропадают после цикла экспорта-импорта; джарник это не контролирует, надо вручную создавать такие компоненты в целевом стенде, или вообще так не делать, все компоненты потока (версионированной группы) создавать в нём самом

  3. при экспорте с single instance (например, из NiFi на localhost разработчика, если нет выделенного Dev стенда) всем процессорам ставится "executionNode": "ALL". Если целевой стенд кластерный, и если определённым процессорам нужен запуск на одной ноде, например primary, это надо вручную прописать в файле шаблона нужным процессорам — "executionNode": "PRIMARY"

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